Home > Articles > Web Development

Developing Scalable Web Applications with Play

  • Print
  • + Share This
  • 💬 Discuss
Like this article? We recommend
You can build upon the foundational understanding of the Play Framework you built in Part 1 of this series, Introduction to Play 2 with Java, to build a real-world Play web application. We explore Play’s support for domain-driven development and use Play’s Scala Templates to build web pages. Now you can create and build a functional Play web application.

The previous article in this series, Introduction to Play 2 for Java, introduced the Play 2 Framework, demonstrated how to set up a Play environment, and presented a simple Hello, World application.  Here I expand upon that foundation to show you how to build a typical web application using Play’s Scala Templates and how Play implements a domain-driven design. We build a simple Widget management application that presents a list of widgets and allows the user to add a new widget, update an existing widget, and delete a widget.

Domain-Driven Design

If you come from a Java EE or from a Spring background, then you’re probably familiar with separating persistence logic from domain objects: A domain object contains the object’s properties, and a separate repository object contains logic for persisting domain objects to and from external storage, such as a database. Play implements things a little differently: The domain object not only encapsulates the object’s properties, but it also defines persistence methods. Play does not force you to implement your applications like this, but if you want your Play application to be consistent with other Play applications, then it is considered a best practice.

Domain objects should be stored, by default, in a “models” package and follow the structure shown in listing 1.

Listing 1. Structure of a Play domain object

public class Widget {
  // Fields are public and getters and setters will be automatically generated and use, 
  //e.g. id = "a" would be translated to setId("a") by Play
  public String id;  
  public String name;

  // Finders are part of the domain object, but are static
  public static Widget findById( String id ) { ...  }
  public static List<Widget> findAll() {...}

  // Update is part of the domain object and updates this instance with a command like JPA.em().merge(this);
  public void update() { ... }

  // Save is part of the domain object and inserts a new instance with a command like JPA.em().persist(this);
  public void save() { .. }

  // Delete is part of the domain object and deletes the current instance with a 
  // command like JPA.em().remove(this);
  public void delete() { ... }
}

Play domain objects define all their fields as public and then Play implicitly wraps assignment calls with set() methods. It makes development a little easier, but it requires a little faith on your part to trust Play will protect your fields for you.

All query methods are defined in the domain object, but they are static, so they do not require an object instance to execute them. You’ll typically see a findAll() that returns a list, findByXX() that finds a single object, and so forth.

Finally, methods that operate on the object instance are defined as instance methods: save() inserts a new object into the data store, update() updates an existing object in the data store, and delete() removes an object from the data store.

For our example we’ll bypass using a database and instead store the “cache” of data objects in memory as a static array in the Widget class itself. Note that this is not a recommended approach, but considering we’re learning Play and not JPA/Hibernate, this simplifies our implementation. Listing 2 shows the source code for the Widget class.

Listing 2. Widget.java

package models;

import java.util.List;
import java.util.ArrayList;

public class Widget
{
    public String id;
    public String name;
    public String description;

    public Widget() {
    }

    public Widget(String id, String name, String description) {
        this.id = id;
        this.name = name;
        this.description = description;
    }

    public static Widget findById( String id ) {
        for( Widget widget : widgets ) {
            if( widget.id.equals( id ) ) {
                return widget;
            }
        }
        return null;
    }

    public static List<Widget> findAll() {
        return widgets;
    }

    public void save() {
        widgets.add( this );
    }

    public void update() {
        for( Widget widget : widgets ) {
            if( widget.id.equals( id ) ) {
                widget.name = name;
                widget.description = description;
            }
        }
    }

    public void delete() {
        widgets.remove( this );
    }

    private static List<Widget> widgets;

    static {
        widgets = new ArrayList<Widget>();
        widgets.add( new Widget( "1", "Widget 1", "My Widget 1" ) );
        widgets.add( new Widget( "2", "Widget 2", "My Widget 2" ) );
        widgets.add( new Widget( "3", "Widget 3", "My Widget 3" ) );
        widgets.add( new Widget( "4", "Widget 4", "My Widget 4" ) );
    }
}

The Widget class defines three instance variables: id, name, and description. Because a Widget is a generic term for a “thing,” its attributes are not important, so these will suffice. The bottom of Listing 2 defines a static ArrayList named widgets and a static code block that initializes it with some sample values. The other methods operate on this static object: findAll() returns the widgets list; findById() searches through all widgets for a matching one; save() adds the object to the widgets list; update() finds the widget with the matching ID and updates its fields; and delete() removes the object from the widgets list. Replace these method implementations with your database or NoSQL persistence methods and your domain object will be complete.

Scala Templates

With our domain object defined, let’s turn our attention to the application workflow. We’re going to create controller actions for the following routes:

  • GET /widgets: Returns a list of widgets
  • POST /widgets: Creates a new widget
  • GET /widget/id: Returns a specific widget
  • POST /widget-update/id: Updates a widget
  • DELETE /widget/id: Deletes a specific widget

Let’s start by reviewing the GET /widgets URI, which returns a list of widgets. We need to add a new entry in the routes file:

GET     /widgets                    controllers.WidgetController.list()

This route maps a call to GET /widgets to the WidgetController’s list() action method. The list() method is simple: It retrieves the list of Widgets by calling Widget.findAll() (that we created in the previous section) and sends that to our list template:

public static Result list() {
    return ok( list.render( Widget.findAll()) );
}

The list template (list.scala.html) accepts a list of Widgets and renders them in HTML, which is shown in Listing 3.

Listing 3. list.scala.html

@( widgets: List[Widget] )

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Widgets</title>
</head>
<body>
    <h2>Widgets</h2>
    <table>
        <thead><th>ID</th><th>Name</th><th>Description</th></thead>
        <tbody>
            @for( widget <- widgets ) {
            <tr>
                <td><a href="@routes.WidgetController.details(widget.id)">  @widget.id </a></td>
                <td>@widget.name</td>
                <td>@widget.description</td>
            </tr>
            }
        </tbody>
    </table>
</body>
</html>

The first line in Listing 3 tells Play that the template requires a List of Widgets and names it “widgets” on the page. The page builds a table that shows the Widget’s id, name, and description fields. The Scala notation for iterating over a collection is the @for command. The iteration logic is backward from Java’s notation but it reads: For all widget instances in the widgets collection do this.

@for( widget <- widgets )

Now we have a widget variable in the body of the for loop that we can access by prefixing it with an at symbol (@):

  • @widget.id: Returns the widget’s ID
  • @widget.name: Returns the widget’s name
  • @widget.description: Returns the widget’s description

We also added a link to the id that invokes the details action, which we review next. Note that rather than using the URI of the details action, we reference it through its routes value:

@routes.WidgetController.details(widget.id)

The details action accepts the ID of the Widget to display, loads the Widget from the Widget.findById() method, fills in a Form, and renders that form using the details template:

    private static Form<Widget> widgetForm = Form.form( Widget.class );

    public static Result details( String id ) {
        Widget widget = Widget.findById( id );
        if( widget == null ) {
            return notFound( "No widget found with id: " + id );
        }

        // Create a filled form with the contents of the widget
        Form<Widget> filledForm = widgetForm.fill( widget );

        // Return an HTTP 200 OK with the form rendered by the details template
        return ok( details.render( filledForm ) );
    }

The WidgetController class defines a Play form (Form<Widget>) object, which will be used to both render an existing Widget object as well as serve as a mechanism for the user to POST a Widget to the controller to create a new Widget object. (The full WidgetController is shown below.) The details() method queries for a Widget and, if it is found, then it fills the form by invoking the widget form’s fill() method, renders the form using the details template, and returns an HTTP OK response. If the Widget is not found, then it returns an HTTP 404 Not Found response by invoking the notFound() method.

The routes file maps the following URI to the details action:

GET     /widget/:id                 controllers.WidgetController.details(id : String)

We use the HTTP GET verb for the URI /widget/:id and map that to the details() method, which accepts the ID as a String.

Listing 4 shows the source code for the details template.

Listing 4. details.scala.html

@(widgetForm: Form[Widget])
@import helper._

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
</head>
<body>

    <h2>Widget</h2>

    @helper.form( action = routes.WidgetController.update() ) {
        @helper.inputText( widgetForm( "id" ), '_label -> "ID" )
        @helper.inputText( widgetForm( "name" ), '_label -> "Name" )
        @helper.inputText( widgetForm( "description" ), '_label -> "Description" )
        <input type="submit" value="Update">
    }

</body>
</html>

The details template accepts a Form[Widget] and assigns it to the widgetForm variable. It uses a helper class to manage the form, so it imports helper._ (Scala notation of import helper.*). The helper class creates a form using its form() method and passes the action URI to which to POST the form body to, which in this case is the routes.WidgetController.update() action. The body of the form is wrapped in braces and inside the form you can see additional helper methods for creating and populating form elements. The inputText() method creates a form text field; it accepts the form field as its first parameter and the label as its second parameter. If the form has values in it, as it does in this case, then the value of the field is set in the form. Finally, the submit button is used to submit the form to update() action method. The resultant HTML for viewing “Widget 1” is the following:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
</head>
<body>

    <h2>Widget</h2>

<form action="/widget-update" method="POST" >

  <dl class=" " id="id_field">
    <dt><label for="id">ID</label></dt>
    <dd>
      <input type="text" id="id" name="id" value="1" >
    </dd>
    <dd class="info">Required</dd>
  </dl>

  <dl class=" " id="name_field">
    <dt><label for="name">Name</label></dt>
    <dd>
      <input type="text" id="name" name="name" value="Widget 1" >
    </dd>
    <dd class="info">Required</dd>
  </dl>

  <dl class=" " id="description_field">
    <dt><label for="description">Description</label></dt>
    <dd>
      <input type="text" id="description" name="description" value="My Widget 1" >
    </dd>
  </dl>
  <input type="submit" value="Update">
</form>

</body>
</html>

The next thing that we might want to do is add a new Widget to our list. Let’s augment our homepage (the list template) to add a form to the bottom of the page that allows our users to add new Widgets. As we did in the previous section, we need to create a Form object and pass it to the template, but this time the Form object should not be populated:

    private static Form<Widget> widgetForm = Form.form( Widget.class );

    public static Result list() {
        return ok( list.render( Widget.findAll(), widgetForm ) );
    }

We already created the widgetForm in the previous example, but it is shown here again for completeness. When we render the list template, we’re going to pass the list of all Widgets as well as the unpopulated widgetForm. Listing 5 adds the new form to the list template.

Listing 5. list.scala.html

@( widgets: List[Widget], widgetForm: Form[Widget] )
@import helper._

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Widgets</title>
</head>
<body>

    <h2>Widgets</h2>

    <table>
        <thead><th>ID</th><th>Name</th><th>Description</th></thead>
        <tbody>
            @for( widget <- widgets ) {
            <tr>
                <td><a href="@routes.WidgetController.details(widget.id)">  @widget.id </a></td>
                <td>@widget.name</td>
                <td>@widget.description</td>
            </tr>
            }
        </tbody>
    </table>

    <h2>Add New Widget</h2>

    @helper.form( action = routes.WidgetController.save() ) {

        @helper.inputText( widgetForm( "id" ), '_label -> "ID" )
        @helper.inputText( widgetForm( "name" ), '_label -> "Name" )
        @helper.inputText( widgetForm( "description" ), '_label -> "Description" )
        <input type="submit" value="Add">

    }

</body>
</html>

Listing 5 updates the expected parameters to include both a List of Widgets as well as a Widget Form and then it imports the form helper classes. The bottom of Listing 5 creates the form using the helper.form() method, with the action directed to the routes.WidgetController.save() action. This form looks a whole lot like the form in Listing 4.

To complete this example, we need to add one more feature: a delete link. Deleting web resources is accomplished by using the DELETE HTTP method, which we cannot simply invoke by adding a link or a form. Instead we need to make the call using JavaScript. Let’s add a new delete method to our routes file:

DELETE  /widget/:id                 controllers.WidgetController.delete(id : String)

When the DELETE HTTP verb is passed to the /widget/id URI, then the WidgetController’s delete() action will be invoked:

    public static Result delete( String id ) {
        Widget widget = Widget.findById( id );
        if( widget == null ) {
            return notFound( "No widget found with id: " + id );
        }
        widget.delete();
        return redirect( routes.WidgetController.list() );
    }

The delete() method finds the Widget with the specified ID and, if it is found, it deletes it by invoking the Widget’s delete() method; if the Widget is not found, then it returns a notFound() response (404) with an error message. Finally, the method redirects the caller to the WidgetController’s list() action.

Listing 6 shows the final version of our list.scala.html template that includes the new delete button.

Listing 6. list.scala.html (final)

@( widgets: List[Widget], widgetForm: Form[Widget] )
@import helper._

@main( "Widgets" ) {

    <h2>Widgets</h2>

    <script>
        function del(url) {
            $.ajax({
                url: url,
                type: 'DELETE',
                success: function(results) {
                    // Refresh the page
                    location.reload();
                }
            });
        }
    </script>

    <table>
        <thead><th>ID</th><th>Name</th><th>Description</th><th>Delete</th></thead>
        <tbody>
            @for( widget <- widgets ) {
            <tr>
                <td><a href="@routes.WidgetController.details(widget.id)">  @widget.id </a></td>
                <td>@widget.name</td>
                <td>@widget.description</td>
                <td><a href="#" onclick="javascript:del('@routes.WidgetController.delete(widget.id)')">Delete</a></td>
            </tr>
            }
        </tbody>
    </table>

    <h2>Add New Widget</h2>

    @helper.form( action = routes.WidgetController.save() ) {

        @helper.inputText( widgetForm( "id" ), '_label -> "ID" )
        @helper.inputText( widgetForm( "name" ), '_label -> "Name" )
        @helper.inputText( widgetForm( "description" ), '_label -> "Description" )
        <input type="submit" value="Add">
    }
}

Listing 6 may look a little strange compared to our previous listings, primarily because it is lacking the HTML headers and footers and the body is now wrapped in a main() method. When you created your project, Play created a main.scala.html file for you that accepts a title String and content as Html. Listing 7 shows the contents of the main.scala.html template.

Listing 7. main.scala.html

@(title: String)(content: Html)

<!DOCTYPE html>

<html>
    <head>
        <title>@title</title>
        <link rel="stylesheet" media="screen" href="@routes.Assets.at("stylesheets/main.css")">
        <link rel="shortcut icon" type="image/png" href="@routes.Assets.at("images/favicon.png")">
        <script src="@routes.Assets.at("javascripts/jquery-1.9.0.min.js")" type="text/javascript"></script>
    </head>
    <body>
        @content
    </body>
</html>

The title is pasted in as the <head> <title> element and the content is pasted inside the <body> of the document. Furthermore, the header imports JQuery for us, which you’ll find in the public/javascripts folder – we need JQuery to simplify our Ajax delete call. If you want to maintain a consistent look-and-feel to your pages, you should add styling information, menus, and other common resources to the main template and then wrap your other templates in a call to main().

Listing 6 adds a delete link to each Widget with the following line:

<td><a href="#" onclick="javascript:del('@routes.WidgetController.delete(widget.id)')">Delete</a></td>

We invoke the del() JavaScript method, which was shown in Listing 6, passing it the route to the WidgetController’s delete() action and the id of the Widget to delete. The del() method uses JQuery’s ajax() method to invoke the specified URL with the specified type (HTTP DELETE verb) and a success method to invoke upon completion (reload the page).

When you’ve completed your code, launch your application using the play command from your application’s home directory and execute run from the play shell. Open a browser to the following URL and take it for a spin:

http://localhost:9000/widgets

You can download the source code for this article here.

Summary

The Play Framework is not a traditional Java web framework and actually requires us to think about developing web applications differently. It runs in its own JVM, not inside a Servlet container, and it supports instant redeployment of applications without a build cycle. When building Play applications you are required to think in terms of HTTP and not in terms of Java.

The “Introduction to Play 2 for Java” article presented an overview of Play, showed how to set up a Play environment, and then built a Hello, World application. Here we built a more complicated Play application that manages CRUD (create, read, update, and delete) operations for a Widget, which uses Play’s domain-driven paradigm, and that better utilizes Play’s Scala templates.

The final article, Integrating Play with Akka, integrates Play with Akka to realize the true power of asynchronous messaging and to show how to suspend a request while waiting for a response so that we can support more simultaneous requests than a traditional Java web application.

  • + Share This
  • 🔖 Save To Your Account

Discussions

comments powered by Disqus