In this post I’ll cover two aspects about designing RESTful applications:
- How to design the URLs that can be used to interact with your application, so that the state of the resource can represent the action that needs to be taken.
- How to make RESTful services discoverable in the least brittle manner.
We will use a sample application with the following domain to illustrate this:
A company has Employees that belong to Departments. Each Department has a Manager. Employees can be fired, given raises and re-assigned to different departments.
The user interaction model would be:
- User can create an Employee
- User can create a department
- User can assign an employee to a department.
- User can give an Employee a raise
- User can fire employee
- User can hire employee
- User can make and Employee the manager of a department.
- Delete a department
- Delete an Employee
- Search for a nonexistent employee or department (Error handling)
Behavior can be modeled as:
- Employees can be hired(), fired(), givenRaises(), reassigned()
- Departments can be assignedManager(), dropped()
We will attempt to use the 4 verbs provided by HTTP (GET, PUT, DELETE, POST) , to model all the behavior needed by this application. The attempt is to use the state of the resources to represent the behavior after the action is carried out.
The accompanying sample project can be found here. It will be useful to check it out and reference it as I talk about different parts of this sample project.
REST based services are implemented using Spring MVC extensions/annotations.
First let’s look at the Employee class:
... import lombok.Data; import org.springframework.hateoas.Identifiable; public @Data class Employee implements Identifiable{ public Employee(Long id, String fName, String lName) { this.id = id; this.fName = fName; this.lName = lName; this.status = EmployeeStatus.WORKING; } private final Long id; private final String fName; private final String lName; private double salary; private Long depId; private EmployeeStatus status; }
There is nothing noteworthy in the above POJO except that it implements Identifiable from the Spring HATEOAS project. This ensures that all resources that need to be exposed in a RESTful manner, implement a getId() method.
Also to implement getters and setters, the @Data annotation from the lombok project comes in handy.
Next, check out the very standard repository and service interfaces and implementations. There is nothing REST or HATEOAS specific there.
The Controller and the classes in the com.nayidisha.empdep.rest packages is where the bulk of the work happens.
Starting with the EmployeeController:
@RequestMapping(method = RequestMethod.POST, value = "") ResponseEntity createEmployee(@RequestBody Map body) { Employee emp = managementService.createEmployee(body.get("fName"), body.get("lName")) ; ManagementResource resource = managementResourceAssembler.toResource(emp, null); resource.add(linkTo(EmployeeController.class).withSelfRel()); return new ResponseEntity(resource, HttpStatus.CREATED); }
The POST method is being used to create an employee using standard Spring MVC. So far there is nothing especially noteworthy.
However, the managementResourceAssembler is where we use the HATEOAS API to tell our users what links should be exposed when an employee is created. In short, depending on the state of the resource, appropriate links should be exposed to the clients as shown below:
public ManagementResource toResource(Employee employee, Department department) { ManagementResource resource = new ManagementResource(); resource = this.getEmployeeResource(employee, resource); resource = this.getDepartmentResource(department, resource); resource.departmentList = new ArrayList(); resource.departmentList.add(department); resource.employeeList = new ArrayList(); resource.employeeList.add(employee); return resource; }
and
private ManagementResource getEmployeeResource(Employee employee, ManagementResource resource) { if (employee != null) { if (EmployeeStatus.WORKING.equals(employee.getStatus())) { //working employees can be fired, assigned to departments, made managers of departments, and given raises resource.add(linkTo(EmployeeController.class).slash(employee.getId()).withRel("fire")); } else if (EmployeeStatus.FIRED.equals(employee.getStatus())) { //Fired employees can be transitioned to WORKING status resource.add(linkTo(EmployeeController.class).slash(employee.getId()).withRel("hire")); } else if (employee == null || employee.getStatus() == null){ //Employee not yet created. So client can create an employee resource.add(linkTo(EmployeeController.class).withRel("createEmployee")); } } return resource; }
We can see that links are being created based on what state the Employee resource is in.
Here “rels” are relative links to carry out actions later on. All documentation is anchored to them. So the idea is that we can change the internal representation of our resources as much as we want over the life of the application. The rels should not change. Client applications will then traverse the links node and access the desired “rel” to carry out a business operation as specified in the documentation.
First we will use the rest-shell project to see some output from our sample project:
First create an employee
http://localhost:9080/emp:> post /employees --data "{fName:"Tim", lName:"Rice"}" > POST http://localhost:9080/emp//employees > Accept: application/json < 201 CREATED < Server: Apache-Coyote/1.1 < Content-Type: application/json;charset=UTF-8 < Transfer-Encoding: chunked < Date: Thu, 31 Jan 2013 23:35:50 GMT < { "links" : [ { "rel" : "fire", "href" : "http://localhost:9080/emp/employees/0" }, { "rel" : "createDepartment", "href" : "http://localhost:9080/emp/departments" }, { "rel" : "self", "href" : "http://localhost:9080/emp/employees" } ], "employeeList" : [ { "id" : 0, "salary" : 0.0, "depId" : null, "status" : "WORKING", "fname" : "Tim", "lname" : "Rice" } ], "departmentList" : [ null ] }
Above we see that we can only see all employees using the “self” rel, and fire an employee using the “fire” rel.
But now, how does the client know what parameters to pass to fire the employee. For that the client has to look up documentation for the “fire” rel.
The documentation states that to fire an employee we must issue the PUT verb with the following data map:
{ status:”FIRED” }
http://localhost:9080/emp:> put /employees/0 --data "{status:"FIRED"}" > PUT http://localhost:9080/emp//employees/0 > Accept: application/json < 200 OK < Server: Apache-Coyote/1.1 < Content-Type: application/json;charset=UTF-8 < Transfer-Encoding: chunked < Date: Thu, 31 Jan 2013 23:48:38 GMT < { "links" : [ { "rel" : "hire", "href" : "http://localhost:9080/emp/employees/0" }, { "rel" : "createDepartment", "href" : "http://localhost:9080/emp/departments" }, { "rel" : "self", "href" : "http://localhost:9080/emp/employees" } ], "employeeList" : [ { "id" : 0, "salary" : 0.0, "depId" : null, "status" : "FIRED", "fname" : "Tim", "lname" : "Rice" } ], "departmentList" : [ null ] }
We see that the employee is fired ( if that were ever possible, as Tim Rice is my favorite composer Image may be NSFW.
Clik here to view. ) and that the links are updated to hire him again.
Similar semantics surround the interaction for the other “actions” that are represented by the final state of the resource and the four HTTP verbs.
The one area that I’ve not had a chance to try out is the jsonPath class (also a part of the Spring MVC improvements in Spring 3.2) that allows REST clients to traverse the json returned to get a fix on a certain “rel” in it and from there the URI to access. (It looks really promising! XPath for JSON is a sure winner!)