<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-1660012043443342889</id><updated>2011-11-11T17:12:05.583-07:00</updated><category term='queues'/><category term='akka'/><category term='scala'/><category term='grub'/><category term='liftweb'/><category term='arrow keys'/><category term='vmware'/><title type='text'>The Rite of Coding</title><subtitle type='html'>Random thoughts on software development, networking and other topics</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://riteofcoding.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1660012043443342889/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://riteofcoding.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Derek</name><uri>http://www.blogger.com/profile/11374577951957547085</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/_I5O6EA8MygE/StINj_7JHTI/AAAAAAAAAL0/zIq8fxJWJSI/S220/25f0e94.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>10</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-1660012043443342889.post-2927237295284124457</id><published>2011-05-27T21:40:00.001-06:00</published><updated>2011-06-27T21:10:09.077-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='liftweb'/><category scheme='http://www.blogger.com/atom/ns#' term='akka'/><category scheme='http://www.blogger.com/atom/ns#' term='scala'/><title type='text'>Beyond Chat : Lift, Comet and Akka</title><content type='html'>Earlier this week Diego Medina put together a &lt;a href="http://fmpwizard-scala.posterous.com/54204619"&gt;really nice blog post&lt;/a&gt; about Comet in Lift, and that reminded me that I've been meaning to write this one.&lt;br /&gt;&lt;br /&gt;A few weeks ago, as part of the &lt;a href="http://www.meetup.com/frontier-developers/events/16903523/"&gt;Learn Scala in 2 Days&lt;/a&gt; workshop, I wrote a little demo of Lift as a frontend to the adventure game we were developing for the class project. At that point in the workshop we had already gone from using a simple text-input client to using Akka to decouple I/O from game logic, so adding in a CometActor was a no-brainer. In this post I'm going to go over that Akka interaction as well as detailing how I wrote the CometActor.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Game Overview&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;Before we get into the Lift side of things, I want to provide an overview of the game components that we're working with. Full source for the demo is available &lt;a href="https://github.com/samreid/scala-workshop/tree/master/derek-lift-demo"&gt;on GitHub&lt;/a&gt;, but I'll pull relevant snippets out.&lt;br /&gt;&lt;br /&gt;First, let's start with the interaction model. We have a single GameActor engine (Akka) that is responsible for receiving messages from the frontend and dispatching them. The very first message the frontend must send to the GameActor is a Join message:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush:scala"&gt;case class Join(name : String, input : ActorRef, display : ActorRef)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The join message contains three values: the &lt;span style="font-style:italic;"&gt;desired&lt;/span&gt; name of the player joining, as well as references to the frontend actors responsible for displaying and processing choices (input) and for displaying status messages. Once the GameActor has these references, &lt;span style="font-style:italic;"&gt;everything&lt;/span&gt; can be done asynchronously because at that point it's all just a system of actors. We do make one exception and use a synchronous request/reply when sending the Join message so that the GameActor can indicate to the frontend that the Join succeeded.&lt;br /&gt;&lt;br /&gt;In any case, after the initial Join the GameActor can send one of two messages to the frontend. First, the "input" actor can be sent a Prompt message:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush:scala"&gt;case class Prompt(status : String, message: Option[String], choices : List[Choice])&lt;br /&gt;case class Choice(description : String, agent : ActorRef, action : Action)&lt;br /&gt;sealed trait Action&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The Prompt message basically contains the current status of the player, an optional message (e.g. "You have moved to X"), and a List of current Choices for the player. A Choice simply binds a description to some Action as well as the actor that will perform the Action. Remember, everything here is an actor (including other players), so this allows us to do direct chats between users among other things. Action is just a marker trait so that we can constrain things. On the frontend side, when a choice is made the frontend simply sends the "action" instance to the "agent" instance as specified in the Choice.&lt;br /&gt;&lt;br /&gt;We also want the GameActor to be able to asynchronously update the player's display, so we'll add a Display message:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush:scala"&gt;case class Display(message : String) extends Action&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Note that we also make this an Action so that it can pull double-duty as the message that players send to one another for chat.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;On to Lift&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;Now that we have an outline of interaction between the frontend and the game engine, let's look at how we can realize the frontend in Lift using a CometActor. This source is in &lt;a href="https://github.com/samreid/scala-workshop/blob/master/derek-lift-demo/src/main/scala/code/comet/GameDisplay.scala"&gt;GameDisplay.scala&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;The first thing we want to do is define a case class to hold our current state. In Lift, Comet components live for the duration of the page view, so re-rendering the page will create a new component. In our case we want the state to stay in a user's session so that a refresh doesn't end their game.&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush:scala"&gt;case class ClientState(name : String,&lt;br /&gt;status : String,&lt;br /&gt;messages : List[String],&lt;br /&gt;choices : List[Choice])&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;In our GameDisplay, we set up a SessionVar to hold this state in a Box so that we can represent an unjoined state as Empty.&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush:scala"&gt;class GameDisplay extends CometActor {&lt;br /&gt;...&lt;br /&gt;object currentState extends&lt;br /&gt;SessionVar[Box[ClientState]](Empty)&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Bridging in Akka&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;Before we get into the render and message-processing logic of our GameDisplay, we have one more piece of plumbing to create: a bridge between Akka and Lift Actors. Lift has its own Actor library, and it's based on the same core concepts, but their not directly interchangable. For the purposes of this demo I wrote a small proxy Actor with Akka to forward messages from the Akka side of things back to the CometActor (remember, we pass Akka ActorRefs to the GameEngine):&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush:scala"&gt;class BridgeActor extends Actor {&lt;br /&gt;private var target : Option[CometActor] = None&lt;br /&gt;def receive = {&lt;br /&gt;case comet : CometActor =&gt; target = Some(comet)&lt;br /&gt;case msg =&gt; target.foreach(_ ! msg)&lt;br /&gt;}&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Basically you can send a reference to a CometActor to the BridgeActor and it registers it as the forwarding target. Any other message is sent to the forwarding target, if defined. There's actually a cleaner way to do this by using &lt;a href="http://scala.sygneca.com/patterns/duck-typing-done-right"&gt;structural types&lt;/a&gt; and redefining the Join case class (and GameActor's internal code) as:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush:scala"&gt;object ActorType {&lt;br /&gt;type Basic = { def ! (msg : Any) : Unit }&lt;br /&gt;}&lt;br /&gt;case class Join(name : String, input : ActorType.Basic, display : ActorType.Basic)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Just remember that structural typing uses reflection, so you need to be aware of performance and functionality constraints if you want to use it.&lt;br /&gt;&lt;br /&gt;In any case, our code uses the BridgeActor, so we need to set that up in our GameDisplay constructor by instantiating it Akka-style (Actors.actorOf(...).start()) and then sending it a "this" reference to register our GameDisplay as the forwarding target:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush:scala"&gt;class GameDisplay extends CometActor {&lt;br /&gt;// A bridge between the Lift and Akka actor libraries&lt;br /&gt;private val bridge = Actors.actorOf(classOf[BridgeActor]).start()&lt;br /&gt;bridge ! this&lt;br /&gt;&lt;br /&gt;// Make sure to stop our BridgeActor when we clean up Comet&lt;br /&gt;override protected def localShutdown() {&lt;br /&gt;  bridge.stop()&lt;br /&gt;}&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Note that we also override our localShutdown method to make sure we clean up the BridgeActor when our own Comet component is cleaned up (iron9light, thanks for pointing this out).&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Comet Rendering&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;At this point we've defined how we'll store our Comet state and how it will receive messages from the game engine, so now it's time to actually look at the rendering and message processing. In Lift, the CometActor's main render is handled by the "render" method (surprise!). In our case, we want to render differently based on whether the player has joined the game. First, let's look at what happens when the player hasn't joined the game.&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush:scala"&gt;def render = currentState.is match {&lt;br /&gt;case Empty =&gt; {&lt;br /&gt;/* We need to prompt the player for their name in order to join&lt;br /&gt;* the game. The ajaxForm method wraps a regular HTML form&lt;br /&gt;* (specified here directly with Scala's XML literals) and processes&lt;br /&gt;* the form as an AJAX submission. */&lt;br /&gt;SHtml.ajaxForm(&lt;br /&gt;&lt;span&gt;What's your name?&lt;/span&gt; ++&lt;br /&gt;/* SHtml.text generates a text input that invokes a Scala&lt;br /&gt;* callback (in this case, the login method) with the text&lt;br /&gt;* it contains when the form is submitted. */&lt;br /&gt;SHtml.text("", login) ++&lt;br /&gt;&amp;lt;input type="submit" value="Log in" /&gt;&lt;br /&gt;)&lt;br /&gt;}&lt;br /&gt;...&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Hopefully the comments are self-explanatory, but I do want to point out how nice Lift's AJAX support is. Simply wrapping a normal form in SHtml.ajaxForm(...) gets me a form that will submit via an AJAX call. AJAX and Comet work really well together, and Lift has first-class support both. &lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Sending Messages Synchronously&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;At this point we've rendered a simple form out to the user, and when they put in their name and hit the "Log in" button we'll get a callback (from the SHtml.text input) with the submitted name in the login method:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush:scala"&gt;def login (name : String) {&lt;br /&gt;Controller.game !! Join(name, bridge, bridge) match {&lt;br /&gt;case Some(Prompt(status,message,choices)) =&gt;&lt;br /&gt;currentState.set(Full(ClientState(name,status,message.map(List(_)) getOrElse Nil,choices)))&lt;br /&gt;case other =&gt; error("Error: " + other)&lt;br /&gt;}&lt;br /&gt;reRender()&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The "!!" is actor-ese (supported by both Lift and Akka actors) to send a message to an actor and wait for a reply. In this example we're sending the Join message to our game engine and wait for the game engine's response to pattern match. Note that the return type fo "!!" is an Option (or Box in LiftActor) because it's possible that an error may occur during processing. We only care about the game engine returning our first Prompt, so I've skimped a bit on error handling (errors are simply displayed as a Lift error message), but in a real app you might want something more involved.&lt;br /&gt;&lt;br /&gt;If we do get back a prompt, we want to initialize our state by setting a new Full box containing said state. We simply copy in the name and current choices, and we map the optional message to a List. In either the success or error case we re-render the whole page since everything is going to change.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Rendering with CSS bindings&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;Now that we have some state, we use the second match in our render method to produce some markup:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush:scala"&gt;def render = currentState.is match {&lt;br /&gt;...&lt;br /&gt;case Full(state @ ClientState(name, status, messages, choices)) =&gt; {&lt;br /&gt;/* When we have state to render, utilize Lift's&lt;br /&gt;* CSS binding Domain-Specific Language (DSL) to&lt;br /&gt;* process the template we were given. More on CSS Bindings&lt;br /&gt;* can be found here:&lt;br /&gt;*&lt;br /&gt;* http://www.assembla.com/wiki/show/liftweb/Binding_via_CSS_Selectors&lt;br /&gt;*/&lt;br /&gt;"#status *" #&gt; status &amp;&lt;br /&gt;"#messages *" #&gt; messages.reverse.map{Text(_) ++ &amp;lt;br/&amp;gt;} &amp;&lt;br /&gt;".choice" #&gt; generateChoices(state)&lt;br /&gt;}&lt;br /&gt;}  &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This is simply a set of &lt;a href="http://exploring.liftweb.net/master/index-5.html#toc-Subsection-5.3.2"&gt;CSS selector transforms&lt;/a&gt; that fill in our template (&lt;a href="https://github.com/samreid/scala-workshop/blob/master/derek-lift-demo/src/main/webapp/index.html"&gt;src/main/webapp/index.html&lt;/a&gt;):&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush:html"&gt;&amp;lt;div class="lift:comet?type=GameDisplay"&gt;&lt;br /&gt;&amp;lt;h1&gt;Status: &amp;lt;span id="status"&gt;Nothing&amp;lt;/span&gt;&amp;lt;/h1&gt;&lt;br /&gt;&amp;lt;h1&gt;Messages:&amp;lt;/h1&gt;&lt;br /&gt;&amp;lt;div id="messages"&gt;&amp;lt;/div&gt;&lt;br /&gt;&amp;lt;h1&gt;You may:&amp;lt;/h1&gt;&lt;br /&gt;&amp;lt;div id="choices"&gt;&lt;br /&gt;&amp;lt;ul&gt;&lt;br /&gt;&amp;lt;li class="choice"&gt;Something&amp;lt;/li&gt;&lt;br /&gt;&amp;lt;/ul&gt;&lt;br /&gt;&amp;lt;/div&gt;&lt;br /&gt;&amp;lt;/div&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;We delegate to the "generateChoices" method to create the actual choices since we'll want to use this in other places:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush:scala"&gt;/**&lt;br /&gt;* Because we need to generate choices in both our main render and our&lt;br /&gt;* updateChoices methods, we refactor out the common generation here.&lt;br /&gt;*/&lt;br /&gt;def generateChoices(state : ClientState) : NodeSeq = state.choices.flatMap {&lt;br /&gt;/* Special handling for chat messages. Here we create a new form&lt;br /&gt;* that allows us to customize our message */&lt;br /&gt;case Choice(description, agent, Display(otherPlayer)) =&gt; {&lt;br /&gt;SHtml.ajaxForm(&lt;br /&gt;&amp;lt;li&gt;Say "{ SHtml.text("hi", message =&gt; agent ! Display(state.name + " says: " + message)) }" to {otherPlayer}&lt;br /&gt;&amp;lt;input type="submit" value="Send!" /&gt;&amp;lt;/li&gt;&lt;br /&gt;)&lt;br /&gt;}&lt;br /&gt;case choice =&gt;&lt;br /&gt;SHtml.a(() =&gt; perform(choice), &amp;lt;li&gt;{choice.description}&amp;lt;/li&gt;)&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Here's where we get a little fancy. We're going to flatMap over our list of choices to produce a NodeSeq of the markup. If the choice happens to be a "Display" choice (remember, this is for chat or one-way notification), we generate a text input inside an AJAX form so that we can send user-specified text to the other player's display actor. If it's a normal choice, we just set up an AJAX link to perform the choice when clicked. Performing the choice simply sends the Choice's action to the Choice's actor and waits for a Prompt response. If it receives a proper Prompt response it sends it to itself (this allows for uniform handling of Prompt changes):&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush:scala"&gt;choice.agent !! ClientChoice(state.name, choice.action) match {&lt;br /&gt;case Some(p : Prompt) =&gt; this ! p&lt;br /&gt;case other =&gt; error(other.toString)&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Reacting to External Events&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;Now, up to this point everything is user-driven. However, the fact that Lift's Comet support is actor-driven means we can respond to events triggered in the game engine, too. We achieve this by hooking into the actor processing on the mediumPriority method. This is a PartialFunction[Any,Unit] that can process selected messages. Remember, to work with Akka we've provided a bridge, but otherwise there's no difference if you decide to stick entirely with Lift actors (or some other impl).&lt;br /&gt;&lt;br /&gt;Our mediumPriority handler wants to cover two main cases. First, we need to handle display messages sent to us, either from other players or from the game engine:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush:scala"&gt;override def mediumPriority = {&lt;br /&gt;case Display(message) =&gt; currentState.foreach {&lt;br /&gt;state =&gt; {&lt;br /&gt;currentState.set(Full(state.copy(messages = message :: state.messages)))&lt;br /&gt;partialUpdate(updateMessages())&lt;br /&gt;}&lt;br /&gt;}&lt;br /&gt;...&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;If we receive a new Display message, we update our state (using the ClientState case class's copy method) and then call partialUpdate. The partialUpdate method allows us to send arbitrary JavaScript commands to the client asynchronously to update the page. In our case, the updateMessages method uses JsCmds.SetHtml to replace the "messages" div with our new list of messages:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush:scala"&gt;def updateMessages() : JsCmd = {&lt;br /&gt;currentState.is.map {&lt;br /&gt;state =&gt; JsCmds.SetHtml("messages", state.messages.reverse.flatMap{Text(_) ++ &amp;lt;br/&gt;})&lt;br /&gt;} openOr JsCmds.Noop&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Similarly, if we receive a new Prompt message, we update our state and use partialUpdate to not only render the new messages, but a new list of Choices, too:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush:scala"&gt;override def mediumPriority = {&lt;br /&gt;...&lt;br /&gt;case Prompt(newStatus, message, newChoices) =&gt; currentState.foreach {&lt;br /&gt;state =&gt; {&lt;br /&gt;// Optionally prepend the provided message&lt;br /&gt;val newMessages = message.map(_ :: state.messages) getOrElse state.messages&lt;br /&gt;&lt;br /&gt;currentState.set(Full(state.copy(status = newStatus,&lt;br /&gt;messages = newMessages,&lt;br /&gt;choices = newChoices)))&lt;br /&gt;partialUpdate(updateMessages() &amp; updateChoices())&lt;br /&gt;}&lt;br /&gt;}&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The updateChoices method is similar to updateMessages, except we can use the CSS transforms again on the "defaultHtml" member. This member is initialized to whatever the content of our Comet template tag is, so we can reuse it later. Remember that CSS transforms are really just NodeSeq =&gt; NodeSeq functions, which is why we can use "apply" to perform the transform in place:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: scala"&gt;def updateChoices() : JsCmd = {&lt;br /&gt;currentState.is.map {&lt;br /&gt;state =&gt; JsCmds.SetHtml("choices",&lt;br /&gt;("#choices ^^" #&gt; "ignore" &amp;&lt;br /&gt;".choice" #&gt; generateChoices(state)).apply(defaultHtml)&lt;br /&gt;) &amp;&lt;br /&gt;JsCmds.SetHtml("status", Text(state.status))&lt;br /&gt;} openOr JsCmds.Noop&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Wrapping Up&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;As you can see, we can add a lot of rich functionality with very little code. Excluding the game engine itself, the Comet actor is 170 lines of code including comments, and the template is about 30. We're able to perform any action that the game engine sends us, including customized chat with other players. Lift's use of the Actor model makes event-driven rendering simple and allows for some fantastic client-side functionality.&lt;br /&gt;&lt;br /&gt;I hope that people find this post informative, and I welcome feedback. Happy Lifting!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1660012043443342889-2927237295284124457?l=riteofcoding.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://riteofcoding.blogspot.com/feeds/2927237295284124457/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1660012043443342889&amp;postID=2927237295284124457' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1660012043443342889/posts/default/2927237295284124457'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1660012043443342889/posts/default/2927237295284124457'/><link rel='alternate' type='text/html' href='http://riteofcoding.blogspot.com/2011/05/beyond-chat-lift-comet-and-akka.html' title='Beyond Chat : Lift, Comet and Akka'/><author><name>Derek</name><uri>http://www.blogger.com/profile/11374577951957547085</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/_I5O6EA8MygE/StINj_7JHTI/AAAAAAAAAL0/zIq8fxJWJSI/S220/25f0e94.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1660012043443342889.post-454545241378449939</id><published>2011-02-08T06:07:00.000-07:00</published><updated>2011-02-08T12:13:14.421-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='queues'/><category scheme='http://www.blogger.com/atom/ns#' term='scala'/><title type='text'>It's Not the Size, It's How You Use It</title><content type='html'>Yesterday I saw a flurry of discussion surrounding a post &lt;a href="http://teddziuba.com/2011/02/the-case-against-queues.html"&gt;deriding not just message queueing systems but people who choose to use them&lt;/a&gt;. Normally, considering the tone of the post, I would just ignore it as a rant (did he get saddled with a poor implementation somewhere?), but in this case I actually use queues in a moderate-sized system and I'd like to think I'm not a complete idiot. The post makes some statements about queues that contain some truth to them (as well as some that appear misinformed), but mostly it overgeneralizes and doesn't attempt to provide any in-depth discussion about the issues you need to consider when using (or choosing to use) queues. It certainly doesn't talk about any benefits. That's what this post is for.&lt;br /&gt;&lt;br /&gt;First, a caveat: by no means am I an expert in queuing systems, but I'd like to think that I'm a decent programmer and I've been working with queuing systems in one form or another for about 5 years. If I misstate something here please let me know so I can correct it.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;The Router Analogy&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;As a network engineer, I deal with queues all of the time. These aren't the message passing queues that Ted is talking about per se (I'll get to those shortly), but they share some characteristics. First and foremost, the queues in routers and switches provide elasticity, which in turn provides better link utilization. Network traffic can be quite bursty, and without queues to smooth out the bursts congestion would have a much more significant (and negative) effect than it otherwise does. The same holds true for a job processing system or any other message passing scenario: a queue allows you to plan for the steady state but deal with the occasional spike. &lt;br /&gt;&lt;br /&gt;Another use of queues, coming from the networking perspective, is prioritization. In network equipment prioritization is typically done via 2 or more queues for a given egress port, with a variety of scheduling algorithms used to pull messages from the different queues. You could do the exact same thing with a queuing system, although many brokers support some form of prioritization within a given queue.&lt;br /&gt;&lt;br /&gt;As Ted points out, you can achieve both of these goals with a database, syslog, or even flat text files (I repeat myself), but the question is, do you want to? Sure, I can see scenarios where you might have relatively few/relaxed requirements, but a modern message broker is not the simplistic tool that Ted makes them out to be. For all but the simplest cases you're going to be duplicating a lot of work that others have done (NIH syndrome?) and you probably won't write something as scalable or robust as what's already out there.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Dude, Where's My Message?&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;One of the claims that the post makes is that&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;br /&gt;Depending on your queue implementation, when you pop a message off, it's gone. The consumer acknowledges receipt of it, and the queue forgets about it. So, if your data processor fails, you've got data loss. &lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;br /&gt;Well, the key there is "Depending on your queue implementation". Most brokers I've worked with offer the choice between auto-ACK and explicit ACK. They also can provide things like transactions and durable messages, but I'm not going to get into too much detail here. The way you typically deal with work that you don't want to lose is to pop the message, process it, and then ACK it after you're finished. This is called "at-least-once" processing because if your consumer falls over after it finishes processing but before the ACK, the message will go back on the queue and some other consumer will grab the message and reprocess it. For this reason it's recommended that the processing is &lt;a href="http://www.soapatterns.org/idempotent_capability.php"&gt;idempotent&lt;/a&gt;. Without the explicit ACK messaging is basically "at-most-once". There are also cases where you really don't care if you lose a message, so no one is trying to say that one size fits all.&lt;br /&gt;&lt;br /&gt;You can also try for "exactly-once", but things get significantly more complex. In fact, you really can't achieve it 100% of the time because, hey, we live in a non-deterministic world. There will always be some failure mode, although you can certainly approximate 100% guarantees to successive decimal points. In other words, your solution depends on what's good enough for your requirements.&lt;br /&gt;&lt;br /&gt;As an example, let's look at the post's suggestion of using a database or syslog for collecting job messages. Publishing to syslog is unacknowledged, and clients often have buffers sitting between them and the syslog daemon (network, I/O, etc). It's entirely possible that work would get lost because the message never makes it to the syslog daemon in the first place. Once it does make it to the log you need to keep track of which messages you've processed and which ones remain. You're keeping state somewhere, so with a failure it's still possible that your consumer could reprocess messages, etc. Same thing with databases. You can use locking, transactions, etc to try and guard against lost/reprocessed messages and you're still in the same boat as with message queuing, you're just writing a lot of the logic yourself.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Shades of Blur&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;Another point that the post tries to make is the intractability of monitoring a queuing system. I think that the claim that using a message queue &lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;br /&gt;...blurs your mental model of what's going on. You end up expecting synchronous behavior out of a system that's asynchronous.&lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;br /&gt;is missing the point. What I would say instead is that you need to really understand the systems you're using. That means failure modes, limitations, and fundamentals of operation. This is me speaking not just as a developer, but as a network engineer and sysadmin. Disks will crash, RAID arrays will get corrupted, and BGP will mysteriously route your traffic from Saint Louis to Charlotte via a heavily congested T3 in DC (thanks Cogent!). I've seen syslog crash and fall over on Solaris and I've seen databases corrupt in all sorts of unique ways.&lt;br /&gt;&lt;br /&gt;If you need strict synchronous behavior then a messaging system is probably the wrong tool. But if you understand the limitations going in then you make an informed decision. System engineering and architecture is seldom as black-and-white as this post portrays. &lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Keeping an Eye on Things&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;As I said before, queues can provide elasticity for the occasional spike, but if you're constantly running behind you need to reassess what you're doing. Add more consumers, prioritize, etc. Re-architecting for scale is not something unique to message queues. Database sharding, load balancing, etc, all attest to this. &lt;br /&gt;&lt;br /&gt;A major component of this is monitoring, and monitoring well. Ted asks how you can monitor a queue with just the length. For starters, throughput, and therefor wait time (from production to consumption) is usually a more important metric in the systems I've worked on. Queue length could vary significantly from moment to moment depending on your production patterns, but if there's a good bound on how long messages take to process then you don't really care. If your processing time per-message stays fairly constant then queue length can be a good predictor for queue wait, but this isn't always the case.&lt;br /&gt;&lt;br /&gt;For example, at my job I write and maintain an image processing system that renders images for our orders. Each resulting image can be comprised of one or more source images and one or more operations on those images. The time to render varies depending the script, but generally stays within a certain bound. In our usage we monitor not only the queue length but the average queue throughput. We also record the duration for each render as well as each render's individual operations (crop, compose, etc), but that's more for tuning and QA on the render engines than for indicating health of the system at this point. So far this system has served us well. We have a good idea of how big the queue gets depending on time of year, day of week, etc, and we know that with our current set of servers we should be seeing roughly the same throughput at any given time with a non-empty queue. What constitutes a healthy system is going to be different for every system whether or not it uses a queue (how big is my table, how large is the logfile, etc), so I don't see how not using a queue is going to help. In fact, most brokers I've worked with have good facilities for monitoring built-in (via SNMP, REST, etc), so again, this is something where you can either leverage other people's work or you ending up re-writing it all yourself.&lt;br /&gt;&lt;br /&gt;Additionally, you should be monitoring the consumers. Even if you don't care about how long things take to process, you should know that things are &lt;span style="font-weight:bold;"&gt;being processed&lt;/span&gt;. This is not the difficult problem that the post makes it out to be, and again, you're going to want to do this no matter what mechanism you're using to move jobs around. Do you think that not using a queue is going to magically obviate the need to keep an eye on the whole system? Really?&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Why I Use Queues&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;Thus far I've basically worked at refuting or expanding on some of the points (accusations?) made in the post. Now I would like to cover some of the things that I think make queuing systems beneficial.&lt;br /&gt;&lt;br /&gt;First, queues not only decouple the producer and consumer, but they decouple the &lt;span style="font-style:italic;"&gt;routing&lt;/span&gt; between the producer and consumer. In some systems the routing is configured on the broker itself, in others the routing can be specified by the consumers (AMQP, for example). This allows a lot of flexibility in terms of how you process messages. Because the transport between producer and consumer is abstracted, you can do interesting things like message transformation by re-routing the messages via a middle-man (see Apache Camel, among others). Not everyone will need this flexibility, but when you need it you don't have to make any changes to your producers or consumers.&lt;br /&gt;&lt;br /&gt;Another aspect of this decoupling is the ability to transparently inspect traffic going through the queue. Much as you would configure a mirror port on a switch to do a packet capture, we can tap into a queue and see the traffic that's going through production. In my case I use this to sample the work queue to build QA data sets. That way we can run a representative sample of work without having to build our own artificial jobs to mimic the real work. &lt;br /&gt;&lt;br /&gt;The second thing I like about queuing systems is that they can provide a unified infrastructure service for job management. Now, there is always the danger of holding a hammer and seeing everything as a nail. In our enterprise, however, we have a lot of disparate systems that do batch processing and with queuing we can get a better overall picture of the processing via a single service. We're also looking at federating 3000+ brokers so that our various sites can use a global addressing scheme to support disconnected operation (a limitation of our environment) without having to put all of the logic into each and every client and service. Given the option of writing an federated job system by myself and writing a small federation agent (about 600 lines of Scala) I'm always going to choose the latter.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Is He Done Yet?&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;Almost. Message queuing systems, like any other arrow in an engineer's quiver, can be misapplied. But the mis-/overuse of a given tool by the naive is no reason to tar it (or the people who use it) as useless. In the 14 or so years I've been programming I've seen misuse of a lot of techniques and tools, but that just prompts me to learn from those mistakes and try to pass what I've learned on to others. In that spirit, I hope this post succeeds in making developers better informed about queuing systems and where they work and where they don't.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1660012043443342889-454545241378449939?l=riteofcoding.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://riteofcoding.blogspot.com/feeds/454545241378449939/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1660012043443342889&amp;postID=454545241378449939' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1660012043443342889/posts/default/454545241378449939'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1660012043443342889/posts/default/454545241378449939'/><link rel='alternate' type='text/html' href='http://riteofcoding.blogspot.com/2011/02/its-not-size-its-how-you-use-it.html' title='It&apos;s Not the Size, It&apos;s How You Use It'/><author><name>Derek</name><uri>http://www.blogger.com/profile/11374577951957547085</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/_I5O6EA8MygE/StINj_7JHTI/AAAAAAAAAL0/zIq8fxJWJSI/S220/25f0e94.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1660012043443342889.post-6004492634811277263</id><published>2010-10-29T15:37:00.000-06:00</published><updated>2010-10-29T15:48:21.282-06:00</updated><title type='text'>The pain of dependency management (that's you, vscaladoc-1.1-md-3)...</title><content type='html'>Well, it's certainly been a while since I've posted anything here, but I figured this is as good a place as any for this tidbit. While working on a project at work I ran "sbt clean-cache" accidentally, which ended up clearing out my lib_managed dir. "No problem," I thought, "I'll just run sbt update and make it right". Well, long story short, update failed to download vscaladoc-1.1-md-3:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;[warn]  ::::::::::::::::::::::::::::::::::::::::::::::&lt;br /&gt;[warn]  ::          UNRESOLVED DEPENDENCIES         ::&lt;br /&gt;[warn]  ::::::::::::::::::::::::::::::::::::::::::::::&lt;br /&gt;[warn]  :: org.scala-tools#vscaladoc;1.1-md-3: not found&lt;br /&gt;[warn]  ::::::::::::::::::::::::::::::::::::::::::::::&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Wait, what? That's not one of my dependencies, it must be a transitive dependency from something else that's gumming things up. After some searching through my ~/.ivy directory for "1.1-md-3", I found the culprit: configgy. I'm not sure why configgy is exporting a dependency on vscaladoc (or on specs, for that matter), but I'm guessing that this is just the default behavios and Configgy needs fixed. Anyways, changing my SBT dep to:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: scala"&gt;&lt;br /&gt;val configgy = "net.lag" % "configgy" % "2.0.0" intransitive()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;gets things going again by telling SBT to just ignore all of Configgy's upstream deps.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1660012043443342889-6004492634811277263?l=riteofcoding.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://riteofcoding.blogspot.com/feeds/6004492634811277263/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1660012043443342889&amp;postID=6004492634811277263' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1660012043443342889/posts/default/6004492634811277263'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1660012043443342889/posts/default/6004492634811277263'/><link rel='alternate' type='text/html' href='http://riteofcoding.blogspot.com/2010/10/pain-of-dependency-management-thats-you.html' title='The pain of dependency management (that&apos;s you, vscaladoc-1.1-md-3)...'/><author><name>Derek</name><uri>http://www.blogger.com/profile/11374577951957547085</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/_I5O6EA8MygE/StINj_7JHTI/AAAAAAAAAL0/zIq8fxJWJSI/S220/25f0e94.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1660012043443342889.post-5038013033072150927</id><published>2009-10-09T14:41:00.000-06:00</published><updated>2009-10-09T15:51:54.124-06:00</updated><title type='text'>Amateur tools for professional works</title><content type='html'>Tyler (one of my co-authors on DGL) sent &lt;a href="http://beginningruby.org/what-ive-earned-and-learned/"&gt;this link&lt;/a&gt; to me this week and it made me realize that I had some things to say as well.&lt;br /&gt;&lt;br /&gt;First off, I agree 100% with Peter's assessment of the Royalty Statement. It reads like a tax form and it took me several passes to really figure out what was going on. The previous statements make a lot more sense. I can't fathom why they would want to make the statements more difficult to read, but who knows. It seems like we've really had to push hard to get any kind of numbers out of APress, almost like they're trying to hide things from us. So far Tyler and Marius haven't received their statements, even though it's more than a week since the statement should have arrived according to the contracts. &lt;br /&gt;&lt;br /&gt;My second major complaint about the process was that we had to use Word to write the book (OpenOffice, in my case). At the time that we were negotiating with APress on the book, we were probably 60% done with it already, but it was written in LaTeX. Some of the other publishers that we were talking to either said that LaTeX was no problem or that they had previously helped people translate work from LaTeX. This was actually our main concern at the time, but we had such a positive recommendation on APress from someone else that we decided that this wasn't such a big deal. In hindsight, it was huge. LaTeX is designed to write large, structured documents and make it easy for the writer to do so. Word is not, and it would be extremely difficult for someone to convince me of this after our experience here. &lt;br /&gt;&lt;br /&gt;Word, as a WYSIWYG editor, forces me to not only be a writer, but to be a typesetter and layout artist. That's fine if you want to write a one or two page letter, but for a 2-300 page book it was painful. Styles do little to alleviate the pain. Cross-referencing and indexing are also trivial with LaTeX, but not so simple in Word (or OO). Another thing that I loved about LaTeX that I missed in Word/OO was that LaTeX has a listings package that syntax highlights code for you. We just defined Scala's keywords and syntax in the LaTeX preamble and all of our listings looked very nice. This wasn't supported by APress' style sheets, and even if it was I don't know of any software that would do this for you. I don't think there's any way for me to adequately explain my frustration with using the wrong tool for the job here.&lt;br /&gt;&lt;br /&gt;Related to this is APress' stylistic decision to not use numbered sections or page numbers in references. Instead, it was decided that when we need to reference a section, we should simply use the chapter or section name. This was really hard for me to swallow, especially since the LaTeX version does section and page numbered references just about automatically. That means that whenever we would add a listing or move an existing one, we had to hand-renumber them. It also means that text reads like:&lt;br /&gt;&lt;br /&gt;Additionally, we use Lift's binding mechanism (see the "Binding Values in Snippets" Section)&lt;br /&gt;&lt;br /&gt;instead of &lt;br /&gt;&lt;br /&gt;Additionally, we use Lift’s binding mechanism (Section 3.11.1)...&lt;br /&gt;&lt;br /&gt;Oh, and in the LaTeX PDF, those references are hyperlinks to take you directly to the right place. No such luck in DocWorld.&lt;br /&gt;&lt;br /&gt;My third complaint is that there was little downstream information flow from APress in terms of editorial decisions. We found out after the fact that the title was changed from "Exploring Lift" to "The Definitive Guide to Lift". We also found out when we were nearing completion that there would be no index at the back of the book, something that I find incomprehensible for a tech book (this is also something that's trivial to do with LaTeX). Finally, as we neared the publishing date we were told that the appendices would no longer fit in the print version. The appendices are well referenced from the text so this was not a trivial change to the book. In the end we compromised by making all of the appendices available for free here:&lt;br /&gt;&lt;br /&gt;http://www.apress.com/book/downloadfile/4390&lt;br /&gt;&lt;br /&gt;There was supposed to be a page at the back of the book explaining that due to printing limitations the appendices had to be placed online, and giving that link, but somehow that page never made it into the book. I'll take some blame on that for not properly proofing the final draft, but it was embarrassing and frustrating when the book came out and most of the questions on the mailing list were "Where are the appendices?"&lt;br /&gt;&lt;br /&gt;In the end it was a learning experience, just like it was for Peter. I wouldn't go so far as to say it was a bad experience, as it was definitely a pleasure (for me, at least) to work with the copy editors and other people in the production process at APress. I think that mostly I hope that this post will serve others who are looking to write books that they need to really carefully consider all of the aspects of what goes into writing a book, and what to look out for. I think I would have been happier with the process if I felt like there were less surprises. I also think that if I have to write another book I won't use Word. I think it would be far simpler to fake the same look using LaTeX class and style files than to go through that headache again.&lt;br /&gt;&lt;br /&gt;One final note. Unlike Peter, we already had our work licensed under the Creative Commons before we signed on with APress. Because of that, I still maintain the open source version of the book here:&lt;br /&gt;&lt;br /&gt;http://groups.google.com/group/the-lift-book&lt;br /&gt;&lt;br /&gt;My complaints here notwithstanding, the APress version &lt;span style="font-style:italic;"&gt;is&lt;/span&gt; a good book, and if you want to get started in Lift please check it out.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1660012043443342889-5038013033072150927?l=riteofcoding.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://riteofcoding.blogspot.com/feeds/5038013033072150927/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1660012043443342889&amp;postID=5038013033072150927' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1660012043443342889/posts/default/5038013033072150927'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1660012043443342889/posts/default/5038013033072150927'/><link rel='alternate' type='text/html' href='http://riteofcoding.blogspot.com/2009/10/amateur-tools-for-professional-works.html' title='Amateur tools for professional works'/><author><name>Derek</name><uri>http://www.blogger.com/profile/11374577951957547085</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/_I5O6EA8MygE/StINj_7JHTI/AAAAAAAAAL0/zIq8fxJWJSI/S220/25f0e94.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1660012043443342889.post-374807083844346888</id><published>2009-03-13T23:38:00.000-06:00</published><updated>2009-03-14T06:35:15.597-06:00</updated><title type='text'>ScalaJPA hits 1.0</title><content type='html'>I've been working on packaging up some of the Scala &lt;-&gt; JPA boilerplate in use for the Lift JPA Demo into a small project called ScalaJPA. After some work on documentation and bringing it up to Lift 1.0 goodness, you can find the project here:&lt;br /&gt;&lt;br /&gt;http://scala-tools.org/mvnsites/scalajpa/&lt;br /&gt;&lt;br /&gt;Scaladoc is here:&lt;br /&gt;&lt;br /&gt;http://scala-tools.org/mvnsites/scalajpa/scaladocs/index.html&lt;br /&gt;&lt;br /&gt;If you're using Maven, simply drop this dependency into your pom.xml:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: xml"&gt;&lt;br /&gt;&amp;lt;dependency&amp;gt;&lt;br /&gt;  &amp;lt;groupId&amp;gt;org.scala-libs&amp;gt;/groupId&amp;gt;&lt;br /&gt;  &amp;lt;artifactId&amp;gt;scalajpa&amp;gt;/artifactId&amp;gt;&lt;br /&gt;  &amp;lt;version&amp;gt;1.0&amp;gt;/version&amp;gt;&lt;br /&gt;&amp;lt;/dependency&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Just to give you an idea of how easy it is to use, here's how we would define a per-request EntityManager in Lift:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: scala"&gt;&lt;br /&gt;package com.foo.jpa&lt;br /&gt;&lt;br /&gt;import org.scala_libs.scalajpa.{LocalEM,RequestVarEM}&lt;br /&gt;&lt;br /&gt;object Model extends LocalEM("MyEMName") with RequestVarEM&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The Model object will then act as a ScalaEntityManager that uses a new per-request EM for its operations. We can use it in our code like&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: scala"&gt;&lt;br /&gt;...&lt;br /&gt;val author = new Author("Kurt Vonnegut")&lt;br /&gt;val book = new Book("Cat's Cradle", author)&lt;br /&gt;Model.persistAndFlush(book)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;In addition to the RequestVarEM, there are classes to handle a ThreadLocal EM as well as plain old factory classes that will let you set up and close your own ScalaEntityManagers. Check out the Lift JPA Demo site for more example code:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://github.com/dpp/liftweb/tree/794cac5abf6b1ae5502f6321847f6186fcd8de90/sites/JPADemo"&gt;http://github.com/dpp/liftweb/tree/794cac5abf6b1ae5502f6321847f6186fcd8de90/sites/JPADemo&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1660012043443342889-374807083844346888?l=riteofcoding.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://riteofcoding.blogspot.com/feeds/374807083844346888/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1660012043443342889&amp;postID=374807083844346888' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1660012043443342889/posts/default/374807083844346888'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1660012043443342889/posts/default/374807083844346888'/><link rel='alternate' type='text/html' href='http://riteofcoding.blogspot.com/2009/03/scalajpa-hits-10.html' title='ScalaJPA hits 1.0'/><author><name>Derek</name><uri>http://www.blogger.com/profile/11374577951957547085</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/_I5O6EA8MygE/StINj_7JHTI/AAAAAAAAAL0/zIq8fxJWJSI/S220/25f0e94.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1660012043443342889.post-4114242323440262507</id><published>2008-12-11T14:29:00.000-07:00</published><updated>2008-12-11T14:31:12.152-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='arrow keys'/><category scheme='http://www.blogger.com/atom/ns#' term='grub'/><category scheme='http://www.blogger.com/atom/ns#' term='vmware'/><title type='text'>GRUB and arrow keys</title><content type='html'>OK, not really related to coding but I thought it would be useful to put this out there in case anyone runs into the same issue. I'm trying to use a remote VMWare console for a linux VM I'm running at work and for some reason the arrow keys won't work. That means that I can't move up and down to select the recovery boot. I finally figured out, after much trial and error, that you can use Ctrl-P for "up" and Ctrl-N for "down". Good luck!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1660012043443342889-4114242323440262507?l=riteofcoding.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://riteofcoding.blogspot.com/feeds/4114242323440262507/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1660012043443342889&amp;postID=4114242323440262507' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1660012043443342889/posts/default/4114242323440262507'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1660012043443342889/posts/default/4114242323440262507'/><link rel='alternate' type='text/html' href='http://riteofcoding.blogspot.com/2008/12/grub-and-arrow-keys.html' title='GRUB and arrow keys'/><author><name>Derek</name><uri>http://www.blogger.com/profile/11374577951957547085</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/_I5O6EA8MygE/StINj_7JHTI/AAAAAAAAAL0/zIq8fxJWJSI/S220/25f0e94.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1660012043443342889.post-3952297866550030498</id><published>2008-04-03T08:22:00.001-06:00</published><updated>2010-07-09T10:55:18.239-06:00</updated><title type='text'>More progress on JPA</title><content type='html'>I've refactored my code now that I have a request-per-session. So far, this is what the Model object looks like:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: scala"&gt;&lt;br /&gt;object Model extends Logger {&lt;br /&gt;  def logname = "Model"&lt;br /&gt;&lt;br /&gt;  lazy val factory = Persistence.createEntityManagerFactory("foo")&lt;br /&gt;&lt;br /&gt;  // Per-request entity manager&lt;br /&gt;  //object emVar extends RequestVar[EntityManager](null)&lt;br /&gt;  //def em = emVar.is&lt;br /&gt;&lt;br /&gt;  // Temporarily using ThreadLocal until we get lifecycle handling in RequestVar&lt;br /&gt;  val emVar = new ThreadLocal[EntityManager]&lt;br /&gt;  def em = emVar.get()&lt;br /&gt;&lt;br /&gt;  /**&lt;br /&gt;   This method allows me to clean up my code a bit and only handle JPA-related exceptions.&lt;br /&gt;   An example usage would be:&lt;br /&gt;&lt;br /&gt;   def addFood(newFood : Food) = &lt;br /&gt;     wrapEM({&lt;br /&gt;       em.persist(newFood)&lt;br /&gt;       S.redirectTo("/food/list.html")&lt;br /&gt;     }, {&lt;br /&gt;       case cve : ConstraintViolationException =&gt; S.error("That food already exists!")&lt;br /&gt;       case _ =&gt; S.error("Internal error adding food")&lt;br /&gt;     })&lt;br /&gt;&lt;br /&gt;   Note that if I used normal try/catch then the wildcard match would trap the RedirectException&lt;br /&gt;   thrown by S.redirectTo.&lt;br /&gt;  */&lt;br /&gt;  def wrapEM[A](f : =&gt; A, handler : PartialFunction[Throwable, A]) : A = {&lt;br /&gt;    try {&lt;br /&gt;      val tx = em.getTransaction()&lt;br /&gt;&lt;br /&gt;      tx.begin()&lt;br /&gt;      try {&lt;br /&gt;        val ret : A = f&lt;br /&gt;        ret&lt;br /&gt;      } finally {&lt;br /&gt;        // make sure that we commit even with a redirectexception&lt;br /&gt;        tx.commit()&lt;br /&gt;      }&lt;br /&gt;    } catch {&lt;br /&gt;      // Special case. Usually we want to know why it failed to commit, not just that it failed&lt;br /&gt;      case re : RollbackException =&gt; {&lt;br /&gt;        val (cause,message) = if (re.getCause() == null) { &lt;br /&gt;          (re,"No cause") &lt;br /&gt;        } else { &lt;br /&gt;          (re.getCause(), re.getCause().getMessage())&lt;br /&gt;        }&lt;br /&gt;        this.error("EM Commit error: {}", message)&lt;br /&gt;        handler(cause)&lt;br /&gt;      }&lt;br /&gt;      case he : HibernateException =&gt; {&lt;br /&gt;        this.error("Hibernate error", he)&lt;br /&gt;        handler(he)&lt;br /&gt;      }&lt;br /&gt;      case pe : PersistenceException =&gt; {&lt;br /&gt;        this.error("EM Error", pe)&lt;br /&gt;        handler(pe)&lt;br /&gt;      }&lt;br /&gt;    }&lt;br /&gt;  } &lt;br /&gt;&lt;br /&gt;  /**&lt;br /&gt;   Bridging utility method. This should eventually go away.&lt;br /&gt;   */&lt;br /&gt;  &lt;br /&gt;  def usingEM[A](f: EntityManager =&gt; Can[A]) : Can[A] = wrapEM(&lt;br /&gt;    {f(em)}, {&lt;br /&gt;      case e : Exception =&gt; Failure(e.getMessage(), Full(e), Nil)&lt;br /&gt;    })&lt;br /&gt;&lt;br /&gt;  /**&lt;br /&gt;   Loads the given object and returns a Full can with the object if it could be loaded, Empty if it's not found.&lt;br /&gt;   */&lt;br /&gt;  def load[A](id : java.lang.Long, clazz : Class[A]) : Can[A] = load(this.em, id, clazz)&lt;br /&gt;  def load[A](em : EntityManager, id : java.lang.Long, clazz : Class[A]) : Can[A] = em.find(clazz, id) match {&lt;br /&gt;    case found : A =&gt; Full(found)&lt;br /&gt;    case null =&gt; Empty&lt;br /&gt;  }&lt;br /&gt;  &lt;br /&gt;  /**&lt;br /&gt;    Queries for a single instance of an object based on the queryName and params.&lt;br /&gt;    &lt;br /&gt;    @return Full(x) if a single result is returned, Empty if no results are returned, Failure on any errors&lt;br /&gt;    &lt;br /&gt;   */&lt;br /&gt;  def find[A](providedEM : EntityManager, queryName : String, params : Pair[String,Any]*) : Can[A] = {&lt;br /&gt;    try {&lt;br /&gt;      val query = providedEM.createNamedQuery(queryName)&lt;br /&gt;      &lt;br /&gt;      params foreach { param =&gt; query.setParameter(param._1, param._2) }&lt;br /&gt;      &lt;br /&gt;      Full(query.getSingleResult().asInstanceOf[A])&lt;br /&gt;    } catch {&lt;br /&gt;      case e: NoResultException =&gt; Empty&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  /**&lt;br /&gt;    Queries for a result list of objects based on the queryName and params&lt;br /&gt;&lt;br /&gt;    @return A scala.collection.jcl.Buffer of results&lt;br /&gt;   */&lt;br /&gt;  def findAll[A](em : EntityManager, queryName : String, params : Pair[String,Any]*) : Buffer[A] = { &lt;br /&gt;    val query = em.createNamedQuery(queryName)&lt;br /&gt;    &lt;br /&gt;    params foreach { param =&gt; query.setParameter(param._1, param._2) }&lt;br /&gt;      &lt;br /&gt;    val result = query.getResultList().asInstanceOf[java.util.List[A]]&lt;br /&gt;    &lt;br /&gt;    new BufferWrapper[A] { override def underlying = result }&lt;br /&gt;  }&lt;br /&gt;  &lt;br /&gt;  // Implicit defs to help with entity member access&lt;br /&gt;  implicit def setToWrapper[A](set : java.util.Set[A]) = new SetWrapper[A]{override def underlying = set}&lt;br /&gt;  implicit def listToWrapper[A](list : java.util.List[A]) = new BufferWrapper[A]{override def underlying = list}&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;I just found out that I can do JNDI and JTA in Jetty, so I might take a crack at getting that working once I've ripped out all of the old JPA access code in my various classes.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1660012043443342889-3952297866550030498?l=riteofcoding.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://riteofcoding.blogspot.com/feeds/3952297866550030498/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1660012043443342889&amp;postID=3952297866550030498' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1660012043443342889/posts/default/3952297866550030498'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1660012043443342889/posts/default/3952297866550030498'/><link rel='alternate' type='text/html' href='http://riteofcoding.blogspot.com/2008/04/more-progress-on-jpa.html' title='More progress on JPA'/><author><name>Derek</name><uri>http://www.blogger.com/profile/11374577951957547085</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/_I5O6EA8MygE/StINj_7JHTI/AAAAAAAAAL0/zIq8fxJWJSI/S220/25f0e94.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1660012043443342889.post-1564569456697231390</id><published>2008-04-02T15:11:00.000-06:00</published><updated>2009-03-14T06:42:33.442-06:00</updated><title type='text'>Evolution of JPA from Lift</title><content type='html'>&lt;span style="font-weight:bold;"&gt;Edit: Forgot that I could use RequestVar. Also, Look at my next post for a further evolution of the Model object&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Well, with some suggestions and info from David and Viktor I've started overhauling my use of JPA from within Lift. First and foremost, I've added in support for an EntityManager-per-request. This requires a little setup and utility class in my case:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: scala"&gt;&lt;br /&gt;object Model {&lt;br /&gt;  def createEM() = // fill this in however you want: JNDI, local Persistence, etc&lt;br /&gt;&lt;br /&gt;  // functions for setting a per-request entity manager&lt;br /&gt;  object emVar extends RequestVar[EntityManager](null)&lt;br /&gt;  def em = emVar.is&lt;br /&gt;&lt;br /&gt;  def wrapEM(f : =&gt; Unit, msg : String) : Unit = {&lt;br /&gt;    try {&lt;br /&gt;      f&lt;br /&gt;    } catch { &lt;br /&gt;      case e : Exception =&gt; this.error("Unexpected exception", e); S.error(msg)&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The wrapEM method is something I've put together to make my code a little more concise when all I want to do is display an error when something goes wrong. The real meat of EM-per-session happens in Boot:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: scala"&gt;&lt;br /&gt;// Set up a LoanWrapper to automatically instantiate and tear down the EntityManager on a per-request basis&lt;br /&gt;S.addAround(List(&lt;br /&gt;  new LoanWrapper { &lt;br /&gt;    def apply[T] (f : =&gt; T): T = {&lt;br /&gt;      val em = Model.createEM()&lt;br /&gt;&lt;br /&gt;      // Add EM into S scope&lt;br /&gt;      Model.emVar(em)&lt;br /&gt;   &lt;br /&gt;      try {&lt;br /&gt;        f&lt;br /&gt;      } finally {&lt;br /&gt;        em.close()&lt;br /&gt;      }&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;))&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1660012043443342889-1564569456697231390?l=riteofcoding.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://riteofcoding.blogspot.com/feeds/1564569456697231390/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1660012043443342889&amp;postID=1564569456697231390' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1660012043443342889/posts/default/1564569456697231390'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1660012043443342889/posts/default/1564569456697231390'/><link rel='alternate' type='text/html' href='http://riteofcoding.blogspot.com/2008/04/evolution-of-jpa-from-lift.html' title='Evolution of JPA from Lift'/><author><name>Derek</name><uri>http://www.blogger.com/profile/11374577951957547085</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/_I5O6EA8MygE/StINj_7JHTI/AAAAAAAAAL0/zIq8fxJWJSI/S220/25f0e94.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1660012043443342889.post-2321857909286490448</id><published>2008-03-31T13:44:00.000-06:00</published><updated>2009-03-14T06:44:06.697-06:00</updated><title type='text'>JPA From Lift</title><content type='html'>This has been discussed a little on the lift mailing list so I thought I'd write something up. Lift has a nice, built-in mapper framework for doing database-object mapping, but it's geared mainly toward small, lightweight schemas. When you start to throw a lot of tables at it, or if you need to do legacy or out-of-the-mainstream work, you can start to run into limitations or concept mistmatches that can end up making more sophisticated (and heavyweight) frameworks look more attractive. In my case I have an existing Java project that has its own persistence layer using JPA (Hibernate EntityManager), so it really seemed like it would be best to reuse the existing code rather than trying to forklift the entire thing.&lt;br /&gt;&lt;br /&gt;  To start with, accessing JPA entities from Scala is really no different that accessing them from Java. In either case you're getting a JPA EntityManager interface from somewhere (JNDI in my case) and using the persist, merge, etc. methods to control object persistence management. There are, however, a few things you can do with Scala to make it a little simpler to use and more Scala-ish.&lt;br /&gt;&lt;br /&gt;  The first issue I came to was that all of my JPA entities use Java collections for their collection interfaces. In my case they're all java.util.Sets, but it's nicer to have a Scala set to get access to all of the nice methods like exists, toList, etc. For that, I start out with an implicit definition:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: scala"&gt;&lt;br /&gt;implicit def utilSetToSet[A] (s : java.util.Set[A]) =&lt;br /&gt; new SetWrapper[A] { override def underlying = s}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;That way, when I access a set member of a given entity, I can automatically use it as a Scala set.&lt;br /&gt;&lt;br /&gt;The next thing I've added to make things a little easier is to create a "using" method to automatically handle closing the entity manager and handling any exceptions thrown:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: scala"&gt;&lt;br /&gt;def usingEM[A](f : EntityManager =&gt; Can[A]) : Can[A] = {&lt;br /&gt;  val em = emf.createEntityManager()&lt;br /&gt;&lt;br /&gt;  try {&lt;br /&gt;    f(em)&lt;br /&gt;  } catch {&lt;br /&gt;    case e: Exception =&gt; Failure(e.getMessage(), Full(e), Nil)&lt;br /&gt;  } finally {&lt;br /&gt;    em.close()&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;I've been vacillating on this particular method, since in the past I usually have used a filter to open an EntityManager and then close it at the end. Doing it this way allows me to better handle errors via Scala's nice pattern matching, but it also makes the code more "baroque":&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: scala"&gt;&lt;br /&gt;object entityName extends RequestVar[Can[String]](Empty)&lt;br /&gt;def view (xhtml : NodeSeq) : NodeSeq = usingEM({em =&gt;&lt;br /&gt;    val myEntity = em.createNamedQuery("findEntityByName")&lt;br /&gt;                     .setParamater("name", entityName.is openOr "non-existant")&lt;br /&gt;                     .getSingleResult()&lt;br /&gt;&lt;br /&gt;    Full(bind("me", xhtml, // blah blah))&lt;br /&gt;  }) match {&lt;br /&gt;    case Failure(_, e : NoResultException, _) =&gt; S.error("Could not find entity by name")&lt;br /&gt;    case Failure(msg, exception, _) =&gt; log(msg, exception); S.error("Error retrieving entity"); Text("")&lt;br /&gt;    case Full(x) =&gt; x&lt;br /&gt;  }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;I'm not convinced that this is the right way to do this so I'm open to suggestions.&lt;br /&gt;&lt;br /&gt;Derek&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1660012043443342889-2321857909286490448?l=riteofcoding.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://riteofcoding.blogspot.com/feeds/2321857909286490448/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1660012043443342889&amp;postID=2321857909286490448' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1660012043443342889/posts/default/2321857909286490448'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1660012043443342889/posts/default/2321857909286490448'/><link rel='alternate' type='text/html' href='http://riteofcoding.blogspot.com/2008/03/jpa-from-lift.html' title='JPA From Lift'/><author><name>Derek</name><uri>http://www.blogger.com/profile/11374577951957547085</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/_I5O6EA8MygE/StINj_7JHTI/AAAAAAAAAL0/zIq8fxJWJSI/S220/25f0e94.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1660012043443342889.post-4673510404567017092</id><published>2008-03-25T19:18:00.000-06:00</published><updated>2008-03-25T19:28:36.264-06:00</updated><title type='text'>Lift rocks!</title><content type='html'>I've been spending the past few months working on various projects (personal and work-related) based on the liftweb framework. It's been a heck of a learning process tackling Scala &lt;span style="font-style: italic;"&gt;and&lt;/span&gt; a new web framework but it's been very enjoyable. I've worked a lot with Struts and Tapestry in the past and Lift just feels, well, more natural. Things are simpler (once I've figured them out) and make much more sense. The hardest challenge has been keeping up with the rapid pace at which Scala and Lift are evolving. Just following the mailing lists takes up a small but significant chunk of each day.&lt;br /&gt;&lt;br /&gt;I started seriously using Lift around the time it was version 0.2, June of 2007. Here it is not even April '08 and we're on the brink of 0.7. In the intervening months a lot has changed and my understanding of the internal mechanisms has improved significantly. Being able to browse through the source (http://code.google.com/p/liftweb/) has helped me not only learn how Lift works, but how Scala works as well. Having never done functional programming before it's a lot like drinking from a firehose but I'm managing.&lt;br /&gt;&lt;br /&gt;The only thing I'm not happy with in this whole situation is that my prospects for working with other "Lifties" is pretty limited. It seems like everyone involved with Lift is either in San Francisco, Europe, or Oz :( I mean, it's fun as a personal challenge but I really miss being able to collaborate with other people. Hopefully, as the framework and language grow there will be more opportunities. Either way, I'm definitely having fun in the meantime.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1660012043443342889-4673510404567017092?l=riteofcoding.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://riteofcoding.blogspot.com/feeds/4673510404567017092/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1660012043443342889&amp;postID=4673510404567017092' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1660012043443342889/posts/default/4673510404567017092'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1660012043443342889/posts/default/4673510404567017092'/><link rel='alternate' type='text/html' href='http://riteofcoding.blogspot.com/2008/03/lift-rocks.html' title='Lift rocks!'/><author><name>Derek</name><uri>http://www.blogger.com/profile/11374577951957547085</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/_I5O6EA8MygE/StINj_7JHTI/AAAAAAAAAL0/zIq8fxJWJSI/S220/25f0e94.jpg'/></author><thr:total>0</thr:total></entry></feed>
