FANDOM


Julien Wetterwald, James Mao

Overview Edit

The Scala Programming Language Edit

Scala is a statically typed multi-paradigm programming language integrating features of object-oriented and functional languages. It runs on the JVM and is fully interoperable with Java. Scala is equipped with an expressive type system providing a powerful basis for the safe reuse of component abstractions and provides a combination of language mechanisms that make it easy to smoothly add new language constructs as libraries. The resulting language scalability -- as praised in Guy Steele's famous 1998 OOPSLA keynote, is illustrated by Scala's lightweight actor library. Furthermore, Scala syntax supports native XML literals.

The Lift Framework Edit

Lift is a framework for writing interactive web applications deployed on Servlet containers such as Jetty or Tomcat. Lift provides a designer-friendly templating engine where templates are written in pure XHTML and various types of snippets to generate dynamic content. Lift also provides helpers to easily generate AJAX forms and a library for creating long-lived actors maintaining a Comet-style connection to the browser. Last but not least, Lift features a type-safe object-relational mapper stressing security and access control that we won't discuss in this report.

Illustration Edit

To illustrate some of the features provided by the Lift framework, we are going to write a very simple interactive Comet chat. The different components of the system will communicate using the actor concurrency model. The five following events are going to be exchanged asynchronously by the different actors:

case class AddListener(listener: Actor)
case class RemoveListener(listener: Actor)
case class SendMessage(message: String)
case class MessageReceived(message: String)

The message broker is a singleton object that maintains a list of listeners. When the broker receives the SendMessage event, it forwards the message contained in the event to all its listeners with the MessageReceived event.

object MessageBroker extends Actor {
  val listeners = new HashSet[Actor]

  def act = loop {
    react {
      case AddListener(listener) =>
        listeners += listener
      case RemoveListener(listener) =>
        listeners -= listener
      case SendMessage(message) =>
        listeners.foreach(listener => listener ! MessageReceived(message))
    }
  }

  start
}

An instance of a Comet actor is associated with the user's session. A Comet actor can push data to the user's browser through a special HTTP connection maintained by the Lift framework (backed by the jQuery library). When a chat actor is created, it registers itself to the message broker. When it receives a MessageReceived event from the message broker, it pushes a partial update to the user's browser.

The partialUpdate method takes a JavaScript expression as a parameter. Lift contains several pre-built expressions such as AppendHtml that let the programmer push code to the browser without having to write a single line of JavaScript (i.e. a poor man's GWT) similar to Rails' RJS templates; AppendHtml in Lift corresponds to page.insert_html in RJS.

The render method is called when the Comet actor is initially requested by a template. It can either return XHTML or bindings to be used in the calling template. In this example, we decide to return the latter. We bind "messages" to an empty ordered list and "input" to a text input. The text input is generated by a call to the text function. The second parameter of text is an anonymous function sending the message entered in the text input to the message broker using the SendMessage event when the form is submitted. The way the closure is stored between the request-response cycle where it is created and the request-response cycle where it is executed is discussed in the next section.

class ChatActor(info: CometActorInitInfo) extends CometActor(info) {
  val defaultPrefix = "chat"

  def render = bind(
    "messages" --> <ol id="list" />,
    "input"    --> text("", message => MessageBroker ! SendMessage(message)))

  override def localSetup {
    MessageBroker ! AddListener(this)
  }

  override def localShutdown {
    MessageBroker ! RemoveListener(this)
  }

  override def lowPriority = {
    case MessageReceived(message) =>
      partialUpdate(AppendHtml("list", <li>{ message }</li>))
  }
}

Finally, the following template is used as the main view. In the body of the document, the chat actor is included. We display the messages followed by an AJAX form. The form contains the text input and a traditional submit button. <lift:comet type="ChatActor"> connects the view to the Comet actor named ChatActor. Lift will bind <chat:some_identifier> to the bindings defined in the render function; here, <chat:messages> and <chat:input> are bound to the two values defined in render.

<html xmlns="http://www.w3.org/1999/xhtml" xmlns:lift="http://liftweb.net/">
  <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
  </head>
  <body>
    <lift:comet type="ChatActor">
     <chat:messages />
     <lift:form>
       <chat:input />
       <input type="submit" value="Send" />
     </lift:form>
   </lift:comet>
  </body>
</html>

Unique Features Edit

The View-First Approach Edit

Templates in Lift are used for display only and never contain programming logic. Consequently, they can be manipulated by non-programmers with tools such as Dreamweaver. Furthermore, contrarily to Rails, Lift snippets are invoked by the template, and not vice versa. The following template illustrates the view-first approach by invoking the form method in the User snippet using the <lift:snippet> tag. The body of this tag can refer to values bound by the User snippet in the snippet-defined fields namespace.

<html xmlns="http://www.w3.org/1999/xhtml" xmlns:lift="http://liftweb.net/">
  ...
  <lift:snippet type="user:form" form="POST">
    First name <fields:first />
    Last name <fields:last />
    <fields:submit />
  </lift:snippet>
  ...
</html>

Rails' controller-first dispatch mechanism makes the assumption that there is only one piece of logic on the page and the rest is decoration. When a page contains more than one piece of logic (e.g. a shopping cart and an interactive chat), having to choose a single piece of logic make the controller code complicated. This usually require some combination of controller inheritance (move common logic into the superclass of controllers -- e.g. put the shopping cart code in the ApplicationController), moving code from controllers into helpers and models, and the use of partial views.

Finally, the view-first approach allows the templates to easily glue different components together. However, when gluing components gets more complicated (e.g. when dependencies must be satisfied), the simplicity of this approach fails and actual code has to be written.

Long-Lived Closures Edit

Lift allows the server application to attach closures to elements rendered in the user interface (e.g. form elements). The following snippet illustrates to usage of such closures.

class User {
  def form(template: Group) = {
    var first, last = ""
    bind("fields", template,
      "first"  --> text("", n => first = n),
      "last"   --> text("", l => last = l),
      "submit" --> submit("Submit", ignore => process(first, last)))
  }

  def process(first: String, last: String) {
    ...
  }
}

The n => first = n and l => last = l anonymous functions are attached to two text inputs. The input Lift passes to these functions is the value of the element. The body of these two functions assign the value of text input to the first and last variables. The ignore => process(first, last) anonymous function is attached to the submit button. In this case, we ignore the argument adequately named ignore because a button doesn't have a value. The body is call another function that could, for instance, store the two names in a database.

To achieve this magic, Lift stores the closures in a map when the component is rendered. Randomly generated strings are used as keys and inserted in the generated HTML elements. When the form is submitted, Lift looks for the closures in the map based on the provided keys and executes them. Note how the first and last variables are captured by the three closures and, consequently, stored between the two request-response cycles.

The advantage of this approach is that snippets can specify what happens when a specific button is pushed rather than having a controller parsing the submitted form and process the parameters extracted from the HTTP request. In essence, Lift allows you to specify server-side actions corresponding to user actions when the form is rendered rather than processing the results to figure out what actions the user performed during the next request. This reduces the need for magic constants used to map actions between the rendered view and controllers in other frameworks. In Rails, you might create a form with two buttons labeled 'Buy Now' and 'Buy Later' and then compare the submit parameter to 'Buy Now' and 'Buy Later' in the controller to determine the appropriate action. In Lift, this can be done with two different bindings in the render function.

Critique Edit

Advantages Edit

  • Binding server side state to users' sessions is incredibly powerful. Each user can have a living representation in the server that talks to other users' representations.
  • Built-in Comet support possibly the best Comet support in web frameworks. Pushing data to the browser is as simple as calling partialUpdate or asking for a full re-rendering of the component. Updates of multiple components are automatically serialized in a unique connection.
  • Long-live closures simplify form processing and can be used to create complex wizards.
  • View-first approach helps with 'Lean Controller, Fat Model'.
  • Server state machines support for models, including timeouts. For instance, we can use a machine to specify that after three days without confirmation, a new account has to be deleted. Consequently, there is no need for cron and script/runner type solutions in Rails. Instead of imperatively stating what maintenance operations to do; state what rules to enforce and it is automatically done when necessary.

Being the only web application framework written in Scala, it is worth mentioning some of the advantages of the language and its libraries as well:

  • Provides idioms commonly found in "dynamic" languages (i.e. Ruby, Python, Erlang) with a static type system.
  • Event-based actors relieve the programmer of the synchronization hell. Furthermore, lightweight actors scale.
  • Compiles to Java byte code and thus scales on existing infrastructure such as the Jetty application server.
  • Highly optimized at compile-time and at run-time, and thus faster than Ruby or Python applications.

Weaknesses Edit

  • Moving state to the server comes with all disadvantages discussed in class. That being said, various remote actors libraries are currently being developed and would permit actors living in different JVMs (running possibly on different machines) to communicate between each others seamlessly. Furthermore, it is often possible to architect an application such that loosely coupled components can live on different machines -- it is definitely possible with the stock trading application we will present during our in-class presentation.
  • Terrible API (i.e. badly chosen names, confusing domain model) and no documentation at all (the Scala documentation is OK, though).
  • Despite the fact that the API is stabilizing (unfortunately?), most of the tutorials are outdated.
  • Routing is a nightmare for no infrastructure is available. The same code to route an webservices controller in Lift and Rails are shown in the Appendix. The example is actually slightly biased in the favor of Lift since with a properly named controller, default routing in Rails will handle this without any custom routes.
  • Despite the fact that we can't move logic from snippets to templates, it is possible to move XHTML from templates to snippets. It is not always clear where the view has to be. Is a snippet a controller or a dynamically generated view? Is there actually a difference between the two?

Applications Edit

With Lift's advantages and weaknesses in mind, we conclude that Lift is well suited for highly interactive applications. It is especially well suited for collaborative applications where information flow between connected users and/or other services.

An interesting Lift application is Skittr, an actor-based Twitter clone. It avoids the famous "scaling Twitter" problem by avoiding the bottleneck: the database. Messages are sent directly from a user's actor to another. If we want persistence but don't need all transactional properties (which is arguably the case in a Twitter-like application), then storing messages in a database is extremely easy. When a new message is posted, we can forward it asynchronously to both its recipients and a specialized actors inserting the messages it receives in the database.

Appendix Edit

Rails: map.connect 'api/:action' :controller => :ticker_api

Lift:

class Boot {
  def boot {
    val apiDispatcher: LiftRules.DispatchPf = {
      case RequestMatcher(r @ RequestState("api" :: m :: Nil, _), _) => invokeApi(r, m)
    }
    LiftRules.addDispatchBefore(apiDispatcher)
    ...
    ...
  }
   
  private def invokeApi(request: RequestState, methodName: String)(req: RequestState): Can[ResponseIt] =
    createInvoker(methodName, new TickerAPI(request)).flatMap(_() match {
      case Full(ret: ResponseIt) => Full(ret)
      case _ => Empty
    })
}

Ad blocker interference detected!


Wikia is a free-to-use site that makes money from advertising. We have a modified experience for viewers using ad blockers

Wikia is not accessible if you’ve made further modifications. Remove the custom ad blocker rule(s) and the page will load as expected.