Jump into Java microframeworks, Part 2: Ninja
I previously explained that while I use the term Java microframework, what I am really referring to is an increasingly popular Java framework style that is similar to MVC, but tightly focused on the minimum elements of a web application: routing requests, rendering views, and processing requests in a controller. Whereas MVC is more directly concerned with separation of concerns (with respect to the model, view, and controllers), RVC-style frameworks prioritize exposing the capabilities of routing, views, and controllers to the developer in a convenient and powerful toolset.
If you don't have Ninja set up in your development environment already, please refer to Part 1 for instructions and programming basics. You will also need the updated source code for the demo application (see below). This tutorial will pick up where we left off, adding persistence, logging, a functional UI, and other features that you would need in a working Ninja application.
Our first step for developing a real-world Ninja application is to get its persistence model set up. We'll start with a model class. The example application generates a list of musicians and bands, so our first model class will be a Person, which could be a musician, producer, or songwriter. Recall that after we built our Maven Ninja archetype in Part 1, the project included the Java Persistence API (javax.persistence) interfaces, as well as Hibernate as the ORM implementation provider. The Person model currently looks like what you see in Listing 1.
Note that the @Entity annotation marks the class as a managed persistent one. The @Id and @GeneratedValue annotations specify the primary key field and the strategy for generating it. In JPA, all members are persisted by default unless marked otherwise; therefore, the name fields will be persisted. I added the @Column notations in order to be able to specify names in a way that is compatible with RDBMS.
Now consider the infrastructure required to make this entity work. We'll need three things:
Ninja supports anything that JPA can, using Hibernate as the provider, so you have your choice of virtually any major SQL database. For the example app I'll use MariaDB, a MySQL fork developed by some of MySQL's original designers. MariaDB installs and operates much like MySQL.
Once you've selected and installed a database, add a schema called ninja_app using the create command like so: create database ninja_app. Don't forget to also add your chosen database connector to your pom.xml! In this case I added the mysql-connector-java dependency.
Next, we'll include a JPA configuration file, so our app knows how to connect to the database.
Listing 2 displays the contents of our persistence.xml file, and reveals itself to be a typical JPA config.
The code in Listing 2 is a mostly generic configuration, targeting a MySQL instance. The key part for us is the configuration profile (aka persistence-unit) named devdb. Also note that the transaction-type is RESOURCE_LOCAL, meaning we're using local transactions.
Ninja allows you to select the configuration values for different environments. Supported environments are dev, prod, and test. Left to default, the environment resolves to production. You can select all configuration values with this mechanism, including things like the persistence unit and logging, which I'll demonstrate in a bit.
As an example, you could create different persistence units in your persistence.xml and reference them in application.conf, as shown below. Notice the %<environment name> notation for referencing the environments.
The last piece of the puzzle is to set the active environment. If you don't set it yourself, it will resolve to prod; otherwise, you can set the ninja.mode system property to select any of the supported environments.
Next we'll connect our persistence model to Ninja. Open the /src/main/java/confg/application.conf file and append Listing 3.
Here we are telling Ninja what URL, username, and password to use, along with the persistence_unit to use. Remember that we defined the persistence unit with devdb in Listing 2.
Next we'll save a Person. We'll begin by mapping a URL, which will be a REST endpoint. URL mapping in Ninja happens in the Routes.java file, as shown in Listing 4.
In Part 1 we developed a simple ApplicationController. Now we'll revisit that in order to add a createPerson() method. This will give us a view into how Ninja interfaces with the JPA infrastructure. Listing 5 has the additions for the controller.
Recall that Ninja uses Guice for dependency injection and persistence management. Here you can see it in action. Note the two @Inject annotations, which let us wire in objects by type: First, we inject Provider<EntityManager>, which provides an implementation of javax.persistence.EntityManager -- the main entry point for Guice's persistence support. Second, we inject a Router, which will let us take advantage of Ninja's programmatic routing.
Finally, take a look at the createPerson() method. The @Transactional annotation is very important! It tells Guice that the method should be instrumented for persistence. Without it, nothing interesting will happen. Our next task is to get an instance of EntityManager and call persist() on our Person object.
But wait, how did we get a Person instance
When Ninja detects a request, it will automatically populate the handler argument with appropriate values. In this case, you'll recall from Part 1, we want to send a JSON request body in our POST request. Besides the JSON itself, we need a content-type header for application/json. Without that header, Ninja will not populate the argument.
To test things out, I'll use the Postman app, which I highly recommend for simulating API requests. Using Postman, we can generate a POST request with the body and headers shown in Listing 6.
When the request arrives at createPerson(), Ninja will notice the content type header is JSON. It will then stuff the appropriate values from the body of the request into the Person object, which is the argument to our createPerson method. Ninja is transparently converting from JSON to a Java object based on the content header. Pretty convenient.
You're almost ready to hit Send on the PostMan UI and watch it work! Before you do that, though, you will need a table in your database to receive the object data. Below is the schema DDL.
Now try sending the POST request to /person. When you check the database (select * from person), you should find the new Person waiting for you.
Before we conclude this quick introduction to persistence in Ninja, take a look back at Listing 5. I'll highlight how we handled the response there, and introduce you to some Ninja routing capabilities.
In the last line of Listing 5, you'll see return Results.json().render("{}");. That line sends an HTTP 200 OK response back with an empty JSON body. That works for the AJAX design of our demo app, but say you wanted to redirect the request to another endpoint, passing in everything as a fresh request As an example, say you wanted to do some processing in the createPerson() method, and then redirect the request to the index() handler. The handler would then be enabled to return a response as if it were handling the request.
For that use case you'd need what is called a reverse route: reverse because you go from the handler method to get the route. To do this, we would replace our return value with:
which would achieve a redirect to /. The router.getReverseRoute method takes the handler endpoint and discovers the route URL, and Result.redirect issues the redirect for us.
You can inject the router object that holds getReverseRoute() into your controllers as shown in Listing 8. Just set a member on the Controller class, with the Router type, and use the Guice @Inject annotation. Ninja will then set its own router.
You'll discover the true power of reverse routing when you need to forward a request to a destination in your application that uses URL and/or query parameters. The redirected route resolves based on the given request parameters, and the resulting handler will receive them just like a normal request. Furthermore, you can use the dispatching method to set these parameters; just use the third parameter of the getReverseRoute() method, like so:
In this case, parameterMap is an instance of java.util.Map. Map keys will match to URL parameters if it finds any; otherwise, they'll be treated as query parameters.
Ninja uses Logback for logging, and you can configure it as normal. Specify a Logback file in application.conf, as shown below. Remember, too, that you can select an environment if you wish, using the percentage notation shown in the "Selecting an environment" sidebar:
You can locate this file in the filesystem or on the classpath. The Maven archetype we originally used to generate the project will have placed a default at /src/main/java.
In order to debug your code, you need to be able to step into it. To set yourself up for debugging with Maven, go into the project and run mvnDebug jetty:run, then attach a remote debugger. In Eclipse, go to Run->Debug Configurations...->Remote Java Applications->New, where you should be able to accept the defaults of Socket Attach, localhost:8000. I'll say more about testing later in the article.
Next we'll add a user interface for creating a person. To build a real UI, we'll need control over the application's JavaScript, CSS, and any images or assets that it serves. Ninja gives us this control via the AssetsController class. This controller has already been mapped for us in the Maven archetype-generated project. You can see it by looking at Routes.java, back in Listing 4, where the first two lines display the built-in Asset routing.
Developers have two options for resolving third-party dependencies in Ninja: straight link or WebJars. I'll demonstrate both, starting with WebJars.
WebJars is a JavaScript dependency management solution for Java, similar to Bower or Browserfy but accessible from inside a Java app. The very nice thing about WebJars is that it enables JVM build tools like Maven and Gradle to pull down dependencies for you. This falls into the category of Really Good Ideas.
Look at /src/main/java/view/defaultLayout.ftl.html and you will see the include in Listing 9, below.
Note that our index.ftl.html page includes defaultLayout.ftl.html, which is where the HTML script and CSS includes happen. I described at the beginning of this article how Freemarker templates (those files ending with ftl.html) are resolved and supplied with data. For the moment, it's enough to remember that index.ftl.html is the template that handles requests to the root / index page.
This include of the Bootstrap CSS resolves to the previously mentioned AssetController, specifically, to the AssetController.serveWebJars() method. Check the pom.xml file to verify that the bootstrap dependency is included via Maven, as shown in Listing 10.
Notice that jQuery is also included, in both the POM and in the default.ftl.html header. If we wanted to upgrade jQuery, we could just increment the version number in both locations. Similarly, if we wanted to add a different framework, like Angular or Backbone, we could look it up at at WebJars.org and add it to the POM and the header.
The include URL for Ninja WebJars found in the HTML file (shown in Listing 9) is different from what you'll find at WebJars.org, since with Ninja it goes through the asset controller routing. Whereas the official URL begins with META-INF/resources/webjars, ours begins with the AssetController mapping of /assets/webjars.
Now let's try using Ninja's support for including dependencies directly. Start by creating a new /js folder under the /assets directory. Add a file called app.js there, and also cut-and-paste in the serializeObject jQuery plugin. Include both of these files as shown in Listing 11.
Now update index.ftl.html with the code in Listing 12. This adds a form to create a Person.
All that remains is to wire up the new form with jQuery. Go to app.js and add the code from Listing 13.
Refresh the browser and you'll be able to create a person; just input a first and last name and hit Add.
To round out our persistence model for the Person class, we'll render the people in the database. Start by creating a getPeople() handler method on ApplicationController, as shown in Listing 14. You'll notice that we're going with a single-page, RESTful-style web app.
In Listing 14 we annotate the getPeople() method with @UnitOfWork, which informs Guice/JPA that the method is instrumented as a non-transactional block. Again, we get the EntityManager, and then use it to create and execute a simple JPQL (Java Persistence Query Language) query for all Person entities. Finally, we render the list of Persons with the JSON helper method.
Next, let's add a simple div with an ID of people, to hold the list of people. Place the div in index.ftl.html, like so: <div id="people"></div>.
All we need now is to add a couple things to App.js, as shown in Listing 15.
Now when the application opens the index page, the list of people will be populated. It will also be updated when a new Person is added via the Add button. This is all done with basic jQuery processing of the JSON returned by the getPeople service.
So far we've covered the CR and D of CRUD -- the create, read, update, and delete functions of an application interface. The update function is easy to extrapolate from examples so far, so I'll leave that as an exercise for you. In a more complex application you'd also want to extract the data access into a DAO layer, but that is beyond the scope of the demo here.
No application framework or development process is complete without infrastructure for testing. Ninja offers a variety of options for testing, including Mockito, which works as well as it does with other frameworks. More interesting is NinjaTest support, which allows for HTTP scraping against a real server. Listing 16 has a simple test of the index route that we defined in Part 1 (router.GET().route("/").with(ApplicationController.class, "index");).
The key here is to extend NinjaTest, which exposes the ninjaTestBrowser dependency. This allows us to make requests to the application, which will be started to host our test. We then can make assertions of the response; you can see this above with the assertion that the "add" string is present in the response.
Ninja also includes the NinjaDocTester class. Descended from NinjaTest, this class gives you all of Ninja's basic testing support, and also lets you generate HTML documentation. Together these two classes make generating automated API documentation a fairly straightforward part of the normal build test.
For more in-depth HMTL testing, Ninja has NinjaFluentLeniumTest, which again will automatically spin up the app instance for you, and run FluentLenium tests (an evolution of Selenium).
We've covered a lot of ground in this short introduction to web application development with Ninja. I focused on persistence, but also addressed routing, debugging, UI development, and testing. Ninja is a capable framework that simplifies these necessities, making it possible to get an application off the ground quickly. Working within Ninja is enjoyable, and doesn't require learning a lot of extraneous details or framework-specific technology. Ninja lets you focus on your application, while providing all the tools that you need in a robust, useful configuration.
Leave a comment below if you have any thoughts about this series, or tell me and JavaWorld's readers about your experience with Ninja development in general.