Insights >Blog

How to use the Play framework, part 2

Daniel Bernal


December 20th, 2018

In my first post about the Play framework, I explained how you can get started with it, and some of the advantages that it offers to developers.

What we are going to do next is implement the business model, which requires more code layers, such as controller, service, dao/repository and many others that you may consider important to make your application work correctly.

Now that we know how the project folders are ordered, we are going to split all the source code into packages so it is organized. First, we are going to create (if they are not created already) some packages and leave the source code structure as the following picture shows:

The structure is defined as:

  • controller: Folder that contains all the different HTTP endpoints (controllers) of the application.
  • converter: Folder that contains all the utility classes that are going to convert the data types from DTO (data transfer object) into business model type classes.
  • dto: Folder that contains all the data transfer object classes of the application.
  • entity: Folder that contains all the classes corresponding to business model entities.
  • repository: Folder that contains all the classes corresponding to database connection and mapping of data from/to database.
  • service: Folder that contains all the classes corresponding to the relationship between the controller and the repository layers.

Once we have already organized our source code layers and packages, we are going to define the dependencies and configuration files in order to have a local embedded database (H2) and also to be able to inject dependencies using Google Guice. Our build.sbt file should look like this:

name := "library"
 
version := "1.0"
 
lazy val `library` = (project in file(".")).enablePlugins(PlayJava)
 
resolvers += "scalaz-bintray" at "https://dl.bintray.com/scalaz/releases"
 
scalaVersion := "2.11.11"
 
libraryDependencies ++= Seq(
javaJdbc ,
guice,
javaWs,
"com.h2database" % "h2" % "1.4.197",
javaJpa,
"org.hibernate" % "hibernate-entitymanager" % "5.1.0.Final"
 
)
unmanagedResourceDirectories in Test <+= baseDirectory ( _ /"target/web/public/test" )

Once we have all the dependencies defined in our configuration file, we are going to proceed to define configuration files, in order to have our embedded database working. In the application.conf file we are going to define an embedded database and a fixed number of connections:

db {
default.driver = org.h2.Driver
default.url = "jdbc:h2:mem:play"
 
# Provided for JPA access
default.jndiName=DefaultDS
}
# Point JPA at our database configuration
jpa.default=defaultPersistenceUnit
fixedConnectionPool = 9
 
# Job queue sized to HikariCP connection pool
database.dispatcher {
executor = "thread-pool-executor"
throughput = 1
thread-pool-executor {
fixed-pool-size = ${fixedConnectionPool}
}
}

Afterwards, inside the conf folder we will create (if it’s not created already) a folder named META-INF and inside of it we are going to create a file named persistence.xml with the following information to define the persistence unit:

<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"
             version="2.1">
 
    <persistence-unit name="defaultPersistenceUnit" transaction-type="RESOURCE_LOCAL">
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
        <non-jta-data-source>DefaultDS</non-jta-data-source>
        <properties>
            <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
            <property name="hibernate.hbm2ddl.auto" value="update"/>
        </properties>
    </persistence-unit>
 
</persistence>

Now that we have set all the configuration files, we proceed to code. We will go from the lowest layer to the highest, therefore we will begin with the repository layer. First of all, we are going to create the ExecutionContext with the following class to specify how our code is going to be executed:

package repository;
 
import akka.actor.ActorSystem;
import play.libs.concurrent.CustomExecutionContext;
import scala.concurrent.ExecutionContext;
 
import javax.inject.Inject;
 
/**
* Custom execution context wired to "database.dispatcher" thread pool
*/
public class DatabaseExecutionContext extends CustomExecutionContext {
 
private static final String name = "database.dispatcher";
 
public DatabaseExecutionContext(ActorSystem actorSystem, String name) {
super(actorSystem, name);
}
 
@Inject
public DatabaseExecutionContext(ActorSystem actorSystem) {
super(actorSystem, "database.dispatcher");
}
 
@Override
public ExecutionContext prepare() {
return super.prepare();
}
 
@Override
public void execute(Runnable command) {
super.execute(command);
}
 
@Override
public void reportFailure(Throwable cause) {
super.reportFailure(cause);
}
}

For this tutorial, we are going to create a small application which will save and load information regarding books, which is the reason we named it Library. Once our context is defined, we can proceed to create an interface which is going to define the behaviors of the repository: find a book and save a book. In this case, we need to define a DO (Domain Object) to save a book, which would be:

package entity;
 
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
 
@Entity
@Table(name="books")
public class BookEntity {
 
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
 
private String name;
 
private String authorName;
 
private Long serialNumber;
 
public Long getId() {
return id;
}
 
public String getName() {
return name;
}
 
public String getAuthorName() {
return authorName;
}
 
public Long getSerialNumber() {
return serialNumber;
}
 
public void setId(Long id) {
this.id = id;
}
 
public void setName(String name) {
this.name = name;
}
 
public void setAuthorName(String authorName) {
this.authorName = authorName;
}
 
public void setSerialNumber(Long serialNumber) {
this.serialNumber = serialNumber;
}
 
}

Now that our entity is defined, let’s define the behaviors:

package repository;
 
import com.google.inject.ImplementedBy;
import entity.BookEntity;
 
import java.util.concurrent.CompletableFuture;
 
@ImplementedBy(JPABookRepository.class)
public interface BookRepository {
 
CompletableFuture findBookBySerial(Long serial);
 
void save(BookEntity bookEntity);
}

Now that our behaviors are set in an Interface, let’s proceed to define them:

package repository;
 
import static java.util.concurrent.CompletableFuture.supplyAsync;
 
import entity.BookEntity;
import play.db.jpa.JPAApi;
 
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import java.util.stream.Stream;
 
import javax.inject.Inject;
import javax.persistence.EntityManager;
 
public class JPABookRepository implements BookRepository {
 
private final JPAApi jpaApi;
private final DatabaseExecutionContext executionContext;
 
@Inject
public JPABookRepository(JPAApi jpaApi, DatabaseExecutionContext executionContext) {
this.jpaApi = jpaApi;
this.executionContext = executionContext;
}
 
private T wrap(Function&lt;EntityManager, T&gt; function) {
return jpaApi.withTransaction(function);
}
 
private BookEntity insert(EntityManager em, BookEntity bookEntity) {
em.persist(bookEntity);
return bookEntity;
}
 
private Stream list(EntityManager em) {
List books = em.createQuery("select b from BookEntity b", BookEntity.class).getResultList();
return books.stream();
}
 
private BookEntity listBySerial(Long serial, EntityManager em) {
BookEntity book = em.createQuery("select b from BookEntity b where b.serialNumber = " + serial, BookEntity.class).getSingleResult();
return book;
}
 
@Override
public CompletableFuture findBookBySerial(Long serial) {
 
return supplyAsync(() -&gt; wrap(em -&gt; {
return listBySerial(serial, em);
}), executionContext) ;
}
 
@Override
public void save(BookEntity bookEntity) {
 
supplyAsync(() -&gt; wrap(em -&gt; insert(em, bookEntity)), executionContext);
}
}

Now that we have defined our repository we are going to create the service and controller layers, which will be simpler. To achieve this, we also are going to need the support classes defined in packages dto, converter. So, all of our new classes are as follows:

package dto;
 
public class BookDto {
 
private String name;
 
private String authorName;
 
private Long serialNumber;
 
public String getName() {
return name;
}
 
public String getAuthorName() {
return authorName;
}
 
public Long getSerialNumber() {
return serialNumber;
}
 
public void setName(String name) {
this.name = name;
}
 
public void setAuthorName(String authorName) {
this.authorName = authorName;
}
 
public void setSerialNumber(Long serialNumber) {
this.serialNumber = serialNumber;
}
 
@Override
public String toString() {
 
return
new StringBuilder().append("Name: ").append(name).append(" ").append("Author Name: ").append(authorName)
.append(" ").append("Serial Number: ").append(serialNumber).toString();
 
}
}
 
package converter;
 
import dto.BookDto;
import entity.BookEntity;
 
public class BookConverter {
 
public static BookEntity fromDto(BookDto bookDto) {
 
BookEntity bookEntity = new BookEntity();
bookEntity.setAuthorName(bookDto.getAuthorName());
bookEntity.setName(bookDto.getName());
bookEntity.setSerialNumber(bookDto.getSerialNumber());
 
return bookEntity;
}
 
public static BookDto toDto(BookEntity bookEntity) {
 
BookDto bookDto = new BookDto();
bookDto.setAuthorName(bookEntity.getAuthorName());
bookDto.setName(bookEntity.getName());
bookDto.setSerialNumber(bookEntity.getSerialNumber());
 
return bookDto;
}
}
 
package service;
 
import com.google.inject.Inject;
import converter.BookConverter;
import entity.BookEntity;
import repository.BookRepository;
import dto.BookDto;
 
import java.util.concurrent.CompletableFuture;
 
public class BookService {
 
@Inject
private BookRepository bookRepository;
 
public boolean saveBook(BookDto bookDto) {
 
bookRepository.save(BookConverter.fromDto(bookDto));
return true;
}
 
public CompletableFuture findBookBySerial(Long serial) {
 
return bookRepository.findBookBySerial(serial);
}
}
 
package controller;
 
import com.google.inject.Inject;
import dto.BookDto;
import play.libs.Json;
import play.mvc.Controller;
import play.mvc.Result;
import service.BookService;
 
import java.util.concurrent.CompletionStage;
 
public class BookController extends Controller {
 
@Inject
private BookService bookService;
 
public Result saveBook() {
 
BookDto bookDto = Json.fromJson(request().body().asJson(), BookDto.class);
if (bookService.saveBook(bookDto)) {
return ok();
}
return internalServerError();
}
 
public CompletionStage findBookBySerial(Long serial) {
 
return bookService.findBookBySerial(serial).thenApplyAsync((x) -&gt; {
return ok(Json.toJson(x));
});
}
 
}

As you may see we are already using Google Guice with the @Inject for the dependency injection. Also, we are using the ActorSystem and CompletableFuture  to make all the requests asynchronous.

Now that we have all the code implemented, we can proceed to verify that our application actually does what we expect it to do: save books and load them.

To test the application, we will use Postman (https://www.getpostman.com/). First, we will create a book and then we will verify it exists in our platform.

  • HTTP Post query with JSON body to save the book, with its corresponding response code to indicate that the element has been saved.

  • HTTP Get request to validate the creation of the book in the application.

There we go, we have our application running, storing and loading books. This is a basic application for learning purposes therefore, there are no error handling nor loggers, etc. If you wish to access all the source code then you can go to: https://github.com/bazzo03/library.

Share

Related posts

See also

Services

Software development

Software testing

Consultancy & innovation

User experience

Industries

Fintech

Media & entertainment

Healthcare

All industries

Insights

Blog

Whitepapers

Webinars

Videos

Why Belatrix?

International presence

Nearshore advantages

Project governance

Agile expertise

Flexible engagement models

Our talent development