
Java call stack – from HTTP upto JDBC as a picture (2006) - geococcyxc
https://ptrthomas.wordpress.com/2006/06/06/java-call-stack-from-http-upto-jdbc-as-a-picture/
======
p2t2p
Like what is the problem? Thanks to this stack trace your business logic fits
into two lines. Do you want to have lines and lines of your own transaction
manager? Lines and lines of your own DI framework? Do you suggest to write a
HTTP web server yourself? Why don't we count stack frames in kernel space? And
what do we have in JVM underneath us? And libc/Win32 layer?

I'm really annoyed by this kind of things like they have any meaning except "I
don't know shit about how abstractions work in computers"

%s/you/picture author

~~~
gambler
_> Thanks to this stack trace your business logic fits into two lines._

Two function calls for business logic, a hundred dependency libraries,
thousands of lines of stateful "initialization" code outside of the stack and
a fuckzillion XML files for configuration. But yeah, everything's great!

~~~
kitd
It's called abstraction for a reason. Do you write your system code from
scratch each time?

~~~
stevehb
It's called abstraction, but at some point that becomes a euphemism for
obfuscation.

------
stickfigure
I'm not sure I really see the problem. They picked a particularly pathological
example, with a lot of heavyweight frameworks. It's a choice.

I just counted the frames from my typical stack, a Guice/Hibernate project. It
was about 100 lines from a constraint violation handler in the postgres JDBC
driver up to Thread.run(). This seems pretty reasonable, especially with the
various layers I've added for authentication, declarative transaction
management, AOP logging, etc.

Furthermore, IntelliJ is pretty good about only showing me what I care about.
Overall I find the debugging experience no worse than Python or Ruby, and
_much_ better than exceptions from Python or Ruby native libraries. It's
orders of magnitude better than debugging Go, which destroys stack context
with every "if err != nil return err".

------
arrty88
This is beautiful. Working with ES2015 async await style promises on the
server now... you don’t even get valid stacks.

~~~
linkmotif
Yep, a totally simple stack you can follow. Wouldn’t it be nice in the async
world.

------
ceronman
This looks like the perfect example to illustrate the point that Rich Hickey
tries to make in "Simple made easy" [1].

This huge call stack has been designed to make your life as a developer _easy_
but the price you pay is an enormous amount of complexity.

I've been working a lot with a similar Java web stack and I feel how painful
this complexity is. What is worse, is that I think that a lot of this
complexity is incidental. There are libraries and frameworks designed to make
some things easier, but in the process end up creating a lot of problems that
then requires another library or framework to overcome that problem which also
has other problems and so on... The result is a huge stack like this.

One concrete example of this is Hibernate. A tool designed to make it easier
(apparently) to work with databases, but in the end create so many problems
that the medicine ends up being much worse than the disease.

Resolving an HTTP request that returns a the result of a database call should
not be this complicated! HTTP is simple! Why do we need so many calls to so
many things. I'm not advocating for a flat stack of course, but certainly a
stack this deep is a clear sign that something is wrong.

I very much agree with Rich Hickey, we need to stop thinking about how to make
things _easier_ and start thinking how to make them _simpler_.

[1] [https://www.infoq.com/presentations/Simple-Made-
Easy](https://www.infoq.com/presentations/Simple-Made-Easy)

~~~
le-mark
_Resolving an HTTP request that returns a the result of a database call should
not be this complicated! HTTP is simple! Why do we need so many calls to so
many things. I 'm not advocating for a flat stack of course, but certainly a
stack this deep is a clear sign that something is wrong._

Http is pretty simple, executing sql queries against a database is simple-ish
(close those connections!). Authentication, authorization, marshalling,
unmarshalling, transaction boundaries, ..., are not so simple, especially not
when all taken together.

People bemoan java as you are doing here, but the reality is other languages
and frameworks, any that attempt to address the same problems and concerns
have the same level of complexity. Java has the advantage of kick ass tooling,
debugging, and monitoring infrasture, a lot in the jvm itself (visualvm).

~~~
ceronman
Just to clarify, I am not criticizing the Java language. I'm criticising the
use of excessive layered frameworks that increase complexity.

I like Java. It's simple and performant and has excellent tooling. I just
don't like that sometimes I see a lot of incidental complexity in its
ecosystem.

------
linkmotif
I’ve seen this all over the place and it looks like a totally fine reasonable
stack trace to me.

Anyway while the stack (hibernate, etc) shown in this stacktrace is still
heavily in use, newer async java frameworks/tools usually result in very short
stacks for me. Sometimes I get a trace and it’s 5-6 frames. Of course then you
may not know how you got there. So it’s really not all that great.

------
rhapsodic
This should be titled "Acegi/Spring/Hibernate" call stack. And none of those
technologies are needed to build a JEE webapp.

~~~
meddlepal
While that's entirely true, the glueing of those frameworks together is very
common in the Enterprise Java development space especially around 2006 when
this was taken.

I'm a big fan of the JVM and the Java ecosystem but in many ways the JVM
ecosystem is split into two worlds: frameworks or libraries. This would be an
example of a framework heavy development model where god knows what is going
on between the outside world, your biz logic and the database calls.

~~~
yazr
> common in the Enterprise Java development space especially around 2006

Can you mention which frameworks and glues are common today ?

~~~
topspin
You can get a long way with just Spark:
[https://github.com/perwendel/spark](https://github.com/perwendel/spark)

~~~
klez
Official website, for those wondering

[http://sparkjava.com/](http://sparkjava.com/)

------
wrmsr
Rails code can get that deep, the difference being MRI doesn't have an
inlining jit...

------
asaph
Not every java web app needs to be this bloated. It's possible (even
practical) to build robust, database driven web apps/web services without
spring/hibernate and with far shallower stacktraces.

~~~
commandlinefan
It's possible, but in my experience, frowned upon for some reason.

------
augbog
Would love to see more visualizations of other frameworks in their respective
languages. I wouldn't be surprised if they were just as big.

------
rhacker
Not sure it is any smaller today. Some additional layers not seen are the DTO
transformations (not seen because they don't become part of the stack when
it's hitting JDBC) but two more major subsystems. Many IDEs now automatically
filter out these well known stacks from view.

Personally moving from a mostly Java background to entirely typescript on the
backend and front-end.

~~~
harunurhan
Although I like TypeScript as a language, and use Node.js, also really
appreciate sharing code such as (data types/interfaces etc.) btw frontend and
backend, I think call stack is much worse on the side, especially when you
start using frameworks. At least here you get a huge but relevant stack, you
can find your business logic, but Node.js it's not rare to get irrelevant
unhelpful stack traces which you can't figure out even if you dig in.

------
danbruc
This looks much worse than it actually is. You could implement it in a much
flatter way.

    
    
      function HandleRequest(request : Request) : Response
      {
        LogRequest(request)
      
        CheckAuthentication(request)
        CheckAuthorization(request)
      
        var response = DispatchAndHandleRequest(request)
      
        LogResponse(response)
      
        return response
      }
    

This way you would have no deep stack traces and all would be fine. But only
until you need to do some additional work somewhere right in the middle, say
patch malformed requests between authorization and dispatch. With the above
implementation you would be somewhat screwed, the best you could do would be
reimplementing HandleRequest() with your additional step in the middle.

But with implementations like those show in the article you just have a list
of steps with a common interface, each step does its work and then the next
step gets called. If you need to do something new somewhere in the middle, you
just add a new step to the list of steps in the right place and you are done,
possibly simply by putting the class name of the new step in a configuration
file.

    
    
      var steps =
      {
        new LogRequestStep(),
        new CheckAuthenticationStep(),
        new CheckAuthorizationStep(),
    
        // This stupid XYZ client always sends broken requests.
        new PatchMalformedRequestFromXyzStep(),
    
        new DispatchAndHandleRequestStep(),
        new LogResponseStep()
      }
    
      function HandleRequest(request : Request) : Response
      {
        var context = new Context(request)
    
        foreach (var step in steps)
        {
          step.Execute(context)
        }
    
        return context.Response
      }
    

This would still avoid deep stack traces because we are iterating over all the
steps but note that with this implementation we would not really be able to
abort processing the request somewhere in the middle, say if the authorization
check failed, but we could fix this by adding a flag to the context and check
it inside the loop after a step executed. But a more serious limitation is
that every step only gets one chance to act, note for example that we have two
separate steps for logging the request and the response.

Imagine we wanted to log the request duration, then we would need a step
getting the current time at the beginning and another one getting the current
time after the request was handled at the end. And the first step would have
to somehow communicate the processing start time to the second one, possibly
by storing it in the context. A much more elegant solution is to organize the
list of steps as a linked list with all steps looking like this.

    
    
      function Execute(request : Request) : Response
      {
        PreprocessRequest(request)
    
        var response = GetNextStep().Execute(request)
    
        PostprocessResponse(response)
    
        return response
      }
    

This creates those deep stack traces but it also creates a huge amount of
flexibility and extensibility. It surly looks crazy if you do not need it, but
when you need it, it is really easy with this model.

------
mozboz
OT, but anyone have similar stack traces that show call stacks or abstractions
in other layers, down to transistors, up to user?

------
bullen
My application server has 8 methods from thread to your code:
[http://test.rupy.se/?id=2](http://test.rupy.se/?id=2)

And an integrated distributed async-to-async database that is just as shallow:
[http://root.rupy.se](http://root.rupy.se)

Feel free to use it:
[http://github.com/tinspin/rupy](http://github.com/tinspin/rupy)

------
nickbauman
The fact that Spring even exists should be a big red flag for the value of
static typing. People routinely make mistakes using type systems. And when
done correctly I see static type systems work very well solving problems that
they themselves created.

~~~
jlg23
How do you get from deeply nested method calls to "people routinely make
mistakes using type systems"?

~~~
nickbauman
The gambit of static typing is that the type checker can attest correctness.
But the result, if indeed that the type checker does this (provide a mechanism
of provable correctness), you still have to deal with protocol bloat that type
systems incur. Hence the deeply nested method calls: that's a result of
adopting a static type system.

I'm saying that with all that overhead, people still routinely make mistakes
using static typing systems. They get the compiler to tell them that they're
correct but their code is still not working.

