In this interview with Artima, RIFE project creator Geert Bevin discusses new features in the recently released 1.6 version of RIFE.
The RIFE project released the 1.6 version of this increasingly popular Web application framework. In this interview with Artima, RIFE founder Geert Bevin describes the most significant new features in RIFE 1.6:
Frank Sommers: Can you tell us what's new in RIFE 1.6?
Geert Bevin: In RIFE 1.6, we did a lot of work to integrate with Terracotta. Using Terracotta with RIFE is now completely transparent, especially when you use RIFE's continuations.
You can develop an application with RIFE and its continuation framework for state management, and once you notice that you need to cluster that application out to several nodes, the only thing you need to do is include the Terracotta module. Once you've done that, you're up and running with your application in a distributed, clustered environment.
Frank Sommers: Let's step back a bit. What are continuations?
Geert Bevin: A typical use-case for continuations is the check-out process, a multi-step, transactional, online conversation you have with the application. You fill out several forms across multiple pages, and you have to be able to step back and forward between them. You often want to allow people to fill in data partially, press a button on the form to step back, and all the state still to be maintained from the second step.
If you coded that up in a regular server-side framework, you'd either have to extract this to some kind of flow management, as with Spring Webflow, or you'd have to do the state management yourself by stuffing data into the session or putting things in hidden parameters, and figure out what to do with the back button, and so on.
Continuations allow you to express that with Java code. It's as if you were writing a single-user application on the command line: You ask the user for more input, and then you either continue, or step back to the previous state. To be able to do this, you call a pause() method, which you can call anywhere in your Java code—in the middle of a while loop, inside if statements, and inside method calls.
The advantage is that Java becomes your control flow language. Your local variable scope becomes the state of your conversation. Each step that corresponds to a pause() method call, is a continuation. Those variables will be captured, and they will be stored away with the continuation as references to live data. If you start a new continuation, you can configure the system so that the old continuation either clones the references, or keeps the same references. It's up to you how you want to isolate your data. Continuations keep that data in live memory. And that's where Terracotta comes in.
The fact that continuations keep state in live memory is a major issue for a lot of people looking at continuations as a solution. What if you wanted to persist that state, or transfer that state to another node in a cluster?
In RIFE, we never imposed a serializability requirement on each element or object that may become part of a continuation's state. There were several reasons for that. The main one was that it would have put a lot of burden on the developer to get that done correctly. As your data structures evolve over time, it's easy to forget something and violate serializability of the complete object tree.
That serializability requirement is a moot point now with Terracotta. You can bring down all your nodes entirely, have the Terracotta server store your continuation state on disk, bring up a whole set of new servers, point them to the same Terracotta server, and you're up and running. Or you can add and more nodes, as you see fit.
Terracotta will also byte-code enhance your Java source codes, and add the required functionality to use all the semantics of the Java memory model—how you synchronize across shared state, how you wait and lock on some state—and it will distribute that onto multiple nodes. When you modify parts of some shared state, Terracotta will automatically detect what changed—say one particular element of an array. Other nodes will then pull in that data only when they need it. It's very optimized.
When you combine continuations with Terracotta, you use Java as the driving force for your programming, and use continuations as something that works behind the scenes to do things like control flow management with conversational state.
In RIFE 1.6, we completely isolated the continuations functionality. We extracted it from Web behavior, and now it's a JAR file on its own that contains only what is needed to apply that to other domains. We are considering submitting continuations as a JCP JSR at some point, and want to make sure that we can validate its usefulness across a variety of problem domains.
One of the areas many people are interested in is workflow. I wrote a prototype workflow system application based on continuations that is also clusterable through Terracotta. You can write your workflow tasks natively in Java, and indicate when you're waiting for certain events. At that point, your thread actually stops running, and it becomes just data sitting in memory, which is the continuation. When events related to that data occur, the continuation will be resumed, and a new thread will be started for the continuation. If you have a lot of tasks going on, this gives you a scalable solution because they will never wait and take up thread resources if they are not actually doing anything.
Frank Sommers: You mentioned JRuby support in the RIFE 1.6 release notes. In what way do you support JRuby?
Geert Bevin: JRuby is supported for writing RIFE elements. Elements is a term we use for a concept that other frameworks call actions or components. Elements are both actions and components.
RIFE's approach to server-side development centers around being aware of URLs and, by being aware that parameters are sent either through a form or a query string to whatever on the server-side will handle requests. With elements, if you have some piece of server-side functionality, you can embed other pieces of server-side functionality that receive the parameters from the elements around it. The nested components don't care where they get that piece of information from. They will just be invoked through something, and send out data through either the HTTP response or other elements.
This nestable behavior is why we call our actions elements. You can start out by coding in a way that one element corresponds to one page on your Web site. As you detect that you've got functionality that can be re-used, you can use those elements inside other pages, and they can become components. You can even embed entire applications inside other applications. We have, for instance, a forum application that you can embed as some kind of component inside other components.
The implementation of these elements can be in any language. RIFE really cares about the shell around those elements, so to speak—that elements work through certain parameters and that data can flow out of elements. When needed, RIFE will call the implementation of an element. If that element is implemented in Java or JRuby or Groovy, or any of the other scripting languages we support, RIFE doesn't care. That allows you to build Web sites out of an aggregation of all kinds of scripting languages, and Java.
Frank Sommers: What new features do you envision for the next version of RIFE?
Geert Bevin: RIFE is a full-stack framework, meaning that you can create entire Web applications using this one framework: we have a database layer, templating, life-cycle management. These neatly work together so you get very productive while coding. RIFE also has support of out-of-container testing: You can easily write tests on the code that executes and make meaningful assertions on that code.
However, the real challenge testing a Web application is writing meaningful tests for the execution of a use-case: the user clicks somewhere, and then needs to log in, and then visits some other pages. Such interactions are difficult to simulate and to come up with beforehand. And it's even hard to debug: A user may send you an email, saying that they can't do this or that. But how do you track back to what actually happened? How do you replicate that and include that in your test suite?
Since we have a full-stack framework, I'd like to integrate some kind of messaging support for the entire stack. A JMS message would be sent out for each stateful operation inside RIFE. Each operation that either communicates with the database, or changes something that goes out to the user, calls a new element, and things like that, would send out a JMS message. The messages could be consumed by, for example, a test-case builder. Since that builder would know that the messages come from RIFE, it could write out the statements that are needed to simulate what that particular user did. That would help testing an entire Web application.