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.
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.scalafile in the
./appdirectory, which holds an ApplicationLoader and the router, which is super simple:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
./conf/application.confso 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.
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
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
Anyas 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.