I really wanted to go to Putting the Fun in Functional: Applying Game Mechanics to Social Software by Amy Jo Kim, but my inner geek won out and I went to Applied Web Heresies with Avi Bryant (slides). I hope someone else took good notes.
The basis for the talk is Seaside, a web framework for Smalltalk that Avi wrote several years ago. The problem with Seaside is you're not going to use it! There are a lot of interesting ideas in Seaside that people should know, so this tutorial is way of spreading the ideas outside of Smalltalk.
Avi suggests using Seaside as a recipe. He tells the story of Primo Levy and onions in the varnish. There are a lot of "best practices" of Web development that were good decisions at the time but which are no longer needed.
Here's the basic requirement list:
- OOP
- Servlet-style app server
- Blocks and closures
"First thing we do, let's kill all the templates." Templates were a good idea that have become useless and harmful. They're constraining or they're a bad programming language. The is a belief that templates are useful for model/view separation. But HTML is now a semantic layer and CSS is the real view layer (see Zen Garden). This is an interesting point of view and one I have a hard time arguing with.
So, you need a rich library for generating HTML (see AWT for inspiration). Each widget has a render method that gets a canvas passed to it.
The first thing we did was write a small framework to get things going. Different groups were working in different languages. I used Ruby (even though I don't really know Ruby). Here's mine:
require 'webrick'
require 'stringio'
server = WEBrick::HTTPServer.new(:Port => 2000)
server.mount_proc("/heresy"){|req, res| Application.new.handle(req, res)}
server.mount_proc("/favicon.ico"){|req,res| res.status = 404}
class Application
def handle(req, res)
canvas = Canvas.new(res)
render_on(canvas)
end
def render_on(html)
html.heading("Hello World")
end
end
class Canvas
def initialize(res)
@res = res
res['Content-Type'] = 'text/html'
end
def heading(str, level=1)
@res.body = "<h1>"+ str + "</h1>"
end
end
trap("INT"){ server.shutdown }
server.start
We're omitting tag objects here and just spitting out HTML, but a real API should do that.
The next heresy that Avi proposes is that sessions are two valuable to persist. In general, you can never marshall and unmarshall the stuff in memory reliably. All the good stuff's in memcached anyway. Keep the session in the memory of the application server
What about load balancing? Use sticky sessions. YAGNI Application servers going down is unusual. Users losing session data is a minor annoyance. Live with it.
NeXT's WebObjects is the inspiration for much of what Avi's done.
Next we move from our HelloWorld application to something that has stateful sessions. We're going to build a registry of sessions and push the canvas down a level so that the sessions hold the canvas. My Ruby coding skills were not up to keeping up, so I cheated and grabbed Avi's code (which includes a much more usable Canvas object):
require 'webrick'
require 'stringio'
server = WEBrick::HTTPServer.new(:Port => 2000)
server.mount_proc("/heresy"){|req, res| Application.new.handle(req, res)}
server.mount_proc("/favicon.ico"){|req,res| res.status = 404}
class Registry
def initialize
@items = []
end
def register(item)
@items << item
(@items.size - 1).to_s
end
def find(key)
@items[key.to_i]
end
end
class Application
@@sessions = Registry.new
def handle(req, res)
session_cookie = req.cookies.detect{|c| c.name == "heresy"}
if(session_cookie)
session = @@sessions.find(session_cookie.value)
end
unless session
session = Session.new
res.cookies << WEBrick::Cookie.new("heresy", @@sessions.register(session))
end
session.handle(req, res)
end
end
class Canvas
def initialize
@io = StringIO.new
end
def tag(name, attrs={}, &proc)
@io << "<"
@io << name
attrs.each{|k,v| @io << " #{k}='#{v}'"}
@io << ">"
proc.call
@io << "</#{name}>"
end
def text(str)
@io << str
end
def heading(txt, level=1)
tag("h#{level}"){text(txt)}
end
def string
@io.string
end
end
class Session
def initialize
@count = 0
end
def handle(req, res)
@count += 1
html = Canvas.new
render_on(html)
res.body = html.string
res["Content-Type"] = "text/html"
end
def render_on(html)
html.heading("Hello World: #{@count}")
end
end
trap("INT"){ server.shutdown }
server.start
What's left to do on session? Lots, including:
- Unguessable session keys
- Session keys as query params
- Session locks
- Expiration from registry
The next piece of heresy: meaningful URLs don't carry enough meaning. People put a lot of energy trying to create names that describe the particular point in an application. Not every page in an application is a meaningful part of an API. URLs, particularly query parameters are classic place where people repeat themselves. Don't repeat yourself. Lots of meaningless names create namespace collisions.
Avi proposes using a registry to store IDs against page names. The inspiration for this is TCL/TK: Register closures/blocks as callback objects. Here's the code that implements this refinement (I did it almost all by myself--I had to peak to see how WeBrick handled requests):
require 'webrick'
require 'stringio'
server = WEBrick::HTTPServer.new(:Port => 2000)
server.mount_proc("/heresy"){|req, res| Application.new.handle(req, res)}
server.mount_proc("/favicon.ico"){|req,res| res.status = 404}
class Registry
def initialize
@items = []
end
def register(item)
@items << item
(@items.size - 1).to_s
end
def find(key)
@items[key.to_i]
end
end
class Application
@@sessions = Registry.new
def handle(req, res)
session_cookie = req.cookies.detect{|c| c.name == "heresy"}
if(session_cookie)
session = @@sessions.find(session_cookie.value)
end
unless session
session = Session.new
res.cookies << WEBrick::Cookie.new("heresy", @@sessions.register(session))
end
session.handle(req, res)
end
end
class Canvas
def initialize(cbs)
@io = StringIO.new
@callbacks = cbs
end
def tag(name, attrs={}, &proc)
@io << "<"
@io << name
attrs.each{|k,v| @io << " #{k}='#{v}'"}
@io << ">"
proc.call
@io << "</#{name}>"
end
def text(str)
@io << str
end
def link(name, &proc)
id = @callbacks.register(proc)
tag("a",{ :href=>"?#{id}"}){text(name)}
end
def space
@io << " "
end
def heading(txt, level=1)
tag("h#{level}"){text(txt)}
end
def string
@io.string
end
end
class Counter
def initialize
@count = 0
end
def render_on(html)
html.heading("Hello World: #{@count}")
html.tag("p"){html.text("this is fun!!!")}
html.link("--"){@count -= 1}
html.space
html.link("++"){@count += 1}
end
end
class Session
def initialize
@callbacks = Registry.new
@root = Counter.new
end
def handle(req, res)
req.query.each do |k,v|
if callback = @callbacks.find(k)
callback.call(v)
end
end
html = Canvas.new(@callbacks)
@root.render_on(html)
res.body = html.string
res["Content-Type"] = "text/html"
end
end
trap("INT"){ server.shutdown }
server.start
As we finished this segment, I said "I get what we're doing, but why are we doing it?" In other words, why go to all this trouble to create a registry of callback methods and URLs that point to it uniquely? The answer is in the next little exercise. Here's the code I produced:
require 'webrick'
require 'stringio'
server = WEBrick::HTTPServer.new(:Port => 2000)
server.mount_proc("/heresy"){|req, res| Application.new.handle(req, res)}
server.mount_proc("/favicon.ico"){|req,res| res.status = 404}
class Registry
def initialize
@items = []
end
def register(item)
@items << item
(@items.size - 1).to_s
end
def find(key)
@items[key.to_i]
end
end
class Application
@@sessions = Registry.new
def handle(req, res)
session_cookie = req.cookies.detect{|c| c.name == "heresy"}
if(session_cookie)
session = @@sessions.find(session_cookie.value)
end
unless session
session = Session.new
res.cookies << WEBrick::Cookie.new("heresy", @@sessions.register(session))
end
session.handle(req, res)
end
end
class Canvas
def initialize(cbs)
@io = StringIO.new
@callbacks = cbs
end
def tag(name, attrs={}, &proc)
@io << "<"
@io << name
attrs.each{|k,v| @io << " #{k}='#{v}'"}
@io << ">"
proc.call
@io << "</#{name}>"
end
def text(str)
@io << str
end
def link(name, &proc)
id = @callbacks.register(proc)
tag("a",{ :href=>"?#{id}"}){text(name)}
end
def space
@io << " "
end
def heading(txt, level=1)
tag("h#{level}"){text(txt)}
end
def string
@io.string
end
end
class MultiCounter
def initialize
@counters = [Counter.new, Counter.new, Counter.new]
end
def render_on(html)
@counters.each{|ea| ea.render_on(html)}
end
end
class Counter
def initialize
@count = 0
end
def render_on(html)
html.heading("Hello World: #{@count}")
html.tag("p"){html.text("this is fun!!!")}
html.link("--"){@count -= 1}
html.space
html.link("++"){@count += 1}
end
end
class Session
def initialize
@callbacks = Registry.new
@root = MultiCounter.new
end
def handle(req, res)
req.query.each do |k,v|
if callback = @callbacks.find(k)
callback.call(v)
end
end
html = Canvas.new(@callbacks)
@root.render_on(html)
res.body = html.string
res["Content-Type"] = "text/html"
end
end
trap("INT"){ server.shutdown }
server.start
The only changes here are the addition of a MultiCounter class that creates a three item array of counter objects and a render_on object that calls render_on on each member of the array. Then, instead of putting a Counter object at the root, I put a MultiCount object. And you get three of the Counter widgets on the same page. This shows how easy it is to use the objects as Web widgets. Avi says you can't do this with named URLs.
Avi remarks that pages are a lousy unit of reuse and partials ain't much better. "But every piece of my application is a beautiful and unique snowflake." Again, with the CSS.
What aren't we doing?
- Splitting callback registries up by page view
- Tracking the back-button
- Redirecting after side-effects
- Forms!
Overall this is a very interesting set of thoughts about Web development. Clearly he's espousing a lot of new ideas, which generated a lot of "How do you ...?" questions. Very good stuff. This tutorial made me think, which is the best kind.


