The Workerless Pattern
Who am I?
- Drew Olson
- Developer at Braintree
- github.com/drewolson
What am I talking about?
- The Worker Pattern
- The Workerless Pattern
- Code Samples
The Worker Pattern
The Worker Pattern
- Ryan Smith, heroku
- 2011 Railsconf Talk
- http://ryandotsmith.heroku.com/2011/04/the-worker-pattern.html
The Worker Pattern Concepts
- Load a simple skeleton
- Javascript requests page fragments
- Workers generate fragements in background
- Poll from client JS, waiting for fragments
Example
The Worker Pattern Example
The Worker Pattern Example
The Worker Pattern Example
The Worker Pattern Example
The Worker Pattern Example
The Worker Pattern Example
The Worker Pattern Example
The Worker Pattern Example
The Worker Pattern Example
The Good Stuff
- Concurrent
- Cached
- Separation of concerns
The Not-so-good Stuff
- Infrastructural complexity
- Client-side polling
The Workerless Pattern
The Workerless Pattern Concepts
- Load a simple skeleton
- Javascript requests page fragments
- Evented server generates fragments
- Client connections held open, no polling
Example
The Workerless Pattern Example
The Workerless Pattern Example
The Workerless Pattern Example
The Workerless Pattern Example
The Workerless Pattern Example
The Workerless Pattern Example
The Workerless Pattern Example
The Workerless Pattern Example
The Good Stuff
- Low infrastructural complexity
- Evented
- Cached
The Not-so-good Stuff
- Long-running requests may die
- Evented?
Code
Skeleton Page
<h1>Node Worker Patter</h1>
<div id="a">
<img src="/images/spinner.gif" />
</div>
<div id="b">
<img src="/images/spinner.gif" />
</div>
<div id="c">
<img src="/images/spinner.gif" />
</div>
<script type="text/coffeescript" src="/javascripts/client.coffee"></script>
Client-side Coffeescript
makeRequest = (url, callback) ->
$.ajax
url: url
success: (data, status, request) ->
callback(data)
error: (request, status) ->
console.log(status)
$ ->
makeRequest '/fragment/a', (content) ->
$('#a').replaceWith(content)
makeRequest '/fragment/b', (content) ->
$('#b').replaceWith(content)
makeRequest '/fragment/c', (content) ->
$('#c').replaceWith(content)
Server-side Coffeescript
express = require "express"
externalApi = require "./externalApi"
app = module.exports = express.createServer()
app.get "/", (req, res) ->
res.render "index", {}
app.get "/fragment/:fragmentName", (req, res) ->
query = req.params.fragmentName
externalApi.slowFind query, (err, result) ->
res.render "fragment",
fragmentId: result
fragmentContent: result
layout: false
app.listen 3000
console.log "Express server listening on port %d", app.address().port
External API
cacher = require './cacher'
performSlowExternalCall = (query, callback) ->
timeout = Math.floor(Math.random() * 3001)
setTimeout ->
callback("result for #{query}")
, timeout
slowFind = (query, callback) ->
cacher.performCached
query: query
hit: (value) ->
callback null, value
miss: (cacheResultCallback) ->
performSlowExternalCall query, (result) ->
cacheResultCallback(result)
exports.slowFind = slowFind
Cacher
EventEmitter = require('events').EventEmitter
redis = require "./redisClient"
client = redis.namespacedClient "worker"
class Cacher extends EventEmitter
constructor: (options) ->
@query = options.query
@on 'hit', options.hit
@on 'miss', options.miss
perform: ->
client.get @query, (err, result) =>
if result?
@emit 'hit', result
else
@emit 'miss', (result) =>
client.setWithExpiration @query, result, (err, setResult) =>
@emit 'hit', result
exports.Cacher = Cacher
exports.performCached = (options) ->
cacher = new Cacher options
cacher.perform()
Demo
More Info
- github.com/drewolson/node_worker_pattern
- fingernailsinoatmeal.com/post/5852481154/workerless-pattern
Q & A
/