Skip to main content

Crossing borders: Continuations, Web development, and Java programming

A stateful model for programmers, a stateless experience for users

Bruce Tate (bruce.tate@j2life.com), President, RapidRed
Bruce Tate
Bruce Tate is a father, mountain biker, and kayaker in Austin, Texas. He's the author of three best-selling Java books, including the Jolt winner Better, Faster, Lighter Java. He recently released Spring: A Developer's Notebook. He spent 13 years at IBM and is now the founder of the J2Life, LLC, consultancy, where he specializes in lightweight development strategies and architectures based on Java technology and Ruby.

Summary:  The Crossing borders series looks at how non-Java™ languages solve major problems and what those solutions mean to Java developers today. This article explores continuations, the technique behind frameworks like Smalltalk's Seaside. Continuation servers make it much easier to build Web applications by offering a stateful programming model without giving up the scalability inherent in statelessness.

View more content in this series

Date:  21 Mar 2006
Level:  Introductory
Activity:  6918 views
Comments:  

About this series

In the Crossing borders series, author Bruce Tate advances the notion that today's Java programmers are well served by learning other approaches and languages. The programming landscape has changed since Java technology was the obvious best choice for all development projects. Other frameworks are shaping the way Java frameworks are built, and the concepts you learn from other languages can inform your Java programming. The Python (or Ruby, or Smalltalk, or ... fill in the blank) code you write can change the way that you approach Java coding.

This series introduces you to programming concepts and techniques that are radically different from, but also directly applicable to, Java development. In some cases, you'll need to integrate the technology to take advantage of it. In others, you'll be able to apply the concepts directly. The individual tool isn't as important as the idea that other languages and frameworks can influence developers, frameworks, and even fundamental approaches in the Java community.

Common Web development is a sometimes sweet but often bitter experience. Java Web developers go to great lengths to provide a stateless model, but the resulting performance and ease of deployment make the efforts worthwhile. In this article, I talk about a radically different approach to Web development called the continuation server. By offering a stateful programming model without giving up the scalability inherent in statelessness, continuation servers make Web application development much easier.

The emergence of the Web

When the entire industry moved to Web development back in the mid-1990s, software developers were ecstatic. The client-server applications we'd been building were more user friendly than the terminal-plus-host alternatives, but several problems had plagued us:

  • Performance was often poor. One of the nice things about terminal-based development is that the communication costs are constrained by the programming model. Once those constraints were gone, we lacked the bandwidth, tools, or skill to build simple distributed applications.

  • The applications were not portable. Most client-server development environments dictated your hardware and software environments.

  • Applications were hard to deploy. You had to manage potentially thousands of clients independently.

  • The biggest costs were hidden. Deployment turned out to be the most serious constraint because post-production costs skyrocketed.

Client-server computing still moved forward. Often, companies made financial decisions based on the lower cost of software and hardware, only to see management costs explode after production. In 1995, the client-server model needed dramatic improvements -- and was about to get them.

Enter Web development

Web development exploded in the mid-1990s. With the emergence of the Java language, you could build distributed Web applications and simultaneously solve the most serious client-server problems with new capabilities:

  • Constrained communications. The request/response Web model has all of the characteristics of the terminal-based development. The user types into a form, makes a request, and gets a response. The chatty communications between the client and the server went away and performance improved.
  • Shared-nothing architectures. Servlet-based programming could be stateless. That meant that one servlet could serve any client, and a fixed pool of servlets could serve many more users. You didn't need to reserve a servlet for each user. Performance improved even more.
  • Common standards on the client. Suddenly, by deploying a common browser to all clients, you could build one interface and reach virtually all clients. Supporting many browser clients has since proven problematic but nowhere near as difficult as supporting native user-interface libraries. Many portability problems went away.
  • A better deployment model. By going to a browser as a common client, distribution got much simpler. A company could deploy an application to a few Internet servers and share them across the enterprise. The networking architecture could often share requests across multiple servers, so increasing capacity could be as easy as dropping in another server. The client-side deployment was as easy as making sure the correct browser was on a common client. Management got much simpler.

Performance, scalability, manageability, and portability all got much better, and the Internet revolution kicked into high gear. But you had to make some significant compromises.

Not Utopia

One little word -- stateless -- shifts a tremendous burden from the system to the developer. The payoff can't be disputed: you get fantastic scalability because you don't need to maintain one service, or servlet, per user. But the onus of managing state shifts from the programming language to the developer. Today, you can think of Web development as a series of stateless requests, as in Figure 1:


Figure 1. Web applications are made of request/response pairs
figure 1

With this model, the browser is firmly in control. The application can react only to requests made by the browser. That model works if requests are small and independent, but it fails miserably for Web applications that drive multipart applications. The most common example is online shopping. Click a Submit button twice and you've often unintentionally ordered two of the same item. Leave and come back two weeks later, and you might find the same items in your cart, without expecting that behavior.

You typically provide a stateful experience to your users by putting all data related to a conversation into a session and marking that user session at the client with a cookie, hidden fields, or URL variables. For each new request, you're forced to do the following in this order:

  • Get the user's identifier from the client
  • Restore the conversation state from the session
  • Process the user's request
  • Build the response
  • Store the conversation state in the session
  • Send the response to the user

I've oversimplified the problem because using a stateless model to simulate stateful applications can cause even more serious problems. The most critical question is as old as Web development: How do you handle the Back button?


New answers to old questions

It may shock you to learn that you can do Web development and get a stateless experience for the user with a stateful model for the programmer. In fact, in Hackers and Painters (see Resources), Paul Graham talks about using the underlying approach as early as 1995 at ViaWeb. The approach uses a programming control structure called the continuation.

The basic idea is this: You can let your programming framework load your application's state before a request and save your application's state after each request. I'll start with a look at a continuation in the Ruby programming language.

A Ruby example

If you want to code along, install Ruby and then type irb. Define a method called loop by typing the commands after the > character, as shown in Listing 1:


Listing 1. Creating the loop method
irb(main):001:0> def loop(interrupt)
irb(main):002:1> for i in 1..10
irb(main):003:2> puts "Value of i: #{i}"
irb(main):004:2> callcc {|c| return c} if i == interrupt
irb(main):005:2> end
irb(main):006:1> end
=> nil

The loop method takes a single parameter called interrupt. You start a for loop from 1 to i, print the value of i, and then ... whoa. The cryptic callcc statement means call with continuation. Think of a continuation as the state of a program at a frozen point in time. Ruby calls the code block in braces, passing in a continuation object. The code in braces is a closure and is simply a block of code that's passed to callcc. The end result is that callcc captures the state of execution and stores the result in c. You can now call this method and interrupt execution at any point of the loop, capturing the state of the program. At a later point, you can resume.

Now execute the method a couple of times, as shown in Listing 2:


Listing 2. Executing the loop method
irb(main):007:0> cont = loop 5
Value of i: 1
Value of i: 2
Value of i: 3
Value of i: 4
Value of i: 5
=> #<Continuation:0x2b5a358>
irb(main):008:0> cont.call
Value of i: 6
Value of i: 7
Value of i: 8
Value of i: 9
Value of i: 10
=> 1  10
irb(main):009:0> cont = loop 8
Value of i: 1
Value of i: 2
Value of i: 3
Value of i: 4
Value of i: 5
Value of i: 6
Value of i: 7
Value of i: 8
=> #<Continuation:0x2b562f0>
irb(main):010:0> cont.call
Value of i: 9
Value of i: 10

Each time you implement the call, the continuation picks up where execution left off. So a Web development framework that uses continuations can capture a continuation after processing each request and store it in the session with an identifier. The framework can then restore the continuation from the session before each new request, using the same approach that you used to store user data.

Pros and cons

The continuation-server approach in many ways lets you have your cake and eat it too -- a stateful programming model and a user experience with stateless performance. Drilling down, these are the approach's advantages:

  • It assures statelessness between requests. The framework can identify individual continuations within a URL and store that continuation in the session.

  • It gives you a stateful programming model. The framework can restore any continuation at any time. If the user submits a form a second time, the continuation can pick up processing at an earlier point in time.

  • You can invalidate continuations based on business rules, so it's easy to prevent a form from being submitted twice.

  • You get Back button support thrown in for free. Because you literally have the execution state at any point in time, the framework can simply restore the appropriate continuation if the user ever hits the Back button.

  • Threading becomes easy because no resources are shared. No resource contention means most threading problems go away.

Continuations dramatically simplify the Web development model. With continuations, you can think of a Web application as an application with a series of requests to provide more user information. Figure 2 shows the revised application flow. After the user starts an application, the Web server is in control. Instead of fielding arbitrary requests in an arbitrary order, an application becomes a unified, directed conversation with a user.


Figure 2. Continuations allow a more natural application flow
figure

Like many higher-order abstractions, continuations have drawbacks. Those disadvantages will transfer to the overall approach that depends on them. Continuation servers must solve these common problems:

  • Continuations can be expensive. Making it easier to put data into a session means more people will put more data into a session. A framework designer can lessen the expense by using an approach called partial continuations, where the framework saves only the application-specific portion of the continuation.

  • Ugly URLs. Most continuation servers append an ugly identifier to a continuation.

  • A changing user paradigm. If you use continuation servers pervasively, the user experience will change. The Back button will actually work, and that experience will disorient some users. For example, if you click on the Back button after placing something into your shopping cart, would you expect the application to take something out of your cart?

Overall, I believe that continuations represent a significant step forward, and you're likely to see pervasive implementations of continuation servers in the near future. Now let's take a quick look at some implementations in other languages, and then I'll show you what's going on closer to home.

Implementations in other languages

It turns out that several languages have continuations-based approaches (see Resources for links to more information about all of them). The most common are in Lisp, Ruby, and -- most of all -- Smalltalk.

  • Seaside, the most popular continuation server, is implemented in Smalltalk by Avi Bryant. If you want to understand a radically different and intensely productive Web development framework, get to know Seaside. The experience will change the way that you approach Java programming.

  • The Iowa framework in Ruby was also created by Avi Bryant, with Web Objects as an inspiration. It's now used and maintained in Ruby, with a different supporting cast.

  • The Wee framework is another Ruby framework that has many of the characteristics of a continuation server but doesn't use native language continuations.

  • ViaWeb used continuations with Lisp to get a productivity edge over its competition in 1995.

  • Continuity is a Perl-based continuation server. Perl 6 will support continuations natively.

There are a few others I haven't listed here. Right now, you'd expect most of the implementations of continuation servers to be in languages that support continuations, and these are not mainstream languages. But that's not a fully accurate picture of what's happening.


Continuation servers in the Java language

Java developers are increasingly seeing the power of the continuation-based approach to building Web frameworks, but the Java language does not support native continuations. If your language doesn't support continuations, you have a limited number of options: simulate continuations, add continuations to your existing language, or pick a new language. Different Java frameworks (see Resources) use each one of these approaches:

  • Simulate continuations. Some Java frameworks use alternative methods to capture the state of execution. Lakeshore uses threads, and Spring Web Flow uses a state machine. (You'll see more Web Flow later in this article.)

  • Pick a different language. The Java virtual machine has alternate languages that can sit on top. Cocoon 2 takes this approach using Rhino, a virtual machine that supports JavaScript, but also Java.

  • Implement continuations. The Jetty 6 servlet container, RIFE, and WebWork use this approach.

Simulating native continuations

You don't have to use continuations to capture execution state. Suspended threads and state machines can both capture execution state nicely. The threading approach simply freezes and stores an existing thread. This approach is a little more limited, because it does not give you stateless performance -- you are in fact using a separate thread per user.

But the state machine is a nice approach to continuations. A state machine is a program with well-defined transitions from state to state. By far, the most popular Java framework with a heavy continuation-server influence is Spring's Web Flow. Web Flow models the navigation between the pages of a user interface as a state machine. Web Flow can model flows as Java objects, but usually you'll use XML to describe a flow. Consider the flow representation in Listing 3 from the Web Flow tutorial (see Resources):


Listing 3. Web Flow flow representation

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE webflow PUBLIC "-//SPRING//DTD WEBFLOW//EN"
        "http://www.springframework.org/dtd/spring-webflow.dtd">

<webflow id="myFlow" start-state="displayForm">

    <view-state id="displayForm" view="form">
        <entry>
            <action bean="myFlowAction" method="setupForm"/>
        </entry>
        <transition on="submit" to="processSubmit">
            <action bean="myFlowAction" method="bindAndValidate"/>
        </transition>
    </view-state>

    <action-state id="processSubmit">
        <action bean="myFlowAction"/>
        <transition on="success" to="finish"/>
    </action-state>
        
    <end-state id="finish" view="success"/>

</webflow>

This flow has three core states: displayForm, processSubmit, and finish. The flow defines actions that will make the machine transition from one state to the next. For example, a submit will transition the state from displayForm to processSubmit. Web Flow works above the typical model-view-controller level. You can use many different view technologies to display your forms.

As you transition from one state to the next, the framework captures the current state, with all instance variables, automatically. You get the same Back button support and stateful programming model that other continuation servers provide. This approach does not use native continuations, but you get many of the advantages of continuation servers, and some other advantages as well:

  • Persistence. Although you can't capture the whole call stack, you can capture the current execution state and even persist the current state.

  • Long-lived flows such as workflow. Because the state isn't dependent on a given call stack, the approach is more stable for things like long-duration workflows.

  • Tools. It's relatively easy to build tools for XML.

Alternative languages

Cocoon uses Rhino, the JavaScript virtual machine that plugs into the JVM. Cocoon controllers use a modified version of Rhino to express continuations, so Cocoon lets you express your controller logic in Java or JavaScript. Consider the application in Listing 4, from the Cocoon tutorial (see Resources):


Listing 4. Using Cocoon
  try {
    if (operator == "plus")
      cocoon.sendPage("result.html", {result: a + b});
    else if (operator == "minus")
      cocoon.sendPage("result.html", {result: a - b});
    else if (operator == "multiply")
      cocoon.sendPage("result.html", {result: a * b});
    else if (operator == "divide")
      cocoon.sendPage("result.html", {result: a / b});
    else
      cocoon.sendPage("invalidOperator.html", {operator: operator});
  }
  catch (e) {
    cocoon.sendPage("error.html", 
      {message: "Operation failed: " + e.toString()});
  }

Notice the sendPage method in Listing 4. This method sends a page back to the user. You don't see any of the normal tedious code to support the Back button, or to save user data to the session -- that's all encapsulated into Cocoon's framework.

Implementing continuations

The RIFE framework implements its own continuations, and the WebWork framework uses the RIFE continuations implementation. Jetty 6 also includes a continuation implementation in Java. Listing 6 shows an example from RIFE's tutorial, to play a guess-a-number game:


Listing 5. A RIFE example
while (mGuess != answer) {
  print(template);
  pause();
  guesses++;

  if (answer < mGuess) {
    template.setBlock("indication", "lower");
  } else if (answer > mGuess).{
    template.setBlock("indication", "higher");
  }
}

In this example, the method called pause() captures the continuation and sends the template back to the user for action. RIFE makes continuations simple and accessible to the everyday Web developer.


The near future

You can see that continuations represent a real advancement in Web development frameworks. You can be much more productive with this approach. And because you express your Web application as straightforward Java code instead of as hundreds of disconnected servlets, your applications are much easier to read and maintain.

New advances in Web development are rapidly making the continuations approach much more important. Rather than fetching whole Web pages with the traditional request/response models, Ajax applications can asynchronously fetch a small part of a Web page and weave the results into an existing page. But Ajax applications tend to force an application to maintain a connection to a user for long periods of time to keep applications responsive and keep state tracking easy to code. That practice defeats the purpose of stateless programming because you do need to hold resources for each connected user. With continuations, you can keep the state in a continuation and restore the state on demand.

In the near future, hardware improvements will make the added resource consumption of continuations less critical. Without a major overhaul, Web development frameworks will still be too complicated. Ajax threatens to complicate Web development even more. These factors are all converging to drive the acceptance of continuation servers. In two years, most new Web development will use some continuation server or some simulation of continuations.

Next time, I'll talk about domain-specific languages and their role in Ruby on Rails. I'll then bring the ideas home and show you what's happening for domain-specific languages in Java programming.


Resources

Learn

Get products and technologies

  • RIFE: RIFE is an innovative framework that uses continuations and many other techniques popular in dynamic languages.

  • Spring's Web Flow: Spring Web Flow is a generic flow engine focused on the definition and execution of page flow within a Web application.

  • Lakeshore: Lakeshore is a Java-based Web component framework heavily influenced by Seaside 2 and the Borges Ruby framework.

  • Jetty 6: Jetty is a servlet container in Java with continuations support.

  • Seaside: Seaside is the most popular and influential continuation server.

  • Wee: Wee is another Ruby continuations framework.

  • Continuity: Continuity is a continuation-based Web-programming framework for Perl.

Discuss

About the author

Bruce Tate

Bruce Tate is a father, mountain biker, and kayaker in Austin, Texas. He's the author of three best-selling Java books, including the Jolt winner Better, Faster, Lighter Java. He recently released Spring: A Developer's Notebook. He spent 13 years at IBM and is now the founder of the J2Life, LLC, consultancy, where he specializes in lightweight development strategies and architectures based on Java technology and Ruby.

Comments



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology, Open source, Web development
ArticleID=106261
ArticleTitle=Crossing borders: Continuations, Web development, and Java programming
publish-date=03212006
author1-email=bruce.tate@j2life.com
author1-email-cc=bruce.tate@j2life.com

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Rate a product. Write a review.

Special offers