The first keynote - Avi Bryant on Seaside and redefining web development - using the strengths of Smalltalk.
First thing to address - why bother? Web apps are really clumsy things - they aren't easy to write, and they aren't easy for people to use (limited widget sets, HTTP for interaction, etc). Oh god - I've been quoted:
I'm more of the mind that HTML based app suck
James Robertson
Applets haven't worked out - users had already figured out the "mental model" of the web - website feel, hrefs, back buttons. So while Java applets have mostly failed, Java has succeeded wildly on the server side. Meaning - server apps are a "second chance". Paul Graham and his web store (sold to Yahoo) is a great example of that. Why did he use Lisp? "Because he could" - on the server, no one knows if you're a Lisp app. The image based nature of this made it possible to do things that simply cannot be done in (Java, .NET, etc).
So why should we, as the Smalltalk community - care about web development? Because there's an "unfair" advantage on the client side. On the web, none of the issues (native widgets, platform integration, etc) matter. Instead, you can leverage Smalltalk's strengths directly. Writing web apps is hard - Smalltalk makes it easier.
To start - short history of web apps:
A collection of functions that take HTTP requests as input and produce HTTP responses as output
This is a lot like shell commands and pipes, actually. The basic problem is that HTTP (and thus web apps) are stateless. This works fine for simple applications. Any moderately complex application does, in fact , have state. Simple E-Commerce example - shopping cart, get billing/payment info, verify with the user, process, ship. Lots of state here. How dooes that state stick around? It gets shuttled back and forth from client to server (hidden fields, cookies, url arguments, whatever). A lot of code gets written to deal with this state passing issue. It's hard to generalize this too - it varies too much by application and domain.
So now we get into session management - when you first start an application in the browser, you start a session. When you quit (or timeout), you leave the session. You need to pass the session id back and forth so that the server knows which session you are coming from. So why is this a problem? You pass new data back to the server, and the id - and the server manages the rest.
Why global session id is evil
You start with (Expedia, say) - and spawn a new window to look at options, use the back button, etc. You stay in the same session. You have to be very careful to go back to the right place or you get the wrong result. I can vouch for that - many, many server apps don't deal with the back button at all well. WebObjects did this a little differently - it gave out instance based id's (not global) - this ends the confusion as to "where am I?" What's the problem here? Coupling. This still gives you rigidity in the ordering. It's just a repeat of the mainframe green screen issue - you get forms in the order you get them, and changing the order requires a massive change to the entire system.
Seaside is to most other Smalltalk web toolkits as Smalltalk is to most other OO languages; it's as simple as that.
Cees de Groot
The thing that gets hairy here is conditional flow. How do we move from one part of the application to another outside the standard procedural flow of a web app? One possibility is a set of blocks, one for each possible step. We just ask each block to evaluate itself as appropriate.
checkoutProcess
^ShippingAddress new next:
[:ship |
BillingAddress new next:
[:bill |
paymentInfo nnew next:
: [:bill |
ConfirmationPage new
shippingAddress: ship;
billingAddress: bill;
paymentInfo: pay;
yourself]]]
This is sort of continuation style - you pass blocks in, and changing the order can be done in one place, and one place only. Still too rigid.
checkoutProcess
|pay ship bill |
pay := self call: PaymentInfo new.
ship := self call: ShippingAddress new.
bill := self call: BillingAddress new.
self call:
(ConfirmationPage new
shippingAddress: ship;
billingAddress: nill;
paymentinfo: pay).
The idea? What if execution were "frozen" at each step? This is much easier to write, and makes the execution order much easier to see (and change). In a sense, what we've done is return to a "line oriented" screen application that "waits for input" - which is how end users expect to interact with web browsers. The user can use the back button and rewind the application (as you can in a Smalltalk debugger). This really extends the power of Smalltalk from the developer to the end user in a way that allows for natural interaction by the end user. The context stack is being copied and preserved on each submission. Based on some questions, Avi demonstrated following both possible paths down a web app by cloning a browser page and executing both paths. Now, you can protect against that in cases where it's necessary - there's a way to isolate calls - you'll get redirected to the right place.
self isolate: [self call: PaymentInfo new]
Question: What about the cost? The contexts that are being kept around are expoired (configurable) - generally, only the last N are kept around. The bottom line is, Seaside is memory intensive and trades memory for performance and ease of use - not to mention time to deliver.
Question: What about session identity in a multi-server environment? that's already an issue, and is typically handled by pinning a user to a given server (this is how most load balancers deal with this).
Question: Temporary vars get saved on the stack - what about other stuff? There's a general mechanism for objects that need to be backtracked (a shallow registry of objects that need to be rolled back). You can customize this to handle whatever needs to be held for rolling back. So you want to roll back UI state, generally not domain state
Question: Any issues with multiple sessions trampling on state? That's an application issue. You still need to be aware that you are writing a server application...
So Avi shows us the implementation of a Continuation. In Smalltalk, a Continuation is 10 or so lines of code. It's pretty simple code - you serialize the stack and save it off. Invoking a continuation is a matter of terminating the current context and then restoring the one in question - and then swap the sender out. Question - the context termination will not call the #ensure blocks. This is a thing you need to deal with - make sure that an #ensure does not go across multiple continuations. The interaction of #ensure: and Continuations is not a solved problem. Lisp has exactly the same issues.
Question: could you use a "prevlayer" type approach to serializing stuff out for failover? Yes, but you need to be careful - continuations contain compiled methods, etc. Could be done, but could be complex.
General laugh - with 15 minutes remaining, Avi is moving on to the second half of the presentation. Many good questions in this session. So now on to rendering HTML.
Web heresies - the ID (opaque) in the URL. Another one here - generating the HTML with code (however - the Naked Objects movement wants the same thing). I suspect that the problem is simpler on the web. There's an answer that works better - you have the framework generate simple HTML, and then have CSS render it in a nicer way - this still achieves a separation of presentation and domain. So you do have templates - CSS
Where has this been ported?
- Squeak - main development
- VW (public Store)
- Dolphin - not recent, but possible
- VAST and Smalltalk/X - not possible, as you cannot copy the stack.
Doc - attend the tutorial :) There is an active mailing list. There's also Avi's blog