Building a MongoDB connected REST Service with HATEOAS

February 12, 2018by Ion

  Recently I have had an interesting experience while implementing HATEOAS to a REST web service and I found it quite interesting, also was lucky enough 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 will 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 World Wide Web Consortium states: “… 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 his dissertation, Chapter 5:

  • Client-Server – Implement your service in such a way that you 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 server context and all the information related to sessions is stored on the client;
  • Cache – When the response to a request can be cached (implicitly or explicitly) 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 – Client never knows whether he’s 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

Fig. 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 – Nowadays we’re using other HTTP methods like GET or DELETE for appropriate cases (resources). Usually, these are the CRUD operations;
  • 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 is REST and it’s Maturity Model, I’m going to make a brief introduction into the NoSQL database – MongoDB and after that, right 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 near future it’s more than enough if you achieved level 2, HTTP Verbs.

  What about big services which are going to grow because of some facts? A lot of people will say that it’s okay if you did the level 2. Why? Because HATEOAS is one of the things that make REST complex, it’s difficult, if you really want to feel its advantages you have to write more code, on client – handle errors, how resources are interpreted, how the provided links are analyzed and on server – construct comprehensive and useful links etc. Let’s see what you get if you struggle enough to implement HATEOAS:

  • Usability – client developers can effectively use, learn and explore your service by following the links that you provide. Also they can imagine the skeleton of your project;
  • Scalability – clients which follow the provided links instead of constructing them, don’t depend on the changes in the service code;
  • Flexibility – providing links for older and newer versions of service allows easily to interoperate with old and new client versions;
  • Availability – clients that rely on HATEOAS should never worry about new versions or code changes on the server;
  • 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 don’t use SQL-like query languages and are often called structured storage. These databases exists since 1960, but were not so popular till now, when some big companies like Google and Facebook started to use them. The most notorious advantages are the freedom they offer from a fixed set of columns, joins, and SQL-like query language. Sometimes the name NoSQL may refer to „not only SQL” to assure you that they may support SQL. NoSQL databases use data structures like key-value, wide columns, graphs or documents and may be stored in different formats, for example, JSON. The greatness of NoSQL databases raises depending on the problem that it should solve.

  MongoDB is a schemaless NoSQL database which is document oriented and provides high performance and good scalability, being also cross-platform. MongoDB recommended itself because of the full index support, the easiness and clearness of the structure of the saved objects in a JSON format, amazing dynamic-document query support, unnecessary conversion of application objects to database objects and 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 on which we’ll demonstrate the 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. It should look something like figure 2.

Fig. 2 Bootstrapping the application
 

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

  First, let’s configure our application.properties, for getting 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 yet changed or modified any Mongo properties, then you just have to provide a database name (I 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 FilesMongoDBServer3.6binmongod" --dbpath D:IntherEmployeeManagerwarehouse-datadb

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

[code language=”java”]
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;
}
[/code]

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

[code language=”java”]
public interface EmployeeRepository extends MongoRepository<Employee, String> {}

public interface DepartmentRepository extends MongoRepository<Department,String>{}
[/code]

  As you might notice there are no methods defined in the interfaces defined above, because as some of you know about the central interface in Spring Data named Repository on top of which comes CrudRepository which gives us the basic operations to deal with our models. On top of CrudRepository comes PagingAndSortingRepository which gives us some extended functionalities to simplify paginating and sorting access. And on the top of all in our case stays the MongoRepository which is dealing strictly with our Mongo instance.

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

  • 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 findByTheEmployeesFirstname(String firstname);

  At this point we already can 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 which of your fields is the identity;
  • @Document – a class level annotation to point that this class is going to be persisted in your database;
  • @DBRef – field level annotation to point 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.

[code language=”java”]
@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);
}
};
}
[/code]

  Okay, so far we have some models and we persist 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).

[code language=”java”]
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();
}
[/code]

  And the implementation

[code language=”java”]
@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();
}
}
[/code]

  Nothing special to mention here, so we’re going to get further with our last puzzle piece – controllers! You can check below an implemented controller for the Employee resource.

[code language=”java”]
@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);
}
}
[/code]

  I’m not going to explain in depth what I did there because is pretty obvious and I assume that you already know that. 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 got our way to interact with the data, for example now using Postman we can retrieve our resources like in figure 3, or we can add a new resource like in figure 4.

Fig. 3 Retrieving a list of departments in JSON
 

Fig. 5 Adding a new employee in JSON
 

HATEOAS is cooooming!

  A vast majority of people stop 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.

Fig. 5 Example of road signs
 

  You can imagine HATEOAS as a road signs for you being in a foreign city, 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 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.

[code language=”java”]
@Document
public class Employee extends ResourceSupport{…}
[/code]

  Now let’s get to the link creation and for that Spring HATEOAS provides 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.

[code language=”java”]
@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);
}
}
[/code]

  Here we have:

  • add() – method to set the link value;
  • linkTo(Class<?> controller) – statically imported method which allows creating a new ControllerLinkBuilder with a base pointing to a controller class;
  • methodOn(Class controller, Object… parameters) – 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 request will produce the following response:

[code language=”java”]
{
&amp;quot;employeeId&amp;quot;: &amp;quot;5a6f67519fea6938e0196c4d&amp;quot;,
&amp;quot;firstName&amp;quot;: &amp;quot;Ion&amp;quot;,
&amp;quot;lastName&amp;quot;: &amp;quot;Pascari&amp;quot;,
&amp;quot;age&amp;quot;: 23,
&amp;quot;_links&amp;quot;: {
&amp;quot;self&amp;quot;: {
&amp;quot;href&amp;quot;: &amp;quot;https://localhost:8080/employees/employee/5a6f67519fea6938e0196c4d&amp;quot;
}
}
}
[/code]

  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 is also possible to have another kind of relationships too, like pointing to another class (we’ll get to 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 get more interesting because a department is not pointing just to itself, but to it’s employees too and the employees are pointing to themselves and to their list as well. So let’s take a look at the code.

[code language=”java”]
@RequestMapping(value = &amp;quot;/list/&amp;quot;, method = RequestMethod.GET)
public HttpEntity&amp;lt;List&amp;lt;Department&amp;gt;&amp;gt; getAllDepartments() {
List&amp;lt;Department&amp;gt; departments = departmentService.findAll();
if (departments.isEmpty()) {
return new ResponseEntity&amp;lt;&amp;gt;(HttpStatus.NO_CONTENT);
} else {
departments.forEach(d -&amp;gt; d.add(linkTo(methodOn(DepartmentController.class).getAllDepartments()).withRel(&amp;quot;departments&amp;quot;)));
departments.forEach(d -&amp;gt; d.add(linkTo(methodOn(DepartmentController.class).getDepartmentById(d.getDepartmentId())).withSelfRel()));
departments.forEach(d -&amp;gt; d.getEmployees().forEach(e -&amp;gt; {
e.add(linkTo(methodOn(EmployeeController.class).getAllEmployees()).withRel(&amp;quot;employees&amp;quot;));
e.add(linkTo(methodOn(EmployeeController.class).getEmployeeById(e.getEmployeeId())).withSelfRel());
}));
return new ResponseEntity&amp;lt;&amp;gt;(departments, HttpStatus.OK);
}
}
[/code]

  So this code will produce the following response:

[code language=”java”]
{
&amp;quot;departmentId&amp;quot;: &amp;quot;5a6f6c269fea690904a02657&amp;quot;,
&amp;quot;name&amp;quot;: &amp;quot;Service Department&amp;quot;,
&amp;quot;description&amp;quot;: &amp;quot;Service Rocks!&amp;quot;,
&amp;quot;employees&amp;quot;: [
{
&amp;quot;employeeId&amp;quot;: &amp;quot;5a6f6c269fea690904a02656&amp;quot;,
&amp;quot;firstName&amp;quot;: &amp;quot;Ion&amp;quot;,
&amp;quot;lastName&amp;quot;: &amp;quot;Pascari&amp;quot;,
&amp;quot;age&amp;quot;: 23,
&amp;quot;_links&amp;quot;: {
&amp;quot;employees&amp;quot;: {
&amp;quot;href&amp;quot;: &amp;quot;https://localhost:8080/employees/list/&amp;quot;
},
&amp;quot;self&amp;quot;: {
&amp;quot;href&amp;quot;: &amp;quot;https://localhost:8080/employees/employee/5a6f6c269fea690904a02656&amp;quot;
}
}
}
],
&amp;quot;_links&amp;quot;: {
&amp;quot;departments&amp;quot;: {
&amp;quot;href&amp;quot;: &amp;quot;https://localhost:8080/departments/list/&amp;quot;
},
&amp;quot;self&amp;quot;: {
&amp;quot;href&amp;quot;: &amp;quot;https://localhost:8080/departments/department/5a6f6c269fea690904a02657&amp;quot;
}
}
}
[/code]

  Nothing changed except that fact that there are some links with relationships which aren’t named self, these are the other kind of relationships that I was talking 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 actually did not, because we need much 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 find the full source code on my GitHub repository.

References

Upscale Your

Business TODAY
Connect with us
Bulgara Street 33/1, Chisinau MD-2001, Moldova
+ 373 22 996 170
info@isd-soft.com
De Amfoor 15, 5807 GW Venray-Oostrum, The Netherlands
+31 6 212 94 116

Subscribe to our newsletter today to receive updates on the latest news, releases and special offers.

Copyright ©2024, ISD. All rights reserved | Cookies Policy | Privacy Policy

De Nederlandse pagina’s zijn vertaald met behulp van een AI-applicatie.