Jump into Java microframeworks, Part 4: Play

17.03.2016
Although Play is known as a microframework, really it's a total web development stack, including a build tool that closely integrates with application provisions like view and persistence support. You installed Play in Part 1, and got a quick tour of its programming basics. Now we'll extend that basic application to explore some exciting capabilities of Play.

As with the other deep dives, we'll begin by adding persistence to the basic app from Part 1. If you don't already have it, use the link below to download the source code for the Play demo app.

Listing 1 has the schema for the Play demo app. Note that we'll be using a MariaDB instance running on localhost.

Our first step is to tell Play how to connect to your database using MySQL. Open up application.conf, and insert the connection properties as I have done in Listing 2. You'll notice I left some comments in there. You'll also see the comments in your configuration file, because Play includes them as an example of connecting to the H2 database. I've used root and password for this example, but you shouldn't do that in a real app. You can use a different RDBMS if you prefer; just check the documentation for the specific connection parameters you'll need.

So far so good -- Play knows how to communicate with the MariaDB instance. Now let's consider our options for getting the data in and out from the app. Ninja had JPA/Hibernate bundled in, so we used that. For Spark we mixed a custom cocktail of DBUtils and Boon JSON. For Play we'll use EBean, a lightweight ORM that is similar in spirit to Hibernate but with lighter configuration. (Note that Play is capable of running a full JPA provider if you need that.)

It's your choice whether to start with configuration or code. Personally, I decide using some combination of celestial alignment and barometric pressure. For this project we'll start with the code, by creating a model class. In Part 1 we created a Group class in order to model a musical group. Now we'll extend the Group class into an Entity bean. Listing 3 shows the modified class.

The Group class in Listing 3 is in the models package, which is in the /app/models path. Note that this path lives at the same level as the /controllers and /views directories. Figure 1 shows the file's placement in Eclipse.

Using standard JPA annotations, @Entity denotes that the Group class is persistent, while @Table(name="groups") alters the name of the table when mapping the class to SQL. We need to do this because group is a reserved name in SQL -- MariaDB won't like it if we try to select * from group.

We also annotate the id field using@Id and @GeneratedValue, because we made that an auto-incremented field in our DB table. In keeping with the JPA convention, the field name will be persisted without any additional annotations.

Next, we want to enable the EBean plugin that came with Play, and we do that in the project/plugins.sbt file. Listing 4 shows the line that you want to ensure is uncommented in plugins.sbt.

We've enabled EBean as a plugin but we still have to tell Play to use it in our project. We do that by adding it to the build definition file, build.sbt. You can see how this is done in Listing 5. Notice the project name is microPlay (comment 1) and that the name is referenced in the line commented as 2.

In line 2 we just add PlayEbean to our enablePlugins call -- simple enough. Also in build.sbt (comment 3), we add the MySQL driver as a dependency (mysql" % "mysql-connector-java" % "5.1.18). This is done by adding the dependency to the libraryDependency Sequence, which is just a Scala collection. The format uses the same semantics but different syntax than a Maven dependency; notice how the group ID, artifact ID, and version are separated by percentage signs.

Finally, in Listing 5, notice the line commented as 4. This line isn't normally commented when Play builds your app, but I observed very poor compile and load times when it was enabled, along with numerous timeouts, so your mileage may vary. The issue is addressed in this StackOverflow question.

We're done with build.sbt, but we're not quite done with configuration. We need to tell EBean where our persistent classes are. To do this, you'll open application.conf and add the line from Listing 6 to the end of the file. Here we're telling the Play EBean plugin that our mapped entities are in the /models directory.

We now have a persistent model class and the infrastructure to use it, but we have no way to get at it. We can address that by mapping a URL in the routes file, as seen in Listing 7.

Next, we'll create a form on the app's index page, index.html.scala, which you might recall from Part 1. Add the code in Listing 8 to the index page.

By setting @routes.Application.createGroup() as the action for the above form, we tell Play to route submitted requests to that endpoint. This is a reverse route -- essentially, we're telling Play to find whatever URL will get us the Application.createGroup() method, and supply that. If you inspect the source of the page you'll see that the action is set to /groups. You can also see this in Figure 2.

Before we can handle the request, we need a createGroup() method on our controller. So, open up app/controllers/Application.java and add the method in Listing 9.

Listing 9 shows the tools we can use in Play to extract the request into an object. The method is a three-liner: from getting the entity, to saving it, to returning a redirect. The Form.form() utility function takes a class (in this case, the Group class), allowing us to bind the request into a new instance. We can then simply call group.save() on the instance, because we've done the work of mapping it via EBean. Note that we're dealing less with the ORM solution in this case than we would with a full JPA lineup; we don't ever directly handle or need to inject an EntityManager. Finally, the redirect allows us to again use a reverse route to forward on to the routes.Application.index() handler.

So far we've been developing a pretty traditional web application. The difference between that and a single-page AJAX-style REST-submitted app is just a few lines of JavaScript.

Look again at the routes file and notice this line:

This maps GET requests to the /assets URL to the built-in controller called Assets. It uses the versioned method, routing them via the first argument to /public. Versioned means that these assets will be tagged with a cache-busting string when a new version is deployed in production. As a result, clients will always get the most recent version. (Note that the second argument, file:Asset, is required for versioning to work. Also note that versioning doesn't work in dev mode!)

For this solution to work, our JavaScript files need to be in the /public folder. Fortunately, Play already has a /javascripts folder, which includes a friendly hello.js to get us started. We'll use this script shortly. In Figure 3 you can see for yourself where this folder and file are in the Eclipse navigator.

The first thing we'll do is add jQuery to the application dependencies. We could paste in jQuery to /public, or reference a public CDN. An easier option is to pull the dependency into our project using WebJars. Like Ninja, Play has built-in support for WebJars. To add jQuery, we'll modify our build.sbt to include it, as seen in Listing 10. Just for fun, I've used the bleeding-edge (as of this writing) jQuery version in Alpha (3.0.0-alpha1).

Notice that I actually added two new dependencies: the Play WebJar library, and a reference to the jQuery webjar.

Now we just need to reference the jQuery webjar from our HTML header. Listing 11 shows how to do that. We'll add the line from Listing 8 to main.html.scala. Just make sure to put the line before the hello.js include (because hello.js is going to reference jQuery here in just a moment).

In Listing 11 we use Play's support for WebJar (added in Listing 7) to refer to the jQuery library. Lastly, we need to map a route for it in routes. We only have to do this the first time we set up WebJar. Listing 12 shows the route we want.

To find the WebJar you need, go to http://www.webjars.org/ and type in what you're looking for. Then, select your build tool and cut-and-paste the dependency string into your build file. Figure 4 has a screenshot of the process.

Now jQuery is available to our front-end. We also want to include a small jQuery plugin, serializeObject, which will help us with the form submit. Copy the source of that GitHub project into a /public/javascripts/serializeObject.js file, placing it right next to hello.js in your javascripts folder. Also include it in the main.html.scala file (along with hello.js), as shown in Listing 13.

Now we'll add our own JavaScrip, using hello.js as a base. Start by adding the JavaScript in Listing 14 to hello.js.

Next return to index.html.scala and make the form look like what you see in Listing 15. We're adding a couple of IDs to elements in the form, which our hello.js script will use. In Listing 15, we attach an onClick handler to #addGroupButton, then use #groupForm to get the data from the form. Notice that we also remove the action for the form, since we don't need it anymore.

Now when a user clicks Add Group, the application will POST/submit a JSON object with the form data to /group. The JSON will look something like: {"name": "The Police"}.

Next we'll change the route for /group POST. The script in Listing 16 (the route file) should replace the old /group mapping.

Finally, we'll create the new createGroupJSON() method, as in Listing 17.

Notice that Play is smart enough that we can still use the Form.form(Group.class).bindFromRequest() handler to get our Group instance from the JSON request body. We can call save() on the instance just as we did before. This will be followed by returning a status 200 with the ok() call, sending a simple JSON response body with a message field.

So, we can create groups and save them to the database -- success! Now let's see how we could render out the existing groups. We'll start by adding a route, as shown in Listing 18.

Our loadGroups handler shows how you can utilize the EBean query API:

In Listing 19, Ebean exposes a simple find method for querying entities by class type. We can then take the List and convert it to JSON, with Play's built-in JSON library. If you hit http://localhost:9000/groups with a GET request, you'll now see a JSON-formatted list of the existing groups. Before we move on, let's explore another way to handle querying in Play.

Adding a static Finder member to the Group class is a common querying pattern in Play, as shown in Listing 20.

Returning to the loadGroups() method on the application controller, we would change it to look like Listing 21.

Now we are set up to use the static Finder to access the list of groups.

It's now a fairly simple exercise to render the groups to the UI. First, add a div to the index.html.scala page, like so: <div id="groups"></div>. Next. make the additions from Listing 22 to your hello.js.

When the index page is first opened, the loadGroups method will execute, and some basic jQuery will generate an HTML list. That list will display the results of the JSON-formatted data from the GET /groups service. Whenever a group is added, the list will be refreshed to reflect the changes.

Nothing can stop us now -- we can save and list everything from ABBA to Zappa!

Between the setup in Part 1 and the simple programming exercise in this article, you should have a pretty good grasp of incorporating persistence, assets, views, and JSON in your Play-based applications. Play offers a lot of built-in functionality, and this tour gave us a sense of how things work in the Play idiom. Overall, Play is the most intensive of the Java microframeworks to get into, offering a full-stack alternative to standard Java tooling.

The learning curve is steeper with Play, but you get more integrated power at your beck and call -- just remember that adding EBean was a matter of turning on a plugin. You'll find that other features have similar support, such as Play's security plugin.

Play is a no-brainer if you're a fan of Scala, and it's an overall solid Java framework. You just need to be willing to trade some of your standard Java mindset and tooling for a slightly different programming paradigm.

(www.javaworld.com)

By Matthew Tyson