Play Framework Essentials

By Julien Richard-Foy
  • Instant online access to over 7,500+ books and videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. Building a Web Service

About this book

Play is a framework to write web applications using Scala or Java. It provides a productive development environment, allowing you to just hit the "refresh" button in your browser to compile your changes and reload the application. Because of its stateless nature, the framework makes it easy to build applications that scale. Play provides a reactive programming model to harness the event-driven HTTP layer.

This book provides a step-by-step walkthrough of how to build a complete web application following best application development practices using Play framework 2. All aspects specific to web-oriented architectures are covered: the HTTP layer, JSON manipulation, HTML templating, asset compression and concatenation, form submission, content negotiation, security, and HTTP streaming. The book will also provide detailed architectural insights into Play framework to give you a better understanding in order to successfully build scalable applications.

Publication date:
September 2014
Publisher
Packt
Pages
200
ISBN
9781783982400

 

Chapter 1. Building a Web Service

This chapter will cover the following topics:

  • Bootstrapping a Play project

  • Understanding the different pieces of a Play project

  • Mapping URLs to your service entry points

  • Serving JSON responses and reading and validating JSON requests

 

Play – a framework used to write web applications


Play is a framework used to write web applications. As shown in the following diagram, a web application is based on a client-server architecture and uses the HTTP protocol for communication:

Web-oriented architecture

Users have the role of clients and make HTTP requests to servers to interact with the application. The servers process their requests and send them a response. Along the way, the web application might need to make use of various databases or perhaps other web services. This entire process is depicted in the following diagram:

The Play framework's overall architecture

This book will show you how Play can help you to write such web applications. The preceding diagram shows a first big picture of the framework's overall architecture. We will refine this picture as we read through this book, but for now it is a good start. The diagram shows a web client and a Play application. This one is made of a business layer (on the right), which provides the services and resources specific to the application. These features are exposed to the HTTP world by actions, which themselves can be logically grouped within controllers. Gray boxes represent the parts of code written by the developer (you!), while the white box (the router) represents a component already provided by Play.

HTTP requests performed by the client (1) are processed by the router that calls the corresponding action (2) according to URL patterns you configured. Actions fill the gap between the HTTP world and the domain of your application. Each action maps a service or resource of the business layer (3) to an HTTP endpoint.

All the code examples in this book will be based on a hypothetical shopping application allowing users to manage and sell their items. The service layer of this application is defined by the following Shop trait:

case class Item(id: Long, name: String, price: Double)

trait Shop {
  def list(): Seq[Item]
  def create(name: String, price: Double): Option[Item]
  def get(id: Long): Option[Item]
  def update(id: Long, name: String, price: Double): Option[Item]
  def delete(id: Long): Boolean
}

In Java, the service layer is defined by the following Shop interface:

public class Item {

  public final Long id;
  public final String name;
  public final Double price;

  public Item(Long id, String name, Double price) {
    this.id = id;
    this.name = name;
    this.price = price;
  }

}

interface Shop {
  List<Item> list();
  Item create(String name, Double price);
  Item get(Long id);
  Item update(Long id, String name, Double price);
  Boolean delete(Long id);
}

Tip

Downloading the example code

You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.

The Item type simply says that an item has a name, a price, and an ID. The Shop type defines the typical Create, Read, Update, and Delete (CRUD) operations we want to do. Connecting with the figure that shows the architecture of Play applications, these types represent the business layer of your web service and their definition should live in a models package in the app/ source directory. The remainder of this chapter explains how to write a controller exposing this business layer via HTTP endpoints using JSON to represent the data.

As an example, here is a possible minimalist Scala implementation of the Shop trait:

package models

import scala.collection.concurrent.TrieMap
import java.util.concurrent.atomic.AtomicLong

object Shop extends Shop {
  private val items = TrieMap.empty[Long, Item]
  private val seq = new AtomicLong

  def list(): Seq[Item] = items.values.to[Seq]

  def create(name: String, price: Double): Option[Item] = {
    val id = seq.incrementAndGet()
    val item = Item(id, name, price)
    items.put(id, item)
    Some(item)
  }

  def get(id: Long): Option[Item] = items.get(id)

  def update(id: Long, name: String, price: Double): Option[Item] = {
    val item = Item(id, name, price)
    items.replace(id, item)
    Some(item)
  }

  def delete(id: Long): Boolean = items.remove(id).isDefined
}

This implementation stores the data in memory, so it loses everything each time the application restarts! Nevertheless, it is a sufficient business layer basis, letting us focus on the web layer. The implementation uses a concurrent collection to solve concurrency issues. Indeed, as I will explain later, the code called by the controllers must be thread safe.

For Java developers, here is a minimalist implementation of the Shop interface:

import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.atomic.AtomicLong;

new Shop() {

  SortedMap<Long, Item> items = new ConcurrentSkipListMap<>();
  AtomicLong seq = new AtomicLong();

  @Override
  public Collection<Item> list() {
    return new ArrayList<>(items.values());
  }
  @Override
  public Item create(String name, Double price) {
    Long id = seq.incrementAndGet();
    Item item = new Item(id, name, price);
    items.put(id, item);
    return item;
  }

  @Override
  public Item get(Long id) {
    return items.get(id);
  }

  @Override
  public synchronized Item update(Long id, String name, Double price) {
    Item item = items.get(id);
    if (item != null) {
      Item updated = new Item(id, name, price);
      items.put(id, updated);
      return updated;
    } else return null;
  }

  @Override
  public Boolean delete(Long id) {
    return items.remove(id) != null;
  }
};

As previously mentioned, the code called by controllers must be thread safe, hence the use of Java concurrent collections.

 

Bootstrapping a Play application


While it is totally possible to start a Play project from nothing, you might find it more convenient to start from an empty application skeleton and set up the build system to depend on Play so that you can directly focus on the code of your application.

Typesafe Activator (https://typesafe.com/activator) can be used to generate such an empty application skeleton. This tool lists several application templates designed to be used as the project's starting point. Actually, Activator does a bit more than this: it can also compile and run your project and even provide a minimalist development environment with a code editor right in your web browser!

Let's start with a basic Scala or Java application. Download and install Activator by referring to its documentation. Though some templates already support advanced features out of the box, we will begin with a completely empty application and will progressively enhance it with new features (for example, persistence or client-side technologies).

In a *nix terminal, create a new application skeleton by running the following activator command:

$ activator new

You will be asked which application template to use; choose just-play-scala (or just-play-java to create a Java application). Give the application the name shop.

A new directory, shop/, has been created that contains the application's code. Go to this directory and execute the activator run command:

$ cd shop
$ activator run

Activator starts a development HTTP server listening (by default) on port 9000. In your browser, go to http://localhost:9000 to test it; the HTTP server compiles your application, starts it, and processes your HTTP request. If everything works fine, your browser should show a page titled Just Play Scala (or Just Play Java).

You can stop the running application with Ctrl + D.

You can also start Activator without passing it a command name:

$ activator

In such a case, you enter in the project sbt shell, where you can manage the life cycle of your project as in any sbt project. For instance, you can try these commands: clean, compile, run, and console. The last one starts a REPL where you can evaluate expressions using your application's code.

Note

sbt is a build tool for Scala projects. Check out http://www.scala-sbt.org for more details.

Play applications' layout

To explain why you get this result in your browser when you ran the application, let's have a look at the files created by the activator new command. Under the project root directory (the shop/ directory), the build.sbt file describes your project to the build system.

It currently contains a few lines, which are as follows:

name := """shop"""

version := "1.0-SNAPSHOT"

lazy val root = project.in(file(".")).enablePlugins(PlayScala)

The first two lines set the project name and version, and the last line imports the default settings of Play projects (in the case of a Java project, this line contains PlayJava instead of PlayScala). These default settings are defined by the Play sbt plugin imported from the project/plugins.sbt file. As the development of a Play application involves several file generation tasks (templates, routes, assets and so on), the sbt plugin helps you to manage them and brings you a highly productive development environment by automatically recompiling your sources when they have changed and you hit the reload button of your web browser.

Though based on sbt, Play projects do not follow the standard sbt projects layout: source files are under the app/ directory, test files under the test/ directory, and resource files (for example, configuration files) are under the conf/ directory. For instance, the definitions of the Shop and Item types should go into the app/ directory, under a models package.

After running your Play application, try changing the source code of the application (under the app/ directory) and hit the reload button in your browser. The development HTTP server automatically recompiles and restarts your application. If your modification does not compile, you will see an error page in your browser that shows the line causing the error.

Let's have a deeper look at the files of this minimal Play application.

The app/ directory, as mentioned before, contains the application's source code. For now, it contains a controllers package with just one controller named Application. It also contains a views/ directory that contains HTML templates. We will see how to use them in Chapter 3, Turning a Web Service into a Web Application.

The conf/ directory contains two files. The conf/application.conf file contains the application configuration information as key-value pairs. It uses the Human Optimized Configuration Object Notation syntax (HOCON; it is a JSON superset, check out https://github.com/typesafehub/config/blob/master/HOCON.md for more information). You can define as many configuration points as you want in this file, but several keys are already defined by the framework to set properties, such as the language supported by the application, the URL to use to connect to a database, or to tweak the thread pools used by the application.

The conf/routes file defines the mapping between the HTTP endpoints of the application (URLs) and their corresponding actions. The syntax of this file is explained in the next section.

 

URL routing


The routing component is the first piece of the framework that we will look at:

URL routing

The preceding diagram depicts its process. It takes an HTTP request and calls the corresponding entry point of the application. The mapping between requests and entry points is defined by routes in the conf/routes file. The routes file provided by the application template is as follows:

# Routes
# This file defines all application routes (Higher priority routes first)
# ~~~~

# Home page
GET     /                 controllers.Application.index

# Map static resources from the /public folder to the /assets URL path
GET     /assets/*file     controllers.Assets.versioned(path="/public", file)

Apart from comments (starting with #), each line of the routes file defines a route associating an HTTP verb and a URL pattern to a controller action call.

For instance, the first route associates the / URL to the controllers.Application.index action. This one processes requests by always returning an HTTP response with a 200 status code (OK) and an HTML body that contains the result of the rendering of the views.html.index template.

The content of the routes file is compiled by the sbt plugin into a Scala object named Router and contains the dispatching logic (that is, which action to call according to the incoming request verb and URL). If you are curious, the generated code is written in the target/scala-2.10/src_managed/main/routes_routing.scala file. The router tries each route, one after the other, in their order of declaration. If the verb and URL match the pattern, the corresponding action is called.

Your goal is to expose your Shop business layer as a web service, so let's add the following lines to the routes file:

GET    /items        controllers.Items.list
POST   /items        controllers.Items.create
GET    /items/:id    controllers.Items.details(id: Long)
PUT    /items/:id    controllers.Items.update(id: Long)
DELETE /items/:id    controllers.Items.delete(id: Long)

The first route will return a list of items for sale in the shop, the second one will create a new item, the third one will show detailed information about an item, the fourth one will update the information of an item, and finally, the last one will delete an item. Note that we follow the REST conventions (http://en.wikipedia.org/wiki/REST) for the URL shapes and HTTP verbs.

In the controllers package of your code, add the following Items controller that matches the added routes:

package controllers

import play.api.mvc.{Controller, Action}

object Items extends Controller {
  val shop = models.Shop // Refer to your Shop implementation
  val list = Action { NotImplemented }
  val create = Action { NotImplemented }
  def details(id: Long) = Action { NotImplemented }
  def update(id: Long) = Action { NotImplemented }
  def delete(id: Long) = Action { NotImplemented }
}

The equivalent Java code is as follows:

package controllers;

import play.mvc.Controller;
import play.mvc.Result;

public class  Items extends Controller {

  static final Shop shop = Shop.Shop; // Refer to your Shop implementation

  public static Result list() {
    return status(NOT_IMPLEMENTED);
  }
  public static Result create() {
    return status(NOT_IMPLEMENTED);
  }
  public static Result details(Long id) {
    return status(NOT_IMPLEMENTED);
  }
  public static Result update(Long id) {
    return status(NOT_IMPLEMENTED);
  }
  public static Result delete(Long id) {
    return status(NOT_IMPLEMENTED);
  }
}

Each route is mapped by a controller member of type Action (or in Java, a public static method that returns a Result). For now, actions are not implemented (they all return NotImplemented) but you will progressively connect them to your Shop service so that, for instance, the Items.list action exposes the shop list method.

Route path parameters

In our example, in the first route, the URL pattern associated with the controllers.Items.details action is /items/:id, which means that any URL starting with /items/ and then containing anything but another / will match. Furthermore, the content that is after the leading / is bound to the id identifier and is called a path parameter. The /items/42 path matches this pattern, but the /items/, /items/42/0, or even /items/42/ paths don't.

When a route contains a dynamic part such as a path parameter, the routing logic extracts the corresponding data from the URL and passes it to the action call.

You can also force a path parameter to match a given regular expression by using the following syntax:

GET     /items/$id<\d+>    controllers.Items.details(id: Long)

Here, we check whether the id path parameter matches the regular expression \d+ (at least one digit). In this case, the /items/foo URL will not match the route pattern and Play will return a 404 (Not Found) error for such a URL.

A route can contain several path parameters and each one is bound to only one path segment. Alternatively, you can define a path parameter spanning several path segments using the following syntax:

GET     /assets/*file        controllers.Assets.at(path = "/public", file)

In this case, the file identifier captures everything after the /assets/ path segment. For instance, for an incoming request with the /assets/images/favicon.png URL, file is bound to images/favicon.png. Obviously, a route can contain at most one path parameter that spans several path segments.

Parameters type coercion

By default, request parameters are coerced to String values, but the type annotation id: Long (for example, in the details route) asks Play to coerce the id parameter to type Long. The routing process extracts the content corresponding to a parameter from the URL and tries to coerce it to its target type in the corresponding action (Long in our example) before calling it.

Note that /items/foo also matches the route URL pattern, but then the type coercion process fails. So, in such a case, the framework returns an HTTP response with a 400 (Bad Request) error status code.

This type coercion logic is extensible. See the API documentation of the QueryStringBindable and PathBindable classes for more information on how to support your own data types.

Parameters with fixed values

The parameter values of the called actions can be bound from the request URL, or alternatively, can be fixed in the routes file by using the following syntax:

GET     /          controllers.Pages.show(page = "index")
GET     /:page     controllers.Pages.show(page)

Here, in the first route, the page action parameter is set to "index" and it is bound to the URL path in the second route.

Query string parameters

In addition to path parameters, you can also define query string parameters: parameters extracted from the URL query string.

To define a query string parameter, simply use it in the action call part of the route without defining it in the URL pattern:

GET     /items          controllers.Items.details(id: Long)

The preceding route matches URLs with the /items path and have a query string parameter id. For instance, /items and /items?foo=bar don't match but /items?id=42 matches. Note that the URL must contain at least all the parameters corresponding to the route definition (here, there is only one parameter, which is id), but they can also have additional parameters; /items?foo=bar&id=42 also matches the previous route.

Default values of query string parameters

Finally, you can define default values for query string parameters. If the parameter is not present in the query string, then it takes its default value. For instance, you can leverage this feature to support pagination in your list action. It can take an optional page parameter defaulting to the value 1. The syntax for default values is illustrated in the following code:

GET   /items        controllers.Items.list(page: Int ?= 1)

The preceding route matches the /items?page=42 URL and binds the page query string parameter to the value 42, but also matches the /items URL and, in this case, binds the page query string parameter to its default value 1.

Change the corresponding action definition in your code so that it takes a page parameter, as follows:

def list(page: Int) = Action { NotImplemented }

The equivalent Java code is as follows:

public static Result list(Integer page) {
  return status(NOT_IMPLEMENTED);
}

Trying the routes

If you try to perform requests to your newly added routes from your browser, you will see a blank page. It might be interesting to try them from another HTTP client to see the full HTTP exchange between your client and your server. You can use, for example, cURL (http://curl.haxx.se/):

$ curl -v http://localhost:9000/items
> GET /items HTTP/1.1
> User-Agent: curl/7.35.0
> Host: localhost:9000
> Accept: */*
>
< HTTP/1.1 501 Not Implemented
< Content-Length: 0
<

The preceding command makes an HTTP GET request on the /items path and gets an HTTP response with the status code 501 (Not Implemented). Try requesting other paths such as /items/42, /itemss/foo, or /foo and compare the response status codes you get.

Routes are the way to expose your business logic endpoints as HTTP endpoints; your Shop service can now be used from the HTTP world!

 

Building HTTP responses


Actions process HTTP requests and return HTTP responses. So far, you have seen how HTTP requests were routed by the framework to call your application's code. Now, let's see how you can reply with HTTP responses.

An HTTP response has a status code and some optional headers and can be followed by a body. So, to build a response, you have to at least supply a status code.

For instance, your current action definitions return HTTP responses with status code 501 (Not Implemented). Try changing it to Ok (or ok() in Java) and reload the page in your browser (or perform a request with cURL), you should get a response with status code 200 (OK). Play provides helpers to build responses with common status codes. Examples of other predefined statuses are NotFound (notFound() in Java), BadRequest (badRequest() in Java) or InternalServerError (internalServerError() in Java).

More generally, you can build an HTTP response with any status code by using the Status function (or status in Java). For instance, Status(200) (or status(200) in Java) builds an empty response with status 200 and the Content-Length header set to 0.

In addition to a status code, HTTP responses can contain data in a body. For instance, the Application.index action that was provided with the application skeleton returns an HTML document. Alternatively, we can make it return a text document:

val index = Action {
  Ok("Just Play Scala")
}

The equivalent Java code is as follows:

public static Result index() {
  return ok("Just Play Java");
}

In Scala, you can supply a body to your response by calling the apply method of a status; Ok("Just Play Scala") builds an HTTP response with status code 200 and a body that contains "Just Play Scala". Similarly, in Java, you can just pass the response body as a parameter to the status function.

Clients consuming a web service might want (or need) to know the content type of response body data. HTTP responses have a header for this, which is Content-Type. As the value of this header is tied to the type of values you send, Play automatically infers the former from the latter, freeing you from writing redundant code.

In practice, in Scala, writing Ok("foo") builds an HTTP response with a Content-Type header set to text/plain because "foo" has type String. Play infers the right content type by using the type of the value you pass as response body. The type signature of the apply method of the Result type is the following:

def apply[A](a: A)(implicit w: play.api.mvc.Writeable[A]): Result

This means that you can supply a response of type A only if you provide an implicit value of type play.api.mvc.Writeable[A] . The Writeable typeclass actually tells Play which content type to use (and how to serialize it to the HTTP response body). For convenience, Play provides implicit Writeable values for common content types such as JSON, HTML, XML, and plain text. However, if you want to send data of a type that is not currently supported by Play, you have to define the corresponding Writeable instance.

Note

Typeclasses are a feature of the Haskell programming language to achieve ad hoc polymorphism (http://www.haskell.org/tutorial/classes.html). It can be encoded in Scala using parameterized types and implicit parameters.

In Java, the result method helpers, such as ok() and notFound(), are overloaded to support common data types (for example, String, byte[]); ok("foo") builds an HTTP response with a Content-Type header set to text/plain. Data types that are not directly supported must implement the play.mvc.Content interface to be correctly handled by Play. This interface specifies which content type to use and how to serialize the data to the HTTP response body.

Serializing application data in JSON

Now that you know how to build an HTTP response containing a body, your last step to bootstrap your web service consists of returning your business data as a JSON document in the body of your HTTP responses.

Play comes with a rich library for JSON manipulation. Let's start by returning a JSON value in the details action:

import play.api.libs.json.Json

def details(id: Long) = Action {
  shop.get(id) match {
    case Some(item) =>
      Ok(Json.obj(
        "id" -> item.id,
        "name" -> item.name,
        "price" -> item.price
      ))
    case None => NotFound
  }
}

This code tries to retrieve the item in the shop and if found, returns it as a JSON object. The Json.obj function builds a JSON object from a list of name-value pairs. If there is no item with the ID passed as a parameter, the action returns a NotFound response. JSON objects have type JsValue, and Play has a built-in Writeable[JsValue] instance that sets the content type of a JSON response body to application/json.

The equivalent Java code is as follows:

import play.libs.json.Json;

public static Result details(Long id) {
  Item item = shop.get(id);
  if (item != null) {
    return ok(Json.toJson(item));
  } else {
    return notFound();
  }
}

In Java, Play uses the Jackson library (http://jackson.codehaus.org/) to automatically serialize the Item value so that you don't need to explicitly tell how to transform an Item into a JSON object. The Jackson object mapper that performs this task can handle some simple data structures like the Item class, but for more complex data structures (for example, involving cycles or bidirectional associations), you might have to supply your own serialization process by annotating your types with Jackson annotations.

The Scala API does not follow this approach because the Scala language gives convenient mechanisms that allow you to tell how to serialize data without relying on reflection and with minimal boilerplate.

If you call this action from your HTTP client, you will get a response like the following (assuming your shop has an item with the ID 42):

$ curl http://localhost:9000/items/42
{"id":42,"price":4.2,"name":"Play Framework Essentials"}

Similar to the implementation of the details action, here is how you can implement the list action and return the list of the items in the shop as a JSON array. The Java version of the code is as follows:

public static Result list() {
  return ok(Json.toJson(shop.list()));
}

Again, the Json.toJson call delegates the JSON serialization of the list of items to Jackson.

The Scala version of the code is as follows:

val list = Action {
  Ok(Json.arr(shop.list.map(item => Json.obj(
    "id" -> item.id,
    "name" -> item.name,
    "price" -> item.price
  )): _*))
}

We use the Json.arr method to create a JSON array and pass it a collection of JSON objects as a parameter.

You might have noticed that the code defining these JSON objects from the items duplicates the code already written in the details action. Instead, you should isolate the logic corresponding to the serialization of an item into a JSON object as a function and reuse it. Actually, you can do even better; the Play JSON library defines a play.api.libs.json.Writes[A] typeclass that captures the serialization logic for the type A, so you can just write an implicit value of type Writes[Item] and Play will use it when needed. This typeclass has just one method, which is writes(item: Item): JsValue that defines how to transform an Item into a JSON value. The JsValue type is an algebraic data type representing JSON values. For now, you have seen how to define JSON objects (represented by the JsObject type in Play) and arrays (JsArray), using Json.obj and Json.arr, respectively, but there are other types of JsValue such as numbers (JsNumber), strings (JsString), booleans (JsBoolean), and null (JsNull).

Your first reusable JSON serializer for items can be defined and used as follows:

import play.api.libs.json.Writes

implicit val writesItem = Writes[Item] {
  case Item(id, name, price) =>
    Json.obj(
      "id" -> id,
      "name" -> name,
      "price" -> price
    )
}

val list = Action {
  Ok(Json.toJson(shop.list))
}

def details(id: Long) = Action {
  shop.get(id) match {
    case Some(item) => Ok(Json.toJson(item))
    case None => NotFound
  }
}

The implicit value, writesItem, defines the serialization logic for Items. Then, in the list and details actions, we use Json.toJson to transform our items into JSON objects. This toJson method has the following signature:

def toJson[A](a: A)(implicit Writes[A]): JsValue

This means that it can serialize any value of type A if there is an implicit value of type Writes[A] in the implicit scope. Fortunately, Play defines such JSON serializers for common types and can combine them by chaining implicits; this is why Play is able to serialize an Item as well as a List[Item].

Though the code of your writesItem is quite concise, it follows a repetitive pattern. Each field of the Item class is serialized to a JSON field of the same name. Hopefully, Play provides a macro that generates JSON serializers following this pattern, so the previous serializer can be synthesized by just writing the following:

implicit val writesItem = Json.writes[Item]

You might be wondering why automatic generation of JSON serializers is not the default behavior. There are two reasons for this. First, the automatic generation mechanism cannot handle all data types (for example, cyclic data types). Secondly, sometimes you don't want to use the same names for your JSON object fields and your Scala object fields.

 

Reading JSON requests


Now your web service is able to send JSON data representing the application data. However, clients cannot yet create new data by sending JSON requests; the create action is still not implemented. This action should read the JSON data of requests to extract the information required to create a new item, effectively create the item, and return a response telling the client whether its operation succeeded.

The first step consists in defining which information is required to create a new item:

case class CreateItem(name: String, price: Double)

The equivalent Java code is as follows:

public class CreateItem {
  public String name;
  public Double price;
}

The CreateItem data type just glues together the information needed to create a new item: a name and price. The Java version uses public fields so that it can automatically be handled by the Jackson object mapper.

The CreateItem data type is easy to work with in your server-side code, but it means nothing for HTTP clients that only send JSON blobs. So you also have to define a JSON structure corresponding to the CreateItem data type. A simple solution consists of representing a CreateItem instance with a JSON object by mapping each member of the CreateItem type with a member of the JSON object. That is, a JSON object with a member "name" that contains a string value and a member "price" that contains a number value.

The next step consists of defining how to convert a JSON object consistent with this structure into a CreateItem value.

In Scala, similar to the Writes[A] typeclass that defines how to serialize an A value into a JSON object, there is a Reads[A] typeclass that defines how to get an A value from a JSON object. This typeclass has one abstract method:

def reads(json: JsValue): JsResult[A]

The JsResult[A] type represents either a successful conversion, JsSuccess(a), or a unsuccessful conversion, JsError(errors), which contains a list of errors such as missing fields in the JSON source object. So the Reads[A] typeclass tells how to try to convert a JSON value to an A value.

Play provides Reads[A] values for common types such as String, Int, or Double. You can then combine them to define Reads[A] values for more complex types. For instance, you can define a Reads[CreateItem] value that tells how to try to convert a JSON value to a CreateItem value, as follows:

import play.api.libs.json.{__, Reads}
import play.api.libs.functional.syntax._

implicit val readsCreateItem: Reads[CreateItem] = (
  ((__ \ "name").read[String]) and
  ((__ \ "price").read[Double])
)(CreateItem.apply _)

This code combines the Reads[String] and Reads[Double] values using the and combinator. The (__ \ "name") expression is a JSON path referring to a member "name" so that the (__ \ "name").read[String] expression reads the "name" member as String and the (__ \ "price").read[Double] expression reads the "price" member as Double. Finally, these values are passed to the apply method of the CreateItem data type to make a CreateItem instance. Before showing how to use this JSON reader to effectively transform the content of a JSON HTTP request, let's give more details on the process of transforming JSON blobs to values. As our readsCreateItem type is built by combining two subreaders using and, it tries to apply all of them. If all succeed, the obtained values are passed to the CreateItem.apply function to build a CreateItem instance and the reader returns a JsSuccess[CreateItem] value. If one of the subreaders fails, the reader returns a JsError value.

Tip

The and combinator is not a method of Reads[A]. It is available thanks to an implicit conversion imported by play.api.libs.functional.syntax._. This import brings several other combinators, such as or, which succeeds if one of the two subreaders succeeds. These combinators are not specific to the JSON API, and this is why they are defined in a separate package.

In our case, both sub-readers look up a member in a JSON object, according to a path defined by the \ operator. Note that we can define longer paths by chaining the \ operator. Consider, for instance, the following expression that defines a path locating a member "latitude" nested in a "position" member of a JSON object:

__ \ "position" \ "latitude"

Just like the Writes definition, the readsCreateItem definition is quite mechanical. We try to get each field of the CreateItem case class from a field of the same name in the JSON object. Just like the Writes definition, there is a macro automating the work for Scala case classes so that the preceding Reads definition is completely equivalent to the following:

implicit val readsCreateItem = Json.reads[CreateItem]

In Java, the Jackson mapper is used to convert JSON data to POJOs using reflection, so you don't need to provide similar definitions.

Finally, the last step consists of making the create action interpret request content as JSON data and making a CreateItem value from this data:

val create = Action(parse.json) { implicit request =>
  request.body.validate[CreateItem] match {
    case JsSuccess(createItem, _) =>
      shop.create(createItem.name, createItem.price) match {
        case Some(item) => Ok(Json.toJson(item))
        case None => InternalServerError
      }
    case JsError(errors) =>
      BadRequest
  }
}

The equivalent Java code is as follows:

import play.mvc.BodyParser;

@BodyParser.Of(BodyParser.Json.class)
public static Result create() {
  JsonNode json = request().body().asJson();
  CreateItem createItem;
  try {
    createItem = Json.fromJson(json, CreateItem.class);
  } catch(RuntimeException e) {
    return badRequest();
  }
  Item item = shop.create(createItem.name, createItem.price);
  if (item != null) {
    return ok(Json.toJson(item));
  } else {
    return internalServerError();
  }
}

There are three important points to note in the preceding code. First, we tell the create action to interpret the request body as JSON data by supplying the parse.json value to the Action builder (or in Java, by annotating the method with @BodyParser.Of(BodyParser.Json.class)). In Play, the component responsible for interpreting the body of an HTTP request is named body parser. By default, actions use a tolerant body parser that will be able to parse the request content as JSON, XML, or URL-encoded form or multipart form data, but you can force the use of a specific body parser by supplying it as the first parameter of your action builder (or by using the @BodyParser.Of annotation in Java). The advantage is that within the body of your request, you are guaranteed that the request body (available as the body field on the request value) has the right type. If the request body cannot be parsed by the body parser, Play returns an error response with the status 400 (Bad Request).

Secondly, in the Scala version, the second parameter passed to the Action builder is not a block of the Result type, as with previous actions, but a function of type Request[A] => Result. Actually, actions are essentially functions from HTTP requests (represented by the Request[A] type) to HTTP responses (Result). The previous way to use the Action builder (by just passing it a block of type Result) was just a convenient shorthand for writing an action ignoring its request parameter. The type parameter, A, in Request[A] represents the type of the request body. In our case, because we use the parse.json body parser, we actually have a request of type Request[JsValue]; the request.body expression has type JsValue. The default body parser produces requests of the type Request[AnyContent], whose body can contain JSON or XML content as described previously. In Java, Play sets up a context before calling your action code (just after the routing process) so that within a controller, you can always refer to the current HTTP request by using the request() method.

Thirdly, we make the CreateItem value from this request body by calling request.body.validate[CreateItem] (or Json.fromJson(json, CreateItem.class) in Java). The Scala version returns a JsResult value; this type can either be JsSuccess if the CreateItem object can be created from the JSON data (using the Reads[CreateItem] value available in the implicit scope), or JsError if the process failed. In Java, the result is simply null in the case of an error.

Note

In Scala, there is a body parser that not only parses the request body a JSON blob but also validates it according to a reader definition and returns a 400 (Bad Request) response in the case of a failure so that the previous Scala code is equivalent to the following shorter version:

val create = Action(parse.json[CreateItem]) { implicit request =>
  shop.create(request.body.name, request.body.price) match {
    case Some(item) => Ok(Json.toJson(item))
    case None => InternalServerError
  }
}

Validating JSON data

At this point, your clients can consult the items of the shop and create new items. What happens if one tries to create an item with an empty name or a negative price? Your application should not accept such requests. More precisely, it should reject them with the 400 (Bad Request) error.

To achieve this, you have to perform validation on data submitted by clients. You should implement this validation process in the business layer, but implementing it in the controller layer gives you the advantages of detecting errors earlier and error messages can directly refer to the structure of the submitted JSON data so that they can be more precise for clients.

In Java, the Jackson API provides nothing to check this kind of validation. The recommended way is to validate data after it has been transformed into a POJO. This process is described in Chapter 3, Turning a Web Service into a Web Application. In Scala, adding a validation step in our CreateItem reader requires a few modifications. Indeed, the Reads[A] data type already gives us the opportunity to report errors when the type of coercion process fails. We can also leverage this opportunity to report business validation errors. Incidentally, the Play JSON API provides combinators for common errors (such as minimum and maximum values and length verification) so that we can forbid negative prices and empty item names, as follows:

implicit val readsCreateItem = (
  (__ \ "name").read(Reads.minLength[String](1)) and
  (__ \ "price").read(Reads.min[Double](0))
)(CreateItem.apply _)

The preceding code rejects JSON objects that have an empty name or negative price. You can try it in the REPL:

scala> Json.obj("name" -> "", "price" -> -42).validate[CreateItem]
res1: play.api.libs.json.JsResult[controllers.CreateItem] = JsError(List(
    (/price,List(ValidationError(error.min,WrappedArray(0.0)))), 
    (/name,List(ValidationError(error.minLength,WrappedArray(1))))))

The returned object describes two errors: the first error is related to the price field; it has the "error.min" key and additional data, 0.0. The second error is related to the name field; it has the "error.minLength" key and an additional data, 1.

The Reads.minLength and Reads.min validators are predefined validators but you can define your own validators using the filter method of the Reads object.

Handling optional values and recursive types

Consider the following data type representing an item with an optional description:

case class Item(name: String, price: Double, description: Option[String])

In Scala, optional values are represented with the Option[A] type. In JSON, though you can perfectly represent them in a similar way, optional fields are often modeled using null to represent the absence of a value:

{ "name": "Foo", "price": 42, "description": null }

Alternatively, the absence of a value can also be represented by simply omitting the field itself:

{ "name": "Foo", "price": 42 }

If you choose to represent the absence of value using a field containing null, the corresponding Reads definition is the following:

(__ \ "name").read[String] and
(__ \ "price).read[Double] and
(__ \ "description").read(Reads.optionWithNull[String])

The optionWithNull reads combinator turns a Reads[A] into a Reads[Option[A]] by successfully mapping null to None. Note that if the description field is not present in the read JSON object, the validation fails. If you want to support field omission to represent the absence of value, then you have to use readNullable instead of read:

(__ \ "name").read[String] and
(__ \ "price).read[Double] and
(__ \ "description").readNullable[String]

This is because read requires the field to be present before invoking the corresponding validation. readNullable relaxes this constraint.

Now, consider the following recursive data type representing categories of items. Categories can have subcategories:

case class Category(name: String, subcategories: Seq[Category])

A naive Reads[Category] definition can be the following:

implicit val readsCategory: Reads[Category] = (
  (__ \ "name).read[String] and
  (__ \ "subcategories").read(Reads.seq[Category])
)(Category.apply _)

The seq combinator turns Reads[A] into Reads[Seq[A]]. The preceding code compiles fine; however, at run-time it will fail when reading a JSON object that contains subcategories:

scala> Json.obj(
  "name" -> "foo",
  "subcategories" -> Json.arr(
    Json.obj(
      "name" -> "bar",
      "subcategories" -> Json.arr()
    )
  )
).validate[Category]
java.lang.NullPointerException
        at play.api.libs.json.Json$.fromJson(Json.scala:115)
        …

What happened? Well, the seq[Category] combinatory uses the Reads[Category] instance before it has been fully defined, hence the null value and NullPointerException!

Turning implicit val readsCategory into implicit lazy val readsCategory to avoid the NullPointerException will not solve the heart of the problem; Reads[Category] will still be defined in terms of itself, leading to an infinite loop! Fortunately, this issue can be solved by using lazyRead instead of read:

implicit val readsCategory: Reads[Category] = (
  (__ \ "name).read[String] and
  (__ \ "subcategories").lazyRead(Reads.seq[Category])
)(Category.apply _)

The lazyRead combinator is exactly the same as read, but uses a byname parameter that is not evaluated until needed, thus preventing the infinite recursion in the case of recursive Reads.

 

Summary


This chapter gave you an idea of the Play framework, but it contained enough material to show you how to turn a basic application into a web service. By following the principles explained in this chapter, you should be able to implement the remaining Items.update and Items.delete actions.

You saw how to generate an empty Play application skeleton using activator. Then, you saw how to define the mapping between HTTP endpoints and your application entry points and the different ways you can bind your controller action parameters from the request URL. You saw how to build HTTP responses and the mechanism used by Play to infer the right response content type. Finally, you saw how to serve JSON responses and how to read and validate JSON requests.

In the next chapter, you will replace the in-memory storage system with a persistent storage system, and you will see how your Play application can be integrated with existing persistence technologies like JDBC.

About the Author

  • Julien Richard-Foy

    Julien Richard-Foy likes to design code that seamlessly expresses the ideas he has in mind. He likes finding the right level of abstraction, separating concerns, or whatever else that makes the code easy to reason about, to maintain and to grow.

    He works at Zengularity, the company that created the Play framework, and actively contributes to the evolution of the framework.

    He aims at working on technically challenging and innovative projects that have a positive environmental or social impact on the world.

    Browse publications by this author
Book Title
Unlock this full book FREE 10 day trial
Start Free Trial