Seaside Statefulness: Call-and-Answer
So Avi Bryant spoke at CUSEC this last week and I was fortunate enough to sit in on a Seaside tutorial that he gave to a couple of people. It got me really interested in this Smalltalk web framework stuff. Smalltalk is of course nothing new except for the fact that it was the first language to have some of the awesome code features we use today, but that’s all in the past, so let’s move on.
The interesting part is that playing with Seaside is completely unlike learning Smalltalk (unless you’ve never seen a language with lambdas). It’s a complete mind-blowing experience on its own level; a revolutionary way to look at web application development, not "let’s do what Ruby on Rails does.. in Smalltalk!". The revolution is in the idea that the statelessness of HTTP need not be transitive. It’s the idea that you shouldn’t have to reconstruct bits and pieces of many web requests and figure out what your user just did. In short, it brings back the workflow based semantics that make desktop app development so much more intuitive by comparison. In shorter: it’s the future. Smalltalk might not be, but these concepts definitely are.
State: Big Deal?
There’s something really sexy about being able to do the following:
go | user | user := self call: AuthRegisterComponent new. user username ~= '' ifTrue: [ self inform: ('Good Job ', user username) ] ifFalse: [ self inform: 'You did not register' ]
The code above starts from class AuthRegisterTask, throws you off to a register screen (AuthRegisterComponent) and then returns a new AuthUser object back to the task. The task then validates and sends you off to another inform: page (helper page with some text and a simple "Ok" button that brings you back to the previous page when clicked) with the success/failure results. A total of 4 web requests are happening here from start to finish, but by looking at the code (assuming haven’t seen Seaside before) you would never know it. In fact, it looks like you just created an AuthRegisterComponent object and verified the results— the implementation details of how those results are retrieved are properly hidden from you. They’re hidden because they’re ugly.
AuthRegisterComponent is implemented as:
renderContentOn: html | user | user := AuthUser new. html form: [ html paragraph with: [ html span with: 'Username'. html textInput on: #username of: user. ]. html paragraph with: [ html span with: 'Password'. html textInput on: #password of: user. ]. html submitButton callback: [ self answer: user ]. ].
The magic here is in the call: and answer: methods. This is where Seaside communicates (continuation style) between requests by saving and restoring state like a conventional application would.
Seaside: Pages are not Islands
Of course the example I used wasn’t perfect, but it’s the shortest illustration I can come up with right now. A much more appropriate (and much longer) example would be a 3+ page wizard style registration where the user might follow one of many possible flow graphs to complete the form. Seaside would handle this with ease because it does not treat pages as islands. In fact, such a wizard would be as easy as implementing one in Swing, or using some Neatbeans auto-wizardifier because the logic and implementation would be equivalent. This is not the case when dealing with the web. You usually have to hack your session and throw tons of state in there to emulate what continuations do in a very clean fashion.
AJAX WAT? AJAX Where Art Thou.
Moral of the story: you should learn continuations because they will blow your mind.