24 Feb 2015 - Digital Strategy // By Productive Edge Team

Exploring ActiveWeb pure RESTful possibilities

In this article we will explore ActiveWeb functionalities to expose a pure JSON based RESTful API: ActiveWeb "...is a Java framework for rapid development of web applications", and our goal here is to take advantage of its lean approach to implement a simple project and demonstrate adherence to REST principles. We are not providing guidelines or best practices, rather exploring different ways to achieve RESTful compliance in a JavaLite based web application.

The ActiveRest project is available on GitHub and follows the basic structure of an ActiveWeb project, though partially bypassing the view layer in favor of pure data-oriented JSON responses. The idea is to use the framework as-is and at the same time to give the web application a “Microservices” flavor, supporting requirements of the most recent responsive website designs.

In the rest of this article we will provide more details about this experimental implementation, covering the structure of the data layer, REST operations and routing, JSON serialization and finally errors and exception handling.

Data Layer ActiveRest relies on a basic data model with Users and Tasks, and provides CRUD operations for these entities:
  • users(id, name, created_at, updated_at)

  • tasks(id, description, user_id, created_at, updated_at)

The User-Task association has been implemented as a simple one-to-many relationship: a many-to-many relationship would be more meaningful, however, it would also add complexity (e.g. creation of tasks shared between users, tasks existing even if not assigned to users) which is outside the scope of this simple example.

 The persistence layer consists of simple ActiveJDBC model classes (app.models), and uses the DB-migrator maven plugin to populate and version the schemas. Common JDBC properties are then used to create the actual connections, which in turn are made available to the controllers through the default ActiveWeb DBConnectionFilter.

RESTful operations

The application operations are implemented using the two sets of annotations available in Activeweb, to seamlessly map HTTP methods to Java methods:

verb

path

action

used for

GET

/users

index

display a list of all users

GET

/users/new_form

new_form

return an HTML form to create a new user

POST

/users

create

create a new user

GET

/users/{user_id}

show

display a specific user

GET

/users/{user_id}/edit_form

edit_form

return an HTML form to edit a user

PUT

/users/{user_id}

update

update a specific user

DELETE

/users/{user_id}

destroy

delete a specific user

  • app.controllers.TasksController relies on the specific @GET, @POST, @PUT, @DELETE annotations applied to the related action methods, and on custom defined routing described in the next section: in this case new_form and edit_form were skipped on purpose as they require UI interaction that falls outside pure RESTful APIs.

REST urls: routing and links

As previously mentioned both default and custom routings were used for this implementation, and while the UsersController completely relies on the framework facilities for requests mapping through @RESTful, TasksController’s actions are wired leveraging Activeweb custom routing and user/dynamic segments in the app.config.RouteConfig class:

route("/users/{user_id}/tasks/{id}").to(TasksController.class).get().action("show"); route("/users/{user_id}/tasks/{id}").to(TasksController.class).put().action("update"); route("/users/{user_id}/tasks/{id}").to(TasksController.class).delete().action("destroy"); route("/users/{user_id}/tasks").to(TasksController.class).get().action("index"); route("/users/{user_id}/tasks").to(TasksController.class).post().action("create");

 As you can see url segments and method-based routing allow for all the flexibility needed to map HTTP methods and capture path parameters properly: actual values can then simply be retrieved using the framework param() method (e.g. param(USER_ID_TOKEN)).

Simple resources linking has also been provided for the User entities, to comply with the principle of automated discovery and navigation of a well designed REST API. The code relies on the spring-hateoas project for some pre-built facilities, but this dependency is not strictly required for the functionality, and can easily be replaced with ad-hoc implementation.

 Here an example of a returned User resource:

{

   "id": 1,

   "name": "Marta",

   "links": [

       {

           "rel": "self",

           "href": "http://localhost:8080/users/1"

       },

       {

           "rel": "http://localhost:8080/users/1/tasks",

           "href": "rel"

       }

   ]

}

As you can see the resour ce provides the self link, as well as the related (“rel”) link to access the task resources assigned to this User.

 JSON serialization/deserialization

JSON mapping to and from Java objects is achieved relying on ActiveJDBC features, on the Jackson project and on ActiveWeb ordinary views.

 User instances are serialized directly through the instrumented toJson method, being able to select which fields are included:

user.toJson(false, ID_TOKEN, NAME_TOKEN, CREATED_AT_TOKEN)

Additionally a app.utils.JsonHelper class has been defined to allow POJO based mapping through Jackson.

 Finally Task instances are serialized relying on ordinary Activeweb views:  Errors/Exception handling

A specific exception type and global filter have been defined to standardize RESTful related errors handling and return JSON messages in case:

{

   "errors": [

       {

           "code": 400,

           "description": "User not found"

       }

   ]

}

The system default error views have been adjusted as well to result in JSON messages:

Stefano Crespi - Senior Developer
Share
Exploring ActiveWeb pure RESTful possibilities