Per Spilling's Blog

Software Craftsmanship, DDD, Software Architecture and Agile Methods

Getting Started With AngularJS, Jetty and Dropwizard

AngularJS front-end

I had some time between customer engagements these past weeks, so I decided to learn the exiting new AngularJS HTML/JavaScript framework that I had planned to look into for a while already. AngularJS is one of the hottest front-end frameworks at the moment as it makes it much easier than before to create advanced webapps running in the browser.

Dropwizard for back-end RESTful services

To make a realistic little real-world example I decided to find out how it would work together with a Java RESTful services backend. For the backend services I chose to use the excellent Dropwizard framework created by the guys at Yammer. Dropwizard is a best-practice framework that combines a number of more specialized frameworks into a good total package for creating RESTful services. Very good guidelines are provided for how to create high performance RESTful services that are easy to deploy and monitor.

Jetty as servlet container and web server

Jetty is the servlet container and web server used by the Dropwizard framework. It is my favorite servlet container as it is fast, embeddable, and very flexible when it comes to deployment. In this example Jetty is used both for the Dropwizard services and for the AngularJS webapp.

Setting up the skeleton for the AngularJS webapp

The application was split in two Maven modules; angularjs-webapp and dw-server. Here is a quick description of how it was made.

Step 1. Configure Maven & Jetty for the webapp

In order to make the development environment as developer-friendly as possible I created a Maven project where Jetty is used as an embedded web container, making it possible to run the webapp from a static void main() function as is described in Maven + Jetty = Quick WebService and http://www.jamesward.com/2011/08/23/war-less-java-web-apps. The resulting main() function looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class AnguarJSWebApp {
    public static void main(String[] args) throws Exception {
        // The simple Jetty config here will serve static content from the webapp directory
        String webappDirLocation = "src/main/webapp/";

        // The port that we should run on can be set into an environment variable
        // Look for that variable and default to 8080 if it isn't there.
        String webPort = System.getenv("PORT");
        if (webPort == null || webPort.isEmpty()) {
            webPort = "8080";
        }
        Server server = new Server(Integer.valueOf(webPort));

        WebAppContext webapp = new WebAppContext();
        webapp.setContextPath("/");
        webapp.setDescriptor(webappDirLocation + "/WEB-INF/web.xml");
        webapp.setResourceBase(webappDirLocation);

        server.setHandler(webapp);
        server.start();
        server.join();
    }
}

Step 2. The AngularJS application skeleton

I created an application structure similar to the one recommended by the angular-seed project.

Step 3. Create a view template with a menu sidebar

I created a typical view layout template with a menu on the left-side. The ìndex.html file for this looks as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?xml version="1.0" encoding="UTF-8"?>
<html>
<head>
    <link href="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/css/bootstrap-combined.min.css" rel="stylesheet">
    <!--link rel="stylesheet" href="css/foundation.min.css"-->
    <link rel="stylesheet" href="css/raffler.css">
</head>
<body>

<div class="container-fluid" ng-app="helloApp">

    <div class="row-fluid">

        <!--Sidebar content-->
        <div class="span2">
            <div ng-controller="MenuCtrl">
                <div menu></div>
            </div>
        </div>

        <!--Body content-->
        <div class="span10">
            <div ng-view></div>
        </div>

    </div>
</div>

<!-- AngularJS files -->
...

<!-- Project specific JS files -->
<script src="js/menu.js"></script>
...
</body>
</html>

A quick explanation of what this code does:

  • Bootstrap is used for styling.
  • The ng-app="helloApp" statement will bootstrap the AngularJS application
  • The ng-controller="MenuCtrl" statement means that a controller called MenuCtrl will be used to dynamically generate the menu. The code for the MenuCtrl (in the menu.js file) will replace the menu div with a generated menu where the currently selected menu item is highlighted. The menu code looks like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
var sidebarMenu = angular.module('sidebarMenu', ['ngRoute'])
    .config(function ($locationProvider, $routeProvider) {
        // browser reload doesn't work when html5 mode is turned on..
        //$locationProvider.html5Mode(true);
        $routeProvider
            .when('/', {templateUrl: '/partials/hello.html'})
            .when('/sharing-data', {templateUrl: '/partials/sharing-data.html'})
            .when('/filters-and-directives', {templateUrl: '/partials/filters-and-directives.html'})
            .when('/controller-and-directives', {templateUrl: '/partials/controller-and-directives.html'})
            .when('/scope-isolation', {templateUrl: '/partials/scope-isolation.html'})
            .when('/raffler', {templateUrl: '/partials/raffler.html'})
            .when('/tabs', {templateUrl: '/partials/tabs.html'})
            .when('/persons', {templateUrl: '/partials/persons.html'})
            .otherwise({redirectTo: '/'})
    });

sidebarMenu.controller("MenuCtrl", function ($scope, $location, Menu) {
    $scope.menu = Menu;

    /*
     See: http://stackoverflow.com/questions/12592472/how-to-highlight-a-current-menu-item-in-angularjs
     */
    $scope.getClass = function (item) {
        if ($location.path() == item.href) {
            return "active"
        } else {
            return ""
        }
    }
});

sidebarMenu.directive("menu", function () {
    return {
        restrict: "A",
        template: '<ul class="nav nav-list">' +
            '<li class="nav-header">Examples</li>' +
            '<li ng-repeat="item in menu.items" ng-class="getClass(item)"><a href=""></a></li>' +
            '</ul>'
    }
});

sidebarMenu.factory('Menu', function () {
    var Menu = {};
    Menu.items = [
        {
            class: "",
            href: "/#!/index.html",
            name: "Hello world"
        },
        ...
        {
            class: "",
            href: "/#/persons",
            name: "Person Admin"
        }
    ];
    return Menu;
});
  • Finally, the <div ng-view/> will be replaced with the partial template for the current route.

Step 4. Create partials

Create HTML partials for each route defined in the application. In my case I created various small examples based on the excellent http://www.egghead.io tutorial videos and the Raffler example from Ryan Bates.

Create the Dropwizard server module

Step 1. Set up Maven dw-server module

This Maven module was structured according to the guidelines described in the Dropwizard Getting Started Guide.

The Maven pom was set up to create a “fat JAR file” as recommended by Dropwizard, so that it is possible to build and deploy the Dropwizard service in the following simple way:

1
2
> mvn package
> java -jar target/dw-server-1.0-SNAPSHOT.jar server dw-server.yml

Step 2. Create a Dropwizard service

For my example app I created a simple service for administering a person database. It consists of the following parts:

  • EventService - contains the run() method used to initialize and start up the service.
  • EventConfiguration - contains configuration parameters for the service.
  • PersonResource - handles person HTTP requests
  • Person - a simple POJO representing a person
  • PersonDao - the persistence code for a person
  • PersonMapper - a mapper class used to map a person to- and from a database result set

I will only describe a few of these in some detail as the Dropwizard Getting Started Guide describes this very well already.

Create the PersonResource with Jersey

The PersonResource that handles the HTTP requests is implemented with JAX-RS (Jersey), and is quite simple:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
@Path("/persons")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class PersonsResource {
    private PersonDao personDao;

    public PersonsResource(PersonDao dao) {
        personDao = dao;
    }

    @GET
    @Path("/{id}")
    @Timed
    public Person getPerson(@PathParam("id") Integer id) {
        Person p = personDao.findById(id);
        if (p != null) {
            return p;
        } else {
            throw new WebApplicationException(Response.Status.NOT_FOUND);
        }
    }

    @GET
    @Timed
    public List<Person> listPersons() {
        return personDao.getAll();
    }

    @POST
    @Timed
    public void save(Person person) {
        if (person != null && person.isValid()) {
            if (person.getId() != null) {
                personDao.update(person);
            } else {
                personDao.insert(person);
            }
        }
    }

    @DELETE
    @Path("/{id}")
    @Timed
    @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN})
    public void deletePerson(@PathParam("id") Integer id) {
        /**
         * Note: AngularJS $resource will send a DELETE request as content-type test/plain for some reason;
         * so therefore we must add MediaType.TEXT_PLAIN here.
         */
        if (personDao.findById(id) != null) {
            personDao.deleteById(id);
        } else {
            throw new WebApplicationException(Response.Status.NOT_FOUND);
        }
    }
}

Create the PersonDao with JDBI

The PersonDao is implemented with the lean and mean JDBI JDBC framework. Dropwizard recommends using JDBI’s SQL Objects API, which makes it possible to create the DAO classes simply as interfaces, like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@RegisterMapper(PersonMapper.class)
public interface PersonDao {
    @SqlUpdate("create table PERSON (id int auto_increment primary key, name varchar(80), email varchar(80), phone varchar(20))")
    void createPersonTable();

    @SqlUpdate("insert into PERSON (name, email, phone) values (:name, :email, :phone)")
    void insert(@BindBean Person person);

    @SqlUpdate("update PERSON set name = :p.name, email = :p.email, phone = :p.phone where id = :p.id")
    void update(@BindBean("p") Person person);

    @SqlQuery("select * from PERSON where id = :id")
    Person findById(@Bind("id") int id);

    @SqlQuery("select * from PERSON")
    List<Person> getAll();

    @SqlUpdate("delete from PERSON where id = :it")
    void deleteById(@Bind int id);

    @SqlUpdate("delete from PERSON where email = :it")
    void deleteByEmail(@Bind String email);
}

That’s about as simple a DAO as it gets. JDBI is great!

The EventService class

This is the “main” class that is used to configure and run the service and its resources.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
 * This main-class will be used by the start_server.sh script to start the service. It can also be
 * started up in the IDE, just remember to set the correct working directory and provide the expected
 * parameters: server dw-server.yml
 */
public class EventService extends Service<EventConfiguration> {
    ...

    public static void main(String[] args) throws Exception {
        new EventService().run(args);
    }

    @Override
    public void initialize(Bootstrap<EventConfiguration> bootstrap) {
        bootstrap.setName("dw-server"); // name must match the yaml config file
    }

    @Override
    public void run(EventConfiguration conf, Environment env) throws ClassNotFoundException {
        String template = conf.getTemplate();
        String defaultName = conf.getDefaultName();

        //DBIFactory factory = new DBIFactory();
        //final DBI jdbi = factory.build(env, conf.getDatabaseConfiguration(), "postgresql");
        // using in-memory H2 database here for simplicity during development
        JdbcConnectionPool jdbcConnectionPool = JdbcConnectionPool.create("jdbc:h2:mem:test", "username", "password");
        DBI jdbi = new DBI(jdbcConnectionPool);
        PersonDao personDao = jdbi.onDemand(PersonDao.class);
        personDao.createPersonTable();
        seedTheDatabase(personDao);  // add some test data

        env.addResource(new PersonsResource(personDao));
        env.addResource(new HelloWorldResource(template, defaultName));
        env.addHealthCheck(new TemplateHealthCheck(template));
    }
}

I’m using the H2 embedded database here for simplicity during development. Some of the code here is only relevant during development, so the code should be refactored somewhat to make it ready for production.

Note the use of health checks that Dropwizard supports. It makes it easy to add self tests to your service and thus making the solution more robust and dev ops friendly. Dropwizard also has hooks for service monitoring via their own Metrics framework. Very nice!

Make it possible to call the Dropwizard services from the webapp

A deployment diagram of the application configuration is as follows:

Since the webapp and the back-end services have different “origins” the browser will not allow the JavaScript code to directly call the back-end Dropwizard services (see Same-origin Policy). There are several ways to solve this, such as Cross Origin Resource Sharing or making the webapp server act as a proxy for the back-end server. I chose the proxy solution. This is done by adding some Jetty configuration statements to the web.xml file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">
    ...
    <!-- Configure a proxy to the back-end services -->
    <servlet>
        <servlet-name>proxy</servlet-name>
        <servlet-class>org.eclipse.jetty.servlets.ProxyServlet$Transparent</servlet-class>
        <init-param>
            <param-name>ProxyTo</param-name>
            <param-value>http://localhost:9000/</param-value>
        </init-param>
        <init-param>
            <param-name>Prefix</param-name>
            <param-value>/api</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
        <!--async-supported>true</async-supported-->
    </servlet>

    <servlet-mapping>
        <servlet-name>proxy</servlet-name>
        <url-pattern>/api/*</url-pattern>
    </servlet-mapping>

</web-app>

As you can see, the calls to /api/* will be handled by the proxy servlet and re-directed to the back-end server.

Complete the AngularJS webapp

Now we can go back to the AngularJS application skeleton and create the person admin CRUD application. Btw. the application skeleton with sidebar menu, template and partials was not really necessary for the CRUD application but I wanted to learn how to make those things as well with AngularJS.

The person CRUD application should have the following functionality:

  • Show the list of all persons
  • Filter the list of persons
  • Add a new person
  • Delete a person
  • Edit a person

Step 1. Create the HTML for the person CRUD app

I’m not a frontend specialist, so I simply used Bootstrap as best I could, and ended up with the following (persons.html):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<h2>Simple CRUD app using RESTful backend services</h2>

<div ng-controller="PersonsCtrl">

    <div class="control-group">
        <input class="span4" type="text" ng-model="searchText.$" placeholder="Filter the list..">
        <!--
         The following defines two buttons used for either hiding or showing the person form. The personForn.show
         variable in the PersonCtrl determines which button is displayed.
        -->
        <button ng-show="!personForm.show" class="span3 btn" style="margin-left: 0; margin-right: 4px" type="button"
                ng-click="togglePersonForm()">Show person form</button>
        <button ng-show="personForm.show" class="span3 btn" style="margin-left: 0; margin-right: 4px" type="button"
                ng-click="togglePersonForm()">Hide person form</button>
    </div>

    <div ng-show="personForm.show">
        <form class="form-inline" ng-submit="savePerson(personForm.person)">
            <input type="text" class="span3" placeholder="Name" ng-model="personForm.person.name">
            <input type="text" class="span4" placeholder="Email" ng-model="personForm.person.email">
            <input type="text" class="span2" placeholder="Mobile" ng-model="personForm.person.phone">
            <button type="submit" class="btn btn-primary">Save</button>
            <!--
             Note that the clear-button here must have the type attribute set to 'button' otherwise it will
             by default get type=submit. See:
             http://stackoverflow.com/questions/12319758/angularjs-clicking-a-button-within-a-form-causes-page-refresh
            -->
            <button type="button" class="btn" ng-click="clearForm()">Clear form</button>
        </form>
    </div>

    <h3>List of persons</h3>

    <table class="table table-striped table-bordered">
        <thead>
        <tr>
            <th style="width: 100px">Operation</th>
            <th style="width: 20px">Id</th>
            <th>Name</th>
            <th>Email</th>
            <th>Phone</th>
        </tr>
        </thead>
        <tbody>
        <tr ng-repeat="p in persons | orderBy:'name' | filter:searchText">
            <td style="width: 100px">
                <button class="btn" ng-click="editPerson(p)"><i class="icon-edit"></i></button>
                <button class="btn" ng-click="deletePerson(p)"><i class="icon-trash"></i></button>
            </td>
            <td style="width: 20px"></td>
            <td></td>
            <td></td>
            <td></td>
        </tr>
        </tbody>
    </table>
</div>

All the ng-* attributes are AngularJS specific functions and directives. The function calls specified with the ng-click statements are functions implemented by the PersonsCtrl controller.

Step 2. Create the controller for the person admin app

Firstly we need to import and configure the AngularJS modules we want to use. In this case I want to use the following:

The module config part of the looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var myApp = angular.module('persons', ['ngResource', 'ui.bootstrap'], function ($dialogProvider) {
    $dialogProvider.options({backdropClick: false, dialogFade: true});
});

/**
 * Configure the PersonsResource. In order to solve the Single Origin Policy issue in the browser
 * I have set up a Jetty proxy servlet to forward requests transparently to the API server.
 * See the web.xml file for details on that.
 */
myApp.factory('PersonsResource', function ($resource) {
    return $resource('/api/persons', {}, {});
});

myApp.factory('PersonResource', function ($resource) {
    return $resource('/api/persons/:id', {}, {});
});

The $resource is a factory used to create a resource object that lets you call RESTful backend-end services. Impressive how simple it is to use. So now we are ready to create the PersonCtrl controller and using the resources and the dialog function from ui-bootstrap:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
function PersonsCtrl($scope, PersonsResource, PersonResource, $dialog, $q) {
    /**
     * Define an object that will hold data for the form. The persons list will be pre-loaded with the list of
     * persons from the server. The personForm.person object is bound to the person form in the HTML via the
     * ng-model directive.
     */
    $scope.personForm = {
        show: true,
        person: {}
    }
    $scope.persons = PersonsResource.query();

    /**
     * Function used to toggle the show variable between true and false, which in turn determines if the person form
     * should be displayed of not.
     */
    $scope.togglePersonForm = function () {
        $scope.personForm.show = !$scope.personForm.show;
    }

    /**
     * Clear the person data from the form.
     */
    $scope.clearForm = function () {
        $scope.personForm.person = {}
    }

    /**
     * Save a person. Make sure that a person object is present before calling the service.
     */
    $scope.savePerson = function (person) {
        if (person != undefined) {
            /**
             * Here we need to ensure that the PersonsResource.query() is done after the PersonsResource.save. This
             * is achieved by using the $promise returned by the $resource object.
             */
            PersonsResource.save(person).$promise.then(function() {
                $scope.persons = PersonsResource.query();
                $scope.personForm.person = {}  // clear the form
            });
        }
    }

    /**
     * Set the person to be edited in the person form.
     */
    $scope.editPerson = function (p) {
        $scope.personForm.person = p
    }

    /**
     * Delete a person. Present a modal dialog box to the user to make the user confirm that the person item really
     * should be deleted.
     */
    $scope.deletePerson = function (person) {
        var msgBox = $dialog.messageBox('You are about to delete a person from the database', 'This cannot be undone. Are you sure?', [
            {label: 'Yes', result: 'yes'},
            {label: 'Cancel', result: 'no'}
        ])
        msgBox.open().then(function (result) {
            if (result === 'yes') {
                // remove from the server and reload the person list from the server after the delete
                PersonResource.delete({id: person.id}).$promise.then(function() {
                    $scope.persons = PersonsResource.query();
                });
            }
        });
    }
}

The code should be fairly self explanatory, but one thing to note is the use of the $promise variable returned from the $resource after a server call. This is used to ensure that the call to update the list of persons is done after the call to save or delete a person has completed. More info about $promise:

Screenshot of the finished person admin webapp

The resulting screenshot looks like this:

Clicking on the edit button for a person in the list will select that person-item and display it in the person form. When editing a field of the person-item the changes will be reflected immediately in the person list via the data-binding feature that AngularJS provides. Very nice!

Conclusion

AngularJS, Jetty, Dropwizard and Maven is a nice combination for creating modern web apps. With the project setup as described here you can run both the webapp and the Dropwizard service directly in an IDE such as IntelliJ IDEA, creating an effective development environment with a short edit-run-debug cycle.

I’m very impressed with AngularJS so far. It creates a good structure for your JavaScript code, and you get some very powerful features out of the box, which would be quite complicated to implement if you couldn’t use AngularJS (IMHO).

The “downside” is that AngularJS is a large framework with lots of functionality, and the documentation is still not the very best. This means that it takes a while to get a good understanding of AngularJS, and quite a lot of googling when you get stuck with something.

From my colleagues who have used AngularJS more extensively I hear that AngularJS does have some performance issues, so this is something to bear in mind when considering to use AngularJS. For relatively straight forward CRUD apps without very high performance requirements it seems to me that AngularJS should be a good choice.

The source code can be found here: https://github.com/perspilling/angularjs-dropwizard-example

Comments