Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

REST + MongoDB + HATEOAS?

DZone's Guide to

REST + MongoDB + HATEOAS?

We take a look at using MongoDB and HATEOAS to create a REST web service that achieves the third level of Richardson's Maturity Model.

· Integration Zone ·
Free Resource

SnapLogic is the leading self-service enterprise-grade integration platform. Download the 2018 GartnerMagic Quadrant for Enterprise iPaaS or play around on the platform, risk free, for 30 days.

REST?

Recently I had an interesting experience while implementing HATEOAS to a REST web service and I was also lucky enough to get to try out a NoSQL database named MongoDB, which I found really convenient for a lot of different cases where you don't need to manage transactions. So today I'm going to share with you this experience, maybe some of you are going to learn something new maybe not, but still, you'll get a refresher on what you already know.

So, first of all, we're going to introduce REST, and slowly we'll get to HATEOAS and MongoDB. So what is exactly REST?

As the World Wide Web Consortium states, REST is:

 "… a model for how to build Web services [Fielding]. The REST Web is the subset of the WWW (based on HTTP) in which agents provide uniform interface semantics -- essentially create, retrieve, update, and delete -- rather than arbitrary or application-specific interfaces, and manipulate resources only by the exchange of representations..."

Okay, now that we know what REST is, I'm going to list a brief description of all the constraints that Roy Fielding mentioned in chapter five of his dissertation:

  • Client-Server – Implement your service in such way that you'll separate the user interface concerns (client gets portability) from the data storage concerns (server gets scalability).
  • Stateless – Implement the communication between client and server in such a way that when the server is processing the request it never takes advantage of any information that is stored in the server context and all the information related to sessions are stored on the client.
  • Cache – When the response to a request can be cached (implicitly or explicitly) the client should get the cached response.
  • Uniform Interface – All REST services should rely on the same uniform design between the components, interfaces should be decoupled from the services that are provided.
  • Layered System – The client never knows whether they're connected directly to the server or to some intermediary servers along the way. For example, a request can go through a proxy which has the functionality of load balancing or shared cache.

Richardson Maturity Model

Image title

Figure 1 – Levels of the Richardson Maturity Model

As Martin Fowler says, this model is a, "A model (developed by Leonard Richardson) that breaks down the principal elements of a REST approach into three steps. These introduce resources, HTTP verbs, and hypermedia controls."

I'm going to give you a short description of these levels:

  • The Swamp of POX – There is just one resource and one request method POST and a single way of communicating – XML.
  • Resources – We stick to the POST method, but we're getting more resources that we can address.
  • HTTP verbs – Now for appropriate cases (resources) we're using other HTTP methods like GET or DELETE. Usually, CRUD operations are implemented here.
  • Hypermedia Controls – HATEOAS (Hypertext As The Engine Of Application State), you should provide the client a start link for using your service and, after that, each response should contain hyperlinks to other possibilities of your service.

Now that we know what REST is and we've covered its Maturity Model, I'm going to introduce you briefly to a NoSQL database, MongoDB, and, after that, we'll get to the demo!

Why HATEOAS?

Firstly let's point out that REST is not easy and nobody who really understands what REST is said that it's easy. Usually, for small services that are not going to grow or change in the near future, it's more than great if you achieved level 2, HTTP Verbs.

What about big services which are going to grow? A lot of people will say that it's okay if you just do level 2. Why? Because HATEOAS is one of the things that make REST complex, it's difficult, if you really want to get its advantages you have to write more code on the client – handle errors, how resources are interpreted, how provided links are analyzed and server – constructing comprehensive and useful links, etc. Let's look at some of the benefits of HATEOAS :

  • Usability – client developers can effectively use, learn about, and explore your service by following the links that you provide. Also, they can imagine the skeleton of your project.
  • Scalability – clients which are following the provided links instead of constructing them don't depend on the service's code changes.
  • Flexibility – providing links for older and newer versions of the service allows you to easily interoperate with the old version-based clients and with the new version-based ones.
  • Availability – clients that rely on HATEOAS never should worry about new versions or code changes on the server like the hardcoded ones.
  • Loose coupling – HATEOAS promotes loose coupling between client and server by assigning the responsibility to build and provide links just to the server.

NoSQL? MongoDB?

So what are NoSQL databases? Derived from the name 'non-SQL' or 'non-Relationary' these databases are not using SQL-like query languages and are often called structured storage. These databases have existed since 1960 but were not that popular till now, when some big companies like Google and Facebook started to use them. The most notorious advantages are freedom from a fixed set of columns, joins, and SQL-like query languages. Sometimes the name NoSQL may refer to 'not only SQL' to assure you that they may support SQL. NoSQL databases are using data structures like key-values, wide columns, graphs, or documents and may be stored in different formats, for example, JSON. 

MongoDB is a schema-less NoSQL database which is document-oriented, thus, as I mentioned above, it provides high performance and good scalability and is cross-platform. MongoDB recommended itself because of the full index support, the easy and clear structure of saved objects in JSON format, amazing dynamic-document query support, the unnecessary conversion of application objects to database objects, and the professional support by MongoDB.

Time to code! Get Ready for MongoDB!

Okay, now we're ready to get to the real deal. Let's build a simple EmployeeManager web service which we'll use to demonstrate HATEOAS with a MongoDB connection.

For bootstrapping our application, we'll use Spring Initializr. We're going to use Spring HATEOAS and Spring Data MongoDB as dependencies. You should see something like what I've got below in Figure 2.

Image title

Figure 2 – Bootstrapping the application

Once you're done, download the zip and import it as a Maven project in your favorite IDE.

First, let's configure our application.properties. To get a MongoDB connection you should deal with the following parameters :

spring.data.mongodb.host= //Mongo server host
spring.data.mongodb.port= //Mongo server port
spring.data.mongodb.username= //Login user
spring.data.mongodb.password= //Password
spring.data.mongodb.database= //Database name

Usually, if everything is freshly installed and you haven't changed or modified any Mongo properties, then you just have to provide a database name (already created one through the GUI).

spring.data.mongodb.database=EmployeeManager

Also, to get the Mongo instance started, I created a .bat which points to the installation folder and to the data folder, it looks like this:

"C:\Program Files\MongoDB\Server\3.6\bin\mongod" --dbpath D:\Inther\EmployeeManager\warehouse-data\db 

Now we're going to quickly create our models. I have two models, Employee and Department, check them down, make sure to have a constructor without parameters, getters and setters and the equals, and hashCode generated, (don't worry, all the code is on GitHub so you can check it later).

public class Employee {
    private String employeeId;
    private String firstName;
    private String lastName;
    private int age;
}
public class Department {
    private String department;
    private String name;
    private String description;
    private List<Employee> employees;
}

Now that we're done with our models, let's create the repositories so we can test our persistence.The repositories look like

public interface EmployeeRepository

extends MongoRepository<Employee, String> {
}

public interface DepartmentRepository

extends MongoRepository<Department,String>{
}

As you noticed, there are no methods, because, as some of you know, the central interface in Spring Data is named Repository on top of which comes the CrudRepositorywhich provides the basic operations to deal with our models. On top of  CrudRepository  we have PagingAndSortingRepository which gives us some extended functionalities to simplify paginating and sorting access. And on the top of all that, in our case, sits MongoRepositorywhich is dealing strictly with our Mongo instance.

So, for our case, we don't need any methods other than the ones that came out of the box, but just for learning purposes I'm going to mention that are two ways in which you can add other query methods:

  • 'Lazy' (Query creation) – this strategy will try to build a query by analyzing your query method's name and deducting the keywords, for example, findByLastnameAndFirstname.

  • Writing the query – nothing special here, for example, just annotate your method with @Query and write your query by yourself. Yeah! As you heard you can write queries in MongoDB too, here is an example of a JSON-based query method

@Query("{ 'firstname' : ?0 }")
List<Employee> findByTheEmployeesFirstname(String firstname); .

At this point, we can already test how our persistence works, we just need a couple of adjustments to our models and by adjustments I mean that we need to annotate some things. Spring Data MongoDB uses a  MappingMongoConverter to map objects to documents and here are some annotations that we're going to use:

  • @Id– field level annotation to point out which of your fields is the identity.
  • @Document – a class level annotation to communicate that this class is going to be persisted in your database.

  • @DBRef – field level annotation to describe the referentiality.

Once we're done we can get some data in our database using the CommandLineRunner which is an interface used to run pieces of code, when the application is fully started, right before the run() method. Below you can take a look at my bean.

@Bean public CommandLineRunner init(EmployeeRepository employeeRepository, DepartmentRepository departmentRepository) {    
  return (args) -> {       
    employeeRepository.deleteAll();       
    departmentRepository.deleteAll();       
    Employee e = employeeRepository.save(new Employee("Ion", "Pascari", 23));       
    departmentRepository.save(new Department("Service Department", "Service Rocks!", Arrays.asList(e)));        
    for (Department d : departmentRepository.findAll()) {          
      LOGGER.info("Department: " + d);       
    }    
  }; 
}

Okay, up until now, we have created some models and we persisted them. Now we need a way to interact with them. As I said, all the code is available on GitHub so I'm going to show you just one domain service (the interface and the implementation).

public interface EmployeeService {     
  Employee saveEmployee(Employee e);      
  Employee findByEmployeeId(String employeeId);      
  void deleteByEmployeeId(String employeeId);      
  void updateEmployee(Employee e);      
  boolean employeeExists(Employee e);      
  List<Employee> findAll();      
  void deleteAll(); 
}

And the implementation

@Service public class EmployeeServiceImpl implements EmployeeService {      
  @Autowired     
  private EmployeeRepository employeeRepository;  

  @Override     
  public Employee saveEmployee(Employee e) {         
    return employeeRepository.save(e);     
  }      
  @Override     
  public Employee findByEmployeeId(String employeeId) {         
    return employeeRepository.findOne(employeeId);     
  }      
  @Override     
  public void deleteByEmployeeId(String employeeId) {         
    employeeRepository.delete(employeeId);     
  }      
  @Override     
  public void updateEmployee(Employee e) {         
    employeeRepository.save(e);     
  }      
  @Override     
  public boolean employeeExists(Employee e) {         
    return employeeRepository.exists(Example.of(e));     
  }      
  @Override     
  public List<Employee> findAll() {         
    return employeeRepository.findAll();     
  }      
  @Override     
  public void deleteAll() {         
    employeeRepository.deleteAll();     
  } 
}

Nothing special to mention here, so we're going to move on to our last puzzle piece – controllers! You can see an implemented controller for the Employee resource below.

@RestController
@RequestMapping("/employees")
public class EmployeeController {

    @Autowired
    private EmployeeService employeeService;

    @RequestMapping(value = "/list/", method = RequestMethod.GET)
    public HttpEntity<List<Employee>> getAllEmployees() {
        List<Employee> employees = employeeService.findAll();
        if (employees.isEmpty()) {
            return new ResponseEntity<>(HttpStatus.NO_CONTENT);
        } else {
            return new ResponseEntity<>(employees, HttpStatus.OK);
        }
    }

    @RequestMapping(value = "/employee/{id}", method = RequestMethod.GET)
    public HttpEntity<Employee> getEmployeeById(@PathVariable("id") String employeeId) {
        Employee byEmployeeId = employeeService.findByEmployeeId(employeeId);
        if (byEmployeeId == null) {
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        } else {
            return new ResponseEntity<>(byEmployeeId, HttpStatus.OK);
        }
    }

    @RequestMapping(value = "/employee/", method = RequestMethod.POST)
    public HttpEntity<?> saveEmployee(@RequestBody Employee e) {
        if (employeeService.employeeExists(e)) {
            return new ResponseEntity<>(HttpStatus.CONFLICT);
        } else {
            Employee employee = employeeService.saveEmployee(e);
            URI location = ServletUriComponentsBuilder
                    .fromCurrentRequest().path("/employees/employee/{id}")
                    .buildAndExpand(employee.getEmployeeId()).toUri();
            HttpHeaders httpHeaders = new HttpHeaders();
            httpHeaders.setLocation(location);
            return new ResponseEntity<>(httpHeaders, HttpStatus.CREATED);
        }
    }

    @RequestMapping(value = "/employee/{id}", method = RequestMethod.PUT)
    public HttpEntity<?> updateEmployee(@PathVariable("id") String id, @RequestBody Employee e) {
        Employee byEmployeeId = employeeService.findByEmployeeId(id);
        if(byEmployeeId == null){
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        } else {
            byEmployeeId.setAge(e.getAge());
            byEmployeeId.setFirstName(e.getFirstName());
            byEmployeeId.setLastName(e.getLastName());
            employeeService.updateEmployee(byEmployeeId);
            return new ResponseEntity<>(employeeService, HttpStatus.OK);
        }
    }

    @RequestMapping(value = "/employee/{id}", method = RequestMethod.DELETE)
    public ResponseEntity<?> deleteEmployee(@PathVariable("id") String employeeId) {
        employeeService.deleteByEmployeeId(employeeId);
        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
    }

    @RequestMapping(value = "/employee/", method = RequestMethod.DELETE)
    public ResponseEntity<?> deleteAll() {
        employeeService.deleteAll();
        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
    }
}

So, with all the methods implemented above, we positioned ourselves at the 2nd level of Richardson's Maturity Model because we used the HTTP verbs and implemented the CRUD operations. Now we have our means of interacting with the data, and, using Postman, we can retrieve our resources like in figure 3, or we can add a new resource like in figure 4.

Image title

Figure 3 – Retrieving a list of departments in JSON

Image title

Figure 4 – Adding a new employee in JSON

HATEOAS Is Coming!

A vast majority of people stop right at this level because, usually, it's enough for them or for the purpose of the web service, but that's not why we are here. So as I mentioned before a web service that supports HATEOAS or a hypermedia-driven site should be able to provide information on how to use and navigate the web service by including links with some kind of relationships with the responses.

Image title

Figure 5 – Example of road signs

You can imagine HATEOAS as a road sign. While you drive you're being guided by those signs, for example, if you need to get to the airport you just follow the indicators, if you need to get back --- again just follow the indicators and you are always aware of where you're allowed to stay, park, drive, and so on.

Enough talk here, to implement the links that will come with our representations of the resources we have to adjust our models by extending the ResourceSupport to inherit the add() method which will give us a nice option to set values to the resource representation without adding any new fields.

@Document
public class Employee extends ResourceSupport{...}

Now let's get to the link creation and for that Spring HATEOAS provides a Link object to store this kind of information and CommandLinkBuilder to build it.

Let's say that we want to add a link to a GET response for an employee by id.

@RequestMapping(value = "/employee/{id}", method = RequestMethod.GET)
public HttpEntity<Employee> getEmployeeById(@PathVariable("id") String employeeId) {
    Employee byEmployeeId = employeeService.findByEmployeeId(employeeId);
    if (byEmployeeId == null) {
        return new ResponseEntity<>(HttpStatus.NOT_FOUND);
    } else {
        byEmployeeId.add(linkTo(methodOn(EmployeeController.class).getEmployeeById(byEmployeeId.getEmployeeId())).withSelfRel());
        return new ResponseEntity<>(byEmployeeId, HttpStatus.OK);
    }
}

If you notice there is :

  • add() - method to set the link value.
  • linkTo(Class  controller)   a statically imported method which allows for the creation a new ControllerLinkBuilder [1] with a base pointing to a controller class.
  • methodOn(Class controller, Object... parameters) - a statically imported method which creates an indirection to the controller class giving the ability to invoke a method from that class and use its return type.
  • withSelfRel() – a method that finally creates the Link with a relationship pointing by default to itself.

Now a GET will produce the following response :

{
  "employeeId": "5a6f67519fea6938e0196c4d",
  "firstName": "Ion",
  "lastName": "Pascari",
  "age": 23,
  "_links": {
      "self": {
      "href": "http://localhost:8080/employees/employee/5a6f67519fea6938e0196c4d"
    }
  }
}

The response not only contains the employee's details but also contains the self-linking URL where you can navigate.

  • _links represents the newly set value for our resource representation.
  • self  stands for the type of relationship that link is pointing to. In this case, it's a self-referencing hyperlink. It's also possible to have another kind of relationships too, like pointing to another class (we'll see that in a second).
  • href is the URL that identifies the resource.

Now let's say that we want to add links to the GET response for a list of departments, here things are getting more interesting because a department is not pointing just to itself, but to its employees too and the employees are pointing to themselves and to their list as well. So let's take a look at the code.

@RequestMapping(value = "/list/", method = RequestMethod.GET)
public HttpEntity<List<Department>> getAllDepartments() {
    List<Department> departments = departmentService.findAll();
    if (departments.isEmpty()) {
        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
    } else {
        departments.forEach(d -> d.add(linkTo(methodOn(DepartmentController.class).getAllDepartments()).withRel("departments")));
        departments.forEach(d -> d.add(linkTo(methodOn(DepartmentController.class).getDepartmentById(d.getDepartmentId())).withSelfRel()));
        departments.forEach(d -> d.getEmployees().forEach(e -> {
            e.add(linkTo(methodOn(EmployeeController.class).getAllEmployees()).withRel("employees"));
            e.add(linkTo(methodOn(EmployeeController.class).getEmployeeById(e.getEmployeeId())).withSelfRel());
        }));
        return new ResponseEntity<>(departments, HttpStatus.OK);
    }
}

So this code will produce the following response :

{
  "departmentId": "5a6f6c269fea690904a02657",
  "name": "Service Department",
  "description": "Service Rocks!",
  "employees": [
    {
      "employeeId": "5a6f6c269fea690904a02656",
      "firstName": "Ion",
      "lastName": "Pascari",
      "age": 23,
      "_links": {
        "employees": {
          "href": "http://localhost:8080/employees/list/"
        },
        "self": {
          "href": "http://localhost:8080/employees/employee/5a6f6c269fea690904a02656"
        }
      }
    }
  ],
  "_links": {
    "departments": {
      "href": "http://localhost:8080/departments/list/"
    },
    "self": {
      "href": "http://localhost:8080/departments/department/5a6f6c269fea690904a02657"
    }
  }
}

Nothing changed except for the fact that there are some links with relationships which aren't named self, these are the other kinds of relationships that I was talking about earlier and they were constructed with

  • withRel(String rel) – a method that finally creates the Link with a relationship pointing to the given rel.

So congratulations! At this point we can say that we reached the 3rd level of Richardson's Maturity Model, of course, we did not because we need many more checks and improvements on our web service like providing the link regarding the state of a resource or any other things, but we almost did it!

You can get the full source code here GitHub

Hope that you liked it.

Download A Buyer's Guide to Application and Data Integration, your one-stop-shop for research, checklists, and explanations for an application and data integration solution.

Topics:
rest ,hateoas ,mongo db ,integration ,rest web services

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}