June 6, 2011

Continuation-based web applications: just say no

Many people still seem to regard continuations as a possible or even preferable method for writing web applications. This blog post aims to dispel that notion and demonstrate that continuation-based web apps belong in the 90s.

Why are continuations good?

Anton van Straaten's excellent Continuations Continued argues that continuations are a good way to model server-side code, ergo they are a good way to implement server-side code. The modeling assertion is for some instances correct, the implementation assertion is not.

Continuations made sense for pre-JavaScript web applications, where each new functionality predicated on a possible user choice (ViaWeb's color picker palette being a good example) would lead to combinatorial growth in the complexity of the state machine behind the pages that that functionality would interface with. This is because the server was burdened with what is essentially transient client-side state. With AJAX, this is no longer the case.

Why are continuations bad for clients?

There are well-known problems with continuation-based web apps: bookmarks, history, and back/forward buttons don't work.

A web application session is a call-graph from the point of view of the browser, where the URLs are akin to procedures. HTTP interaction flows like a program, with the user making decisions of which procedure to invoke/URL to visit. By this analogy, using continuations is exactly like giving random names to all of the procedures in a program each time a procedure is called.

This is the core of the problem with continuation-based web applications. Everything that revolves around user control of accessing URLs (bookmarking, history, back/forward, etc.) breaks. This also makes it much harder to test continuation-based web applications programmatically and makes debugging harder.

One incidental advantage of this breakage is that some URLs do need to be unique and single-access to prevent cross-site attacks and duplicate form submissions. I argue that these mechanisms should be thought of as token-issuing state machines, and implemented explicitly. This leads to simpler code and manifest state.

Another strategy that has been used is storing continuations on the client side using cookies or URL query parameters. This approach is problematic for the amount of data it transmits on each request, and the possible security implications (the continuations need to be encrypted, and the keys frequently rotated and expired - however the expiry of continuations is exactly the problem that query-parameter serialized continuations were supposed to avoid - links that rely on continuations stored on the server cannot be bookmarked!).

Why are continuations bad for servers?

The essence of using continuations server-side is handing off control of inter-request state serialization to an implicit mechanism that is tied to the structure of application code.

Both data and logic are now intermingled and stored in opaque continuation structures. This makes the code hard to debug, state difficult to replicate for fail-over redundancy, problems difficult to reproduce, and control flow difficult to understand.

What should you do?

The ability of JavaScript to make HTTP requests without reloading the current page (AJAX) allows you to keep what is essentially client-side state on the client. The server is now responsible for a set of URIs, where each URI can be thought of as a separate service that can be modeled as a state machine. This keeps web application state and control flow manifest. Different parts of your web application (represented by different URIs) can now be completely isolated; any state interactions and dependencies between them become explicit.