After a short introduction to the Play Framework I made earlier, there is the time to code something. Today I am going to write a simple REST service. There will be some tests of it as well. At the end, I will run it so I can consume it with curl or httpie.
Ok, I should start with some domain I guess. After searching on the Internet for the enormous count of hours I have found it! I decided to base my play around the timeless example of… todos list! Yay! Yes, I know. Complication level of this domain can only be compared with the complexity of the CERN’s Hadron Collider machine. But seriously, I chose this one, so I can focus on technical details of implementing a service and keep things simple as much as possible. 😉
The key domain class is TodoItem
. To make it useful, I set the following properties of it: identifier, name, due date and status (with two possible values: open
and done
). The service, managing todo items, is a very simple CRUD implementation. There are methods to create, edit, remove and read a single item and list all of them. And additionally, two actions allowing to change a status of a given item.
Let’s do some code. HTTP requests in Play are handled by actions. Actions are grouped in controllers. So before creating any action we actually need a controller class. Naming it TodoController
sounds good.
public class TodoController extends Controller { ... }
Every controller in Play Framework extends abstract play.mvc.Controller
class. What does this base class give? The access to current HTTP context, request and response among other functions. Thanks to this we have the easy way to process a received request and create some response. The class extends other useful class, which is play.mvc.Result
. This one serves a bunch of handy factory methods creating various responses with a given HTTP status code and, if applicable, a body content. The other thing is a controller class has to be public. This is required when it comes to routes mapping.
The controller class is empty. This is the time for the first action to be implemented. Here is how we can create a new item on a todo list:
@BodyParser.Of(BodyParser.Json.class) public Response createTodoItem() { TodoItemRequest req = getRequest(); TodoItemId itemId = store(TodoItem.fromRequest(req)); LOGGER.info("Item " + req.getName() + " has been created with id " + itemId.getId()); return created(toJson(itemId)); }
Starting from the top we have the @BodyParser.Of(BodyParser.Json.class)
annotation. It ensures a request’s body is a valid JSON format, so this is just syntactic check. If you are interested what other data formats are supported, look at the content of play.mvc.BodyParser
class.
Now, it would be good to instantiate the body’s content – I assume this is JSON data with two fields: name
and dueDate
. How can we do this? There is Controller.request()
method that gives us access to, among others, headers and body of the received request. Handling of JSON in Play, on the other hand, comes from play-json
plugin that is using Jackson library under the hood. The plugin contains a neat utility – Json
class – that contains useful methods to handle the format. With the class, it is quite simple to create a domain object with it. You need two function calls to do this:
- parse request body with
request().body().asJson()
– that createsJsonNode
object holding the data, - call
Json.fromJson(JsonNode, Class<t>)
to create a domain object of typeT
.
Next, from the request object I create an item and then save it in a repository. It is not important what this repository is. At the moment I use simple HashMap
but in one of the next post I will switch to some database repository.
As the last thing in this method, a response is returned. The response created by calling play.mvc.Results.created()
method has HTTP status code set to 201
. As an argument, it takes the identifier in JSON format. toJson(...)
method, coming from Json
class, creates a JSON structure based on fields of provided object:
return created(toJson(itemId));
There is no magic when it comes to logging in Play application. It is based on the play.Logger
class which provides all needed logging API. It wraps a logging library from the classpath. The default library used one is Logback. More details about logging you can find here.
We have the action defined however it is not exposed to the outside world as an HTTP endpoint. In Play Framework, routes
file from conf
directory contains mappings between HTTP calls and application actions. The creation of a todo item can be mapped with the following line in the file:
POST /todos pl.devthoughts.controllers.TodoController.addItem()
Ok, so far so good. We have the first action and HTTP endpoint exposed. But there is one thing missing actually. Yes, I have written nothing about testing a controller. While there are various ways how this can be achieved, I will focus on routes testing. This is very similar to using MockMvc
from Spring Framework.
Here is how a test of an item creation can look like:
public class RoutesTest extends WithApplication { @Test public void should_create_single_todo_item() { Result creationResult = route(new RequestBuilder() .method(POST) .uri("/todos") .bodyJson(itemData("Do something", "2016-12-04")) ); assertThat(creationResult.status()).isEqualTo(CREATED); DocumentContext creationCtx = JsonPath.parse(contentAsString(creationResult)); assertThat(creationCtx).jsonPathAsString("$.id").isNotEmpty(); Result findingResult = route(new RequestBuilder() .method(GET) .uri("/todos/" + itemId(creationResult)) ); assertThat(findingResult.status()).isEqualTo(OK); DocumentContext findingCtx = JsonPath.parse(contentAsString(findingResult)); assertThat(findingCtx).jsonPathAsString("$.name").isEqualTo("Do something"); assertThat(findingCtx).jsonPathAsString("$.dueDate").isEqualTo("2016-12-04"); assertThat(findingCtx).jsonPathAsString("$.status").isEqualTo("OPEN"); } }
The test class extends play.test.WithApplication
class. It provides a fake Play application, required for testing. Before every test, the application is started (WithApplication.startPlay()
) and stopped when it is finished (`WithApplication.stopPlay()’).
The test itself is a standard JUnit test (@Test
annotation) and is divided into two steps: the creation of an item and looking for it afterwards. Both requests are created with play.mvc.Http.RequestBuilder
class that simplifies this greatly. In the test, I set up basic parameters only like an HTTP method, a URI of the target endpoint and a JSON body (for the first step of the test only). The body of the second request I have created with the code below. It creates an instance of a com.fasterxml.jackson.databind.node.ObjectNode
class and adds two fields to it. With the Json
class I do not have to create JSON from string template or with Jackson’s com.fasterxml.jackson.databind.ObjectMapper
and the code is much cleaner.
private JsonNode itemData(String name, String dueDate) { return Json.newObject() .put("name", name) .put("dueDate", dueDate); }
Next, having the builder instance configured, there is not too much philosophy when it comes to sending a request in a test. The instance is passed to the play.test.Helpers.routes()
method as the parameter. It takes care of building a request based on builder set up and send it to a specified endpoint. The method returns with play.mvc.Result
instance holding a response received from the endpoint. You can run test assertions on it like HTTP status check or verify a content of response’s body. I have used assertj-json library for checking the content of the received responses. It is way more useful than the standard JUnit assertions.
We can start the application using run
or start
task of the Activator. What is the difference? The first one starts our application in dev
mode that offers a continuous compilation of changed sources and provides more details in case of an application error. Moreover, you can use play.editor
configuration parameter (see application.conf
file). It offers the possibility of adding a link to your IDE to the code causing an error. This feature can be quite useful, releasing us from the necessity of scrolling application logs when looking for an error cause. In the case of using start
task, an application is started in prod
mode and the behaviour in terms of presenting errors is different. Sources of an application are compiled once and error messages are more compact. In case of exception we get only information an error occur and it was logged (if there is no error page provided).
Whatever way you chose to start the todo application you call the exposed endpoints. Yes, this means we can create some items in our todo list, and edit them, and read them, and… Wow! This is cool! 😉
What about other endpoints? They are implemented as well but I am not going to describe all of them here. You can check sources of my Play project to see their implementation and how do I test them. When you open TodoController
class you can spot I have used Javaslang library there. While this is not the subject of this post I can say this is a really powerful library providing a lot of functional programming extensions to Java. Definitely worth to try.
OK, this is it. I have written a REST service with Play Framework. What are my findings from this lesson? I see manual handling of JSON data as a significant disadvantage. This is rather redundant step and the task could be done automatically. I miss the way requests are processed in Spring Framework. @BodyParser.Of
annotation is a nice mechanism but this is not enough. Maybe I missed something in Play and need to figure out some mechanism hidden in the depths of the framework’s code. Luckily there is Json
class simplifying serialisation and deserialisation of a data. What I like too is the creation of responses which is really trivial with helper methods provided. And last but not least, a testing of endpoints with Helper.routes(...)
method is done quite good. I cannot write perfectly since there is no out-of-the-box mechanism to make assertions on results’ content.
There is one more thing. I have found a lot of topics in Play Framework to look deeper at. So it seems the series will have more than three or four parts. See you next time!
0 Comments