Resource centric application

Posted on July 25, 2013

Web application frameworks, from cgi-bin through to PHP and Java Servlets all the way up to ASP.NET MVC, Ruby on Rails etc. are built around the paradigm of modelling a request and response pair: the application receives a request and then generates content, on demand, to return, as a response to the client. Each and every request, whether or not to the same resource, results in a little thread popping up (or being acquired from the thread pool) which executes a bunch of custom code. Often the same resource is requested multiple times and the exact same code will be executed and the exact same result will be returned.

In many situations, this burden on the web servers, is undesirable. So the more complete applications will serve cache headers which will tell various caches, at various points from (and sometimes inside) the web application, through the data centre, through the various ISPs, possibly through reverse proxies and finally on the client (browser or otherwise) that they needn’t make the same request to the web server again until the cache headers expire or are invalidated in some way (using etags etc.). On top of that other strategies will be employed, to separate certain resources, such as images and css files. Thus the server application is protected, by other layers between it and the client, from excess and unnecessary traffic.

It’s a complicated game. It requires putting smarts in your application, choosing cache policies, configuring extra layers of infrastructure such as reverse proxies. It can all too easily turn into a fragile system built using a delicate art form.

For some parts of the system, such as images, it is simpler to consider these resources as ‘static’ and publish them using a completely separate and more efficient mechanism (say a lightweight high performant file based web server or direct to CDNs) far away from the application platform. And it tends to be at these two extremes that we architect our applications.

The result is a simple split between static and dynamic content, with dynamic being clearly defined as any resource that requires computation of any sort, regardless of its lifespan.

This approach, of modelling your application around the request and response pair, in some cases, can dramatically increase its complexity. By using a request/response model for anything that requires computation the opportunity to simplify and clarify the application is lost.

An alternative approach is to model the resources themselves and looking at modelling the mechanism of change. For example, is the resource’s change well known such as on a time basis (every hour) or based on an event (someone has updated a piece of information)? By analysing these attributes of the different resources, and modelling them that way, allows alternative architectures to emerge which reduce the struggle against caches etc. It may even be possible, in some applications, to completely remove dynamic, on demand, computed resources altogether and reduce everything to static resources and thus make redundant the need for an always-on request and response driven web application.

Publishing systems such as Octopress and Jekyll take full advantage of this approach. The attributes of change in the engine are well understood and even allow for different rates of change depending on context. Change has been reduced to such a degree that all resources are static. For example, the Octopress application builds static content based on changes to the blog post source files at the content owners request (via a rake file). If the layout of the site is changed then the entire site is regenerated. Then, using simple mechanisms such as rsync - or even git to push deltas - Octopress’ publishing mechanism simply pushes the static files. Change is so well understood that even if you publish several pieces of content an hour, or only a few pieces every week, month or even year, the approach to resource production and publication is consistent and sound. The result, compared to similar systems in the same domain such as Wordpress, is that no special hosting is required, no concerns about downtime, no custom extensions to install on web servers, no custom running software etc. etc. In fact the delivery mechanism (github pages, s3, CDN, nginx) is not a concern of the publishing app itself and in no way affects the consistency of what is delivered to the client. This is an inspired employment of separation of concerns with all the benefits it begets.

There are other approaches which receive the same end goal. For example, a job could read events off of a queue and generate new static resources in response. Many applications have backend jobs that transfer data, in bulk, at regular time intervals: typically data is transferred from a third party system and directly into the web applications database, which the web application, at the moment of request, then fetches and transforms (into a presentable form more representative of its own domain). This process could be reversed by placing the responsibility of generating the resource as a static resource, on the batch process itself and thus completely bypassing the web application. Another approach is to memoize by generating static content directly off of the web application in a manner similar to warming a cache, except, rather than warming a cache the computed resource is published as a piece of static content elsewhere.

These various approaches have many advantages. Not only do they dramatically simplify the application itself, by removing the complexity around ‘protecting’ the web server from load, they remove, or drastically reduce, the problems of high availability, failover and zero downtime. Generally speaking, the processes that generate the static content will tolerate far, far, higher downtimes than a web application would. It is a far simpler, well known, solved, commodity problem to generate a high availability, scalable, zero downtime system for static resources. And it is far cheaper too.

Another simplification could be to treat resources as immutable. An immutable resource is incredibly simple in terms of cache headers etc. (which are still useful for traffic reduction) as they never run out; if the resource will never change then it can be cached indefinitely. Another side effect is that all resources, by default, have permalinks. Mechanisms can be employed to allow clients to obtain the lastest version, such as using redirects or publishing atom feeds.

Of course, there may be some functions of a system where this behaviour isn’t desirable and a request/response model is preferable. There are clear constraints to determine that case. For example, if resources changed at a far, far higher rate than they were read, or the number of the resources was so great that the sheer volume of static resources would be over whelming, or they were genuinely unique (or the majority was unique) per request or the resources life was so short, then perhaps, for those specific individual resources alone, the generate-on-demand model may be better suited. These types of situations are probably quite small, both in any singular application and across all domains. Particular examples may be shopping baskets (which are short lived) or search results (whose combination is so great and unknown). However, in these situations a hybrid approach could be taken.

The request/response models approach could be perceived as a form of premature optimization and over-engineering. It assumes that we are building systems that are highly dynamic in nature and therefore introduces complexity to the platform by optimizing for that scenario. For the vast majority of applications this is just not the case and therefore they inherit complexity for a solution that is unlikely to be required. It is a clear case of YAGNI. As applications such as Octopress show things can be dramatically simplified if these optimizations are rejected. By starting from the assumption that all resources can be static until proven otherwise.

Unfortunately, for the time being, frameworks are built on the request and response model as opposed to a resource, or hybrid model. For this reason, regardless of the architectural advantages, the productivity boost from using these established frameworks will outweigh the benefits.