Saturday, February 9, 2013

Regain control over your code - it's yours

As we have started using more and more complex and comfortable frameworks, we started to lose control over our own work, our source code. I find myself more often than I want to scratch my head about an issue outside of my code – just like the hundreds of others on Stackoverflow, trying to figure out what went wrong with someone else’s code.

It’s not acceptable that I’m wasting my time trying to correct something I don’t even have access to. A usual web framework is enormous and referenced by binaries only or even worse, the binding to some random libraries happens at runtime and I have no chance understanding what’s going on.

As a weekend project, I tried something very different: from the ground up. I threw away all the magical web frameworks I was working with (like ASP.NET MVC or Spring MVC) and started with the very things I needed.

I decided to embed the HTTP server into my application as relying on magical configurations of Tomcat or IIS was way too troublesome. I’ll just ask them to forward the web traffic to my application running as a simple process. Jetty was a trivial choice and only needed the following “configuration” to start an embedded server. No XML, no magic .properties, that’s it:

Server s = new Server(8080);
ServletContextHandler handler = 
 new ServletContextHandler(ServletContextHandler.SESSIONS);
ContextHandler context = new ContextHandler();
context.setContextPath("/");
context.setHandler(handler);
  
handler.addServlet(new ServletHolder(new BlogServlet()), "/blog");

HandlerCollection handlers = new HandlerCollection();
handlers.addHandler(context);

s.setHandler(handlers);
   
s.start();
s.join();

It’s so simple if something goes wrong I know what it is. Next thing I implemented the BlogServlet, which renders the HTML itself. As HTML rendering usually involves some kind of templates, so I looked for simple engines that do not need (or not even know about) servlets, HTTP or whatsoever. The servlet roughly looks like this:

public abstract class BaseServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
 resp.setContentType("text/html");
 resp.setStatus(HttpServletResponse.SC_OK);
 // ...
 String response = renderTemplate(data, template);
 resp.getWriter().write(response);
}
@Override

protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
 // ...
}
}

The template engine only knows about the template sting and data in and spits the rendered string out, that’s it. I chose Google Closure Templates as they are quite easy to work with, plus I can render them as Javascript, if I choose to build the HTML DOM on client side. I have to admit the IDE support for ASP.NET MVC Razor is by far better than editing the Closure Soy templates manually, but I found that it’s not really the limiting factor during development. In fact I found that most of the things I had though would be were not really limiting factors at all.

Nginx load balacing

I wanted load balancing too, so I chose the well-respected Nginx web server as the frontend. Does not do much, it receives the HTTP request and based on simple rules forwards them to my internal Jetty server. Nginx can reread its configuration file without restart, so I can add new nodes to it or changing the load balancing weights without a hiccup (it is even possible to upgrade Nginx executable without dropping a connection).

As I needed a web server and a JSON server too, I just created another package/process which receives the AJAX requests from the web pages (forwarded by Nginx too), and responds to them using the same logic through embedded Jetty but using JSON serialization instead of Closure templates.

Testing, Upgrading, A-B testing

Using the above approach yielded unforeseen benefits. One benefit is incredible testability. Every component in the system is ‘hand crafted’, no tight coupling to someone else’s framework, a non-mockable class, a hard coded parameter. If I want to test whether the HTML rendering works fine, I can test only that part without even starting an HTTP server, let alone automating web testing using Selenium or some other heavy cannons. Test data in, verifying the rendered HTML, all done within 10 milliseconds (no need for automated log in, visiting the page, entering some data, verifying the HTML– in 10.000 milliseconds).

The other benefit is A-B testing or new version testing built in. All I need to do is to start a new process (even on the same machine on a different port) and ask Nginx to forward some traffic to it (let’s say 10% of total requests). Very soon it’s obvious if something went wrong or my users don’t like the new design. Shutting down the new version involves changing a line in Nginx config. With large frameworks like ASP.NET it’s a much more complex and painful process. Usually the developer has a very nice environment set up as long as the requirements fall within boundaries, everything is fine. Good luck with anything else…

Roll your own
Pros
  • All the code is there, very little external dependency, bugs are easily tracked.
  • There is no need to teach anyone a huge framework – it’s just there.
  • Starting the web server takes about a second as there are no attached magic bindings happening runtime – unless I added them.
  • Every single part of the system is unit testable, making the heavy weight automated web ui testing less important or even obsolete.
  • Scaling and upgrading the system is simple and efficient. All it takes is adding a new machine, starting the self-contained process and configuring Nginx to forward traffic to it and.
  • The system is fast. As the HTTP request does not go through the unlimited layers of the clumsy frameworks, the throughput is surprisingly high.
  • As steep learning curve for the new joiners as I choose. It’s still possible to build a hard to understand system though…

Cons
  • No IDE support. Most of the large web frameworks have excellent IDE support, which gives confidence to the developers.
  • One has to understand HTTP/HTML well. The system does not give much guidance so it may not be the best approach for junior programmers working alone.
  • One has to understand how to build a well-designed system. This is not as easy as it sounds.
  • Authentication, authorization, distributed session state? You have to solve it yourself! It’s not hard but there is no default support for them.
  • Everything is a bit more inconvenient. For instance, no URL or POST parameter parsing into objects. All these small annoyances have to be resolved manually.
  • Lot of “Oh, I didn’t think about that” moments at the beginning.

Summary

Reducing external dependencies or even throwing away the huge but confortable environments come with a cost. The initial setup is definitely longer and more painful but for those who can build nine systems it’s well worth it. Although I still believe that the uniformity of large web frameworks like ASP.NET or Spring can reduce the risk of either a small or a large project where neither the team nor the skillset of the team could handle the freedom of a ground up solution.

Agree, disagree? Leave a comment!

No comments:

Post a Comment