Roll your own web framework in half an hour

i.e. Fun With ActionDispatch

So, a long time ago, in a land far far away there once lived a powerful programmer and this programmer had created a web framework called Rails. Rails was a massive and monolithic framework and about as hard to tame as a dragon and so the Ruby community went to work on solving that particular problem. Armed with hexes, swords and gems, the community surrounded the dragon and cut him up into tiny pieces with the result that what we today know as Rails is mostly a curated (or omakase, iyw) collection of modular functionality that actually works independently of the framework. The dragon got turned into a bunch of tiny and not too ugly dragons that play well with others.

So it has come to pass that making your own web framework in Ruby has now become a trivial undertaking, one that with practice can be completed in the first ad break of the Saturday night Bollywood blockbuster on Sony TV.

So, what’s in a framework then? In order to complete our framework before Katrina Kaif finishes eating her chocolate, we’re going to cheat a bit and change the definition of web framework. Well, we’re going to change it from Rails’ definition to something more in tune with this idea from Bob Martin from his eye-opening talk called Architecture The Lost Years http://www.youtube.com/watch?v=WpkDN78P884 (Highly reccommended if you haven’t seen it already) - A web framework is something that makes it possible to serve your application on the web.

By this definition, the web framework only does all http-y things basically translating http requests into method calls to the domain logic of your application and returning nice status codes and so on to complete the response. It also handles sessions, cookies and user authentication. All the domain logic including persisting data is handled by your application which sits inside its own gem.

So before we go any further, let’s answer the question that is burning in everyone’s mind - Why did Sarah Lund do it? Err….sorry…wrong audience. The question we’re going to answer is - For helvede dude! Why make your own framework?

Seriously?! ‘Because we can’ doesn’t answer anything!

Let me say this upfront. The chance that your new framework gets used by anyone apart from you answers true to #nil? However, there’s no doubt that you’re going to have a better understanding of many of the components that make up your day-to-day experience as a Ruby developer.

Also, Rails controllers are - how do I put this gently - pretty weird! There you are reading about this wonderful thing called Single Responsibility Principle and then you look at your Rails controller blithely ignoring object oriented purity and yeah, it makes you think. Don’t take my word for it - Gary Bernhardt nails it right here

Maybe Rails is just a little too omakase for you. Maybe you don’t entirely trust the LiveController. Maybe you just want to know what makes up a web framework. Whatever your reason for rolling your own framework, Rack and ActionDispatch have your back.

In the following example, we’re going to build a little chess playing API. Very little. Just enough to prove that we have a real web application. The source code for all this is here: https://github.com/svs/ryowf

Rack

Everyone knows what Rack is, right? It sits behind your web server, turns your web requests into nice Ruby hashes and provide a uniform API so you and your pair don’t have to spend hours arguing over names. A rack app is anything that responds to #call(env) with something like [200, {}, “hello world”]. Simple. Our little Rack app looks like this.

We have a method called #call which receives the env (which in turn contains the request and associated data) and responds with something Rack can send back to the web server. The first thing we need to do is to figure out which functionality was requested, for which we need a router. Routers are built with ActionDispatch.

ActionDispatch

ActionDispatch is a lovely little gem which lets you do this -

It’s basically the Rails router and the backbone of our web framework. Our router looks basically like this

The router in turn responds to call by calling the “handler”.

A small word about the design of our framework here. Since we don’t like Controllers that do 10 things, our controller only does two or three. Still a violation of SRP but hey - a tremendous improvement. So what we’re aiming for is for every controller action to be its own class. for example, instead of saying

we want to say something like this

This has a number of advantages. Each class is now back to doing way fewer things. We cannot accidentally expose an action to GET requests because the methods are named according to the request method.

The controller action is basically now calling out to the domain logic and returning a response. If you want more control over how the json is formatted, hand off to a formatter of your choice, use ActiveRecordSerializers or rabl or roar or any of a number of lovely presenters.

With this in mind, we can write our little chess playing API like so

Conclusion

Router.rb + ControllerAction.rb are together 43 LOC. That’s all you need to get a decent router and decent controller DSL to put your app on the web. Need authentication - use warden. Need authorization - use any of the authorization gems. Need caching —- you get the idea. Nothing in this approach precludes you from using the gems you love.

Everything else that Rails provides is basically either a massive convenience or insupportable bloat, depending on how you look at it. Rails does make the life of developers very easy by providing helpful rake tasks, caching, turbolinks, helpers, multiple environment support, code reloading, asset pipeline, a freaking ORM INSIDE THE WEB FRAMEWORK!! It saves hundreds of man weeks arguing about where to put stuff (simple, put anything anywhere). Some of these things are required if you’re generating HTML on the server but if like the rest of people who like to enjoy programming you are also mostly writing APIs and microservices, half of Rails is already not required. To do the job of exposing your business logic to the web, Rails is more and more seeming like overkill.

One of the goals behind the beautiful and very successful modularisation of Rails 3 was indeed to let a thousand web frameworks bloom and I would say Rails core team has done an admirable job. Whereas previously Rails was an all or nothing proposition, we now have the freedom to choose our individual tools, whether they be ORMs, templating engines or what have you. These same freedoms now allow us to step out into the world with really lean, focussed code in case we want to esches the excessive ceremony of Rails.

This is just one of the many reasons I love Ruby and the Ruby community so much!