Play! 2.0: A First Impression
Play! is a web framework for Java and Scala. Play promises to bring the developer productivity of web frameworks like Ruby on Rails to the Java and Scala languages. Of course, it wouldn’t make much sense just to copy Rails. So Play adds its own spin: Play 2.0 is fully statically type checked, giving the developer quick feedback when something doesn’t make any sense.
Now that Play 2.0 is getting closer to final release I took some time to dive in. Here are my first impressions, using the Scala APIs.
TL;DR
Pros:
- Automatic recompile on refresh gives you extremely quick feedback.
- High modularity makes Play easy to learn, use, and extend.
- Very easy deployment to production environments.
- Type safe templates and routes make it easier to keep your application consistent.
- High performance with support for both synchronous and asynchronous HTTP handling.
- Java and Scala APIs are quite similar, so should allow easy transfer of skills.
- Support for pre-compiled assets including CoffeeScript, LESS CSS, and the Closure JavaScript compiler.
- Sessions are simple cookies, making it easy to scale or perform zero-downtime upgrades.
Cons:
- Support for WAR packaging is not planned until 2.1. This is needed if you need to deploy in a Servlet Container.
- Not backwards compatible with the older Play 1.x framework.
- Custom
conf/routes
format instead of simple DSL like Scalatra or spray.cc. - Many APIs rely on singletons (Scala) or static methods (Java), which can make testing challenging. However, fake implementations of the main abstractions are provided (Application, Request, etc).
Out of the box experience
Getting started with Play is straigthforward. Just download the distribution, unpack it, and add it to your $PATH. After that type play new my-app
,
select your application’s name (defaults to my-app
in this example), and type (Scala, Java, or empty). After choosing the Scala application type I
ended up with a new directory containing just a few directories and files (with just 19 lines of Scala code and 17 lines of HTML templates).
Starting the application is as easy as typing play run
and pointing your browser to http://localhost:9000/. The initially
load takes a while as it needs to compile templates and Scala sources. After the page loads you get a nice explanation what to do next and plenty of
documentation links:
Controllers and templates
The first thing you encounter in any web framework is to mapping from HTTP requests to your code. In Play this is extremely straightforward. The
request method and path are located in the conf/routes
file and the indicated method is invoked in your controller. An example route entry is:
GET / controllers.Application.index
So whenever the home page is requested the controllers.Application.index
method is invoked. Notice that you’ll get a compile time error if this
method does not exist or requires parameters. So if you add the following to the conf/routes
file:
GET /:page controllers.Application.page(page, format ?= "html")
and refresh your browser you’ll quickly see an error message:
Fixing this is easy, just add the missing method to the controller.Application
object:
def page(name: String, format: String) = Action {
Ok(<p>You requested page {name} in format {format}</p>.toString).as(HTML)
}
As you can see a controller action is just a method that returns an Action
. The Action
in turn returns an Ok
response, which translates into a
HTTP 200 OK
response. What I like here is that the controller method is directly linked from the conf/routes
file without me having to remember
any mapping conventions. There is also no complicated rendering pipelines that are typically associated with component based web frameworks like JSF
or Wicket.
In this action we directly returned a piece of HTML. It is also possible to use templates. Let’s change the index page to link to our new controller
action. By changing the index.scala.html
template to:
@(message: String)
@main("Welcome to Play 2.0") {
<a href="@routes.Application.page("1984", "PDF")">page example</a>
}
a new link is rendered that maps to the new route. After refreshing the browser you can now click the link to navigate to your new page
action. Extremely straightforward and especially nice is the use of type safe URLs and the light-weight template syntax (just using the @
sign to
add some Scala code in your HTML template).
Also nice is that templates are turned into ordinary methods (with the first line of the template becoming the parameter list) that can be called from
controllers, other templates, or tests. In the template above the main
template is invoked to render layout around the provided link.
Forms
Play also has support for forms, validation, and binding form data to domain objects. Unlike most other web frameworks it is not necessary to alter
your domain objects to use them with forms. Specifically, there is no need to add a default constructor or any setters. Here is an example domain
object User
with a form definition:
import play.api.data._, Forms._, validation.Constraints._
// Domain object
case class User(name: String, age: Int)
// Form definition
val userForm = Form(
mapping(
"name" -> nonEmptyText,
"age" -> number(min = 0, max = 150)
)(User.apply)(User.unapply))
// Form submit action
def register = Action { implicit request =>
userForm.bindFromRequest.fold(
errors => BadRequest(views.html.index(errors)),
user => Redirect(routes.Application.index))
}
The form defines two fields with validation and the mapping from the form fields to the domain object (in this case by using the Scala generated
User.apply
and User.unapply
methods).
The register
action uses the form definition to perform validation and bind data from the request to the domain object. In case of errors, the page
is rerendered with the validation errors. Otherwise a new user instance is available (and in this case a simple redirect is performed, a real
application would probably save the user somewhere). Notice that it is not possible to get a (partially) invalid user object, unlike most other form
validation frameworks (like Ruby on Rails, Spring MVC, and Wicket).
To render the form there are some predefined form helpers (also available in Twitter Bootstrap variant). Unfortunately the field names are not statically checked against the form definition:
@(registrationForm: Form[User])
@helper.form(action = routes.Application.register) {
@helper.inputText(registrationForm("name"))
@helper.inputText(registrationForm("age"))
<input type="submit" value="Register" />
}
Databases
Play comes with built-in support for SQL/JDBC databases. The simplest way to use this to first enable the datasource in conf/application.conf
:
db.default.driver=org.h2.Driver
db.default.url="jdbc:h2:mem:play"
Then add a schema evolution file conf/evolutions/default/1.sql
:
# --- !Ups
create table users (
name text primary key,
age int not null);
# --- !Downs
drop table users;
When you refresh the browser Play will notify you that you need to apply the schema migration:
Pressing the ‘Apply this script now!’ button will immediately perform the database migration.
Now that we have a running database it is easy to query using the JDBC API and the Anorm wrapper library:
import play.api.db._, anorm._, play.api.Play.current
def registeredUsers = DB.withConnection { implicit connection =>
SQL("select name, age from users")().map(row => User(row[String]("name"), row[Int]("age"))).toList
}
Since DB.withConnection
/DB.withTransaction
gives you a plain JDBC connection it is also easy to use any other Java or Scala tool to handle
database persistence.
Deployment
Deploying a Play application is easy. My preference would be to use the play dist
command
that builds a ZIP archive. Then you only need a Java runtime and database installation on your production server to run your application (everything
else is included in the ZIP, including the Scala libraries and a start
script).
Another option is to use the play stage
command to run the application in-place. Combine this with a git push
to Heroku
or your own production server and you’re ready to go.
Performance
Play is build on Netty, which provides very high HTTP server performance. Play supports both synchronous and asynchronous IO, so handling many long-lived connections should be possible (and Play provides excellent support for streaming using its Iteratees based data stream support). Integration with Akka 2.0 should give you all the tools you need to build high-performance and highly-scalable applications.
To give you an idea, I had no trouble getting more than 10.000 HTTP requests per second (tested using jmeter) on my mid-2010 MacBook Pro (2.66 GHz Intel Core i7). The rendered HTML page was fairly trivial (just the user registration form as shown above), but it is good to know basic performance is very good.
Conclusion
This was just a first quick look at Play 2.0, but I’m quite impressed with the ease of learning, development, and deployment. Play 2.0 is a worthy addition to the Typesafe Stack and should give Java and Scala developers a very good platform to build web applications on.