Slim Play App
Play is one of two officially-supported web frameworks from Typesafe, the company behind Scala (the other is Spray). It runs on its own webserver, is non-blocking, and encourages the use of idiomatic Scala. It is often compared with Rails because of its emphasis on convention over configuration and because it’s a full-on framework that comes with most of the bells and whistles needed to build a full-featured webapp. Spray is considered by many to be the defacto API-centric alternative to Play, offering a Sinatra-esque DSL for routing and being slimmer to boot (from a files + LOC perspective).
After looking around I began suspecting that Play comes with the ability to be slimmed down. By combining the String Interpolating Routing DSL and Compile-time dependency injection of Play 2.4, I was able to build a Scala app that would give Sinatra a run for its money in terms of the whole brevity thing.
Methodology
All I did was:
- Use activator to generate a new Play app (
$ activator new slim-play play-scala
) - Delete the auto-generated controller, public, and view directories (won’t be using them)
- Create a
AppLoader.scala
file in the./app
directory, which holds an ApplicationLoader and the router, which is super simple: <div class=’bogus-wrapper’>
</pre></td><td class=’code’><pre>import play.api.ApplicationLoader.Context
import play.api._
import play.api.libs.concurrent.Execution.Implicits._
import play.api.mvc.Results._
import play.api.mvc._
import play.api.routing.Router
import play.api.routing.sird._
import scala.concurrent.Future
class AppLoader extends ApplicationLoader {
def load(context: Context) = new BuiltInComponentsFromContext(context) {
/**
* Simple & fairly self-explanatory router
*/
val router = Router.from {
// Essentially copied verbatim from the SIRD example
case GET(p"/hello/$to") => Action {
Ok(s"Hello $to")
}
/*
Use Action.async to return a Future result (sqrt can be intense :P)
Note the use of double(num) to bind only numbers (built-in :)
*/
case GET(p"/sqrt/${double(num)}") => Action.async {
Future {
Ok(Math.sqrt(num).toString)
}
}
}
}.application
}
</pre></td></tr></table></div></figure></notextile></div>
4. Add play.application.loader=AppLoader
to ./conf/application.conf
so that Play knows to load our custom app (that
contains our simple router)
The end result is a small, one-file Play app powered by a custom router and compile-time dependency injection. For more information, take a look at the slim-play repo on Github.
Conclusion
Play is an awesome framework; scalable, idiomatic (type-safe, threadsafe), well documented, and well supported by Typesafe and a great community. I’ve been happily using it to build various-sized apps for the better part of 2.5 years. If you want to have a well-structured app, it comes out of the box configured to provide that. However, it also has the surprising ability to shed weight and turn into a slim API-focused engine.
A word Sinatra-clones in Scala
Ruby is fairly ubiquitous when it comes to server-side web programming. Rails aside, Sinatra has made its mark on the world and made a name for itself as the DSL to mimic, with imitators in Ruby (Cuba), Python (Bottle, Flask), PHP (Laravel), Scala (Scalatra and its wrapper Skinny), and Javascript (Express). Thanks to its simple and easy to follow DSL routing, it’s gained a large following as well.
That said, blindly copying Sinatra’s DSL in other languages may be problematic, because Sinatra’s DSL relies on the Rack execution model (one request at a time per process/thread), and embraces Ruby’s spirit of developer happiness at the cost of performance. This is especially true in Scala, where the language was designed for concurrency and the community places heavy emphasis on adhering to a non-blocking execution model, eschewing mutation of data.
For example, I filed an issue with Scalatra a few months ago that was largely caused by indiscriminate copying of Sinatra’s DSL, as well being based on the Servlet async API (an intro to why we should move away from Servlets). Among other things, it led to:
- Loss of thread-safety, meaning you can no longer take advantage of Scala’s strength in concurrency for scaling purposes (a lot of Scala libraries also return Futures when dealing with I/O, as they should).
- Loss of static typing, which is terrible at design-time (IDE assistence and refactoring perspective), as well as runtime (performance). Scalatra apps are written in non-idiomatic Scala because the routing implementation takes an
Any
as the result of a route definition, including…yes, shutting down the Servlet container. In addition, it encourages you to mutate existing data (setting statuses on responses).
If you’re coming to Scala from Ruby and what you want is to build a small app using Sinatra-esque DSL in Scala, I would highly suggest evaluating Spray or slim-Play (as presented here) before choosing to go with Scalatra and friends: “Thar be dragons” in the long-run.