Tuesday, July 6, 2010

How to do Everything Wrong With Servlets

When I interview people I often ask them
what an evil developer would do to make his code hard to test. Servlets
are a great example of what to do if you want it to be hard to test

Lets start with constructor. Constructor is the first thing I look
at since it tells me about my dependencies. In the case of servlets the
spec asks for no-argument constructor so that the container can control
the instantiation process. This is all fine but how is my Servlet
supposed to get a hold of its dependencies such as database connection
or any other object which needs to be shared between multiple servlets?
There is no good way! The only thing which I can do is to use global
state and singletons for inter servlet communication (which we have
covered in detailed here, here, and here).

Initialization process is no help here. Yes the container will call
a init method, but the only thing I can get a hold of in the
initialization process is the strings which I have placed in my XML.
What I really want is to share instances of objects between servlets so
that I can configure my collaborators. Again I am forced to result to
global state and singletons. But there is something worse about this.
Mainly the single responsibility principle. Each class is responsible
for exactly one thing. But in our case the servlet is responsible for
the logic, the wiring of itself to the collaborators and initialization
of the collaborators. Now, I like to place my initialization in a
single place so that I can control the order of initialization. But
servlets make this difficult since the servlet container does not
guarantee any order of initialization for your code. This means that
each servlet is a potential source of initialization. Which again
forces you to the singleton and lazy initialization model.

In Java you have single inheritance hierarchy, (which is a good
thing) but Servlets take that away from you by forcing you to inherit
from their class in essence giving you no inheritance choice at all.
Inheritance should be used when we need to take advantage of
polymorphism, but there is no polymorphism going on within servlets.
What passible purpose does inheriting from HttpServlet serve which
could not be addressed with an interface. Code reuse through
inheritance is quite possibly the worst offense in software
engineering. Composition over inheritance should always be the goal.
The worse thing about this is it sets a precedence which is followed on
most projects. Most web apps have deep inheritance hierarchies with
Servlet at the top which seem to serve no other purpose than code
reuse. Testing collaborators is easy since I can test each collaborator
in isolation, but if collaboration is replaced with inheritance now my
tests have to test the full inheritance hierarchy at once. Inheritance
is all or nothing proposition.

OK, if you managed to survive the constructor, initialization, and
inheritance, there are other subtle surprises waiting for you. We are
building web apps, therefor the most common object lifetime should be
equal to the duration of HTTP request. Instead we have servlets whose
lifetime is lifetime of the JVM. This creates a huge problem since all
of the HTTP request information/data now has to travel through the
stack rather than being a part of the object. In other words we have
static code-base and we push the data around through the method
arguments. Sounds like procedural code to me. I don’t want to get into
debate over OO vs Procedural, but I do want to point out that Java is
OO, use it in that way. If you want to write rocedural code, use a
procedural language. Play to the strengths of the language no to its
weakness.

Surely there is nothing else which servlets do wrong. Ohh, but there
is, let me introduce you to the Service Locators anty-pattern. The
HttpServletRequest and HttpServletResponse are two huge service
locators. They themselves hove nothing of interest, but they do have
getter methods which have things which I need. Well actually, I don’t
need that either. What I want is a User for example. What I have is
request. Let’s see does this look familiar?

String cookie = request.getCookie();long userId = cookieParser.parse(cookie).getUserId();
User user = userRepository.find(userId);

What I want is user but what I got is worthy of a Sherlock-Holms
detective story as I try to piece together the identity of the user. If
I had to do this in one location, I would not complain so much but I
have to do this in every single servlet. Please, just give me the user,
or whatever else which I need to get my work done. The thing is that
request has cookie, but it is not the cookie I want, it is the user id
which is embedded in the cookie. But it is not the user id, but the
user i really want. This Law of Demeter violation
is the direct result of two things: (1) service locator nature of the
request / response and (2) the wrong lifetime of the servlet.

Now imagine trying to test this thing. First I have to call new,
great that is easy as I have no argument constructor, but how do I get
all of my mock dependencies into the servelet as collaborators? My only
hope is setting some global state. But, wait the servlet is also
responsible for initialization which means it is loaded with new
operators, and those suck from the testability point of view. (see here)
Now I really want to test the home page but the home page servlet
inherits from the AuthorizedServlet which talks to the database to
authenticate. Great now my every test needs to mock out the
authentication even if it is not testing it. So if I change my
authentication all my test are broken. Now I really just want to create
a new User to test the home page but I have to place my user into a
Mock Repository and assign a mock id. I than have to create a mock
cookie and place it into a mock http request all the while hoping that
I will be able to give you a mock UserRepesitory through some global
state. ?I wish I was making this up, but Servlets are untestable in
their current form.

Let’s see if we can solve these problems. First forget servlets and
create your own classes which have your own minimal inheritance with
preference for composition with object lifetime which equals that of
the HTTP request so that your collaborators can ask for objects such as
User directly in the constructor. This way testing your code is easy.
Now for the servlet part, Since our code is HTTP servlet, request and
response free it is both easy to test and incompatible. Therefor the
job of the servlet is to call the new operators to build the object
graph of collaborators with the right lifetime and to delegate the
execution to our code. In essence the servlet becomes one big factory
and object look up. It is still untestable as ever, but at least there
is no logic to test there. Since the nature of the factories is that
they either work or they blow up spectacularly.

From http://misko.hevery.com/

No comments:

Post a Comment