REST Architecture
The REST (Representational State Transfer) architecture today is widely used in backends, gradually replacing the use of SOAP.
Unlike SOAP, REST is an architectural style and not a protocol; therefore it gives guidelines on how to develop APIs, without specifying the protocols to use. Usually those who use this architectural style, use the HTTP protocol to make communicate the client with the server, use JSON format to receive/send data. But the use of these protocols is absolutely not a constraint by the REST architecture.
In this article we won't explain what REST is in detail, what are its constraints and what are the differences with SOAP; rather, we're going to see what are the best practices writing a good RESTful Web Service using Leonard Richardson's techniques and layer-breaking.
The Richardson Maturity Model
The Richardson Maturity Model is a model that gives guidelines for the creation of REST services, it consists of 4 levels (from 0 to 3) and level 3 fully respects how a true RESTful Web Service should be. But how many times have we seen at work REST services reflect the best practices of level 3? Maybe never. Why these best practices are not respected? A little because they are not known (often), a little because it is more complex to create services that respect them.
Level 0: The Swamp of POX
There is no concept of resource, there is no concept of HTTP methods. Services have a single URI and make use of an
single HTTP method (usually POST). Basically HTTP is only used as a transport protocol.
This mechanism is used for example in POX (Plain Old XML) applications and SOAP services.
Example: We want user information with id 1:
Request url: http://localhost:8080/api (POST)
Request body:
<UserRequest>
<id>1</id>
</UserRequest>
Response (200 OK):
<User>
<id>1</id>
<name>Vincenzo</name>
<surname>Racca</surname>
</User>
If you use it with id 1 it does not exist:
Response (200 OK):
<UserRequestError>
<id>1</id>
<reason>The user doesn't exist</reason>
</UserRequestError>
Level 1: Resources
In this level there is the concept of resource. Each resource will be identified by a specific URI. There is not yet the concept of verb HTTP.
Same as the previous example, but the url of the request is:http://localhost:8080/api/users (POST)
Level 2: HTTP verbs
Here comes into play the concept of HTTP verbs, which we can briefly summarize:
- GET requests to retrieve the resource, with http response 200 if it exists, 204 if the collection is empty, 404 if the single resource does not exist
- POST requests to create a new resource, with response 201 if it was created correctly
- PUT requests to modify a resource if it exists, create it otherwise
- DELETE requests to delete (no matter if physically or logically) a resource
In the example above, we used the verb POST to request user information 1. Using a REST level service
2, the request becomes:http://localhost:8080/api/users/1 (GET, senza request body)
Response (200 OK if the resource exists):
{
"id": "1",
"name": "Vincenzo",
"surname": "Racca",
"cars": [
{
"id":1,
"plate":"BG929RF"
},
{
"id":2,
"plate":"AG929RF"
}
]
}
Response (404 NOT FOUND if the resource does not exist).
The verb GET is defined as a secure method, because it is only used to retrieve data. This also makes it idempotent.
«A REST call is defined as idempowerful if a client can make the same repeated call several times producing always the same result.»
The PUT and DELETE methods are also called idempotent. Calling the PUT several times with the same URI and the same request, You will always overwrite the resource and get the same result. Calling it several times with the same URI delete the resource, if it exists, otherwise you do not perform any operation. By making n POST calls on the same URI instead, you potentially create n new resources.
Level 3: Hypermedia Controls
The final level introduces HATEOAS (Hypermedia As The Engine Of the Application State). Practically every resource contains links to all related resources and beyond. This allows you first of all to tell the client which other resources are connected to the resource call, but without weighing down the body of the response. An additional advantage is that the client can explore the calls made available by the server without real documentation on the list of all APIs available. In addition, on the server side you can easily view new relationships with other resources by simply adding links.
Request: http://localhost:8080/api/users/1 (GET, senza request body)
Response (200 OK if the resource exists):
{
"id": 1,
"name": "Vincenzo",
"surname": "Racca",
"_links": {
"cars": {
"href": "http://localhost:8080/api/users/1/cars"
},
"self": {
"href": "http://localhost:8080/api/users/1"
}
}
}
Note that now cars is a simple link that the client can invoke to retrieve all of the user's cars 1. Particularly useful is also the paging with HATEOAS:
Request: http://localhost:8080/api/users?page=0&size=2
Response:
{
"_embedded": {
"users": [
{
"id": 1,
"name": "Vincenzo",
"surname": "Racca",
"_links": {
"cars": {
"href": "http://localhost:8080/api/users/1/cars"
},
"self": {
"href": "http://localhost:8080/api/users/1"
}
}
},
{
"id": 2,
"name": "Pippo",
"surname": "Pluto",
"_links": {
"cars": {
"href": "http://localhost:8080/api/users/2/cars"
},
"self": {
"href": "http://localhost:8080/api/users/2"
}
}
}
]
},
"_links": {
"first": {
"href": "http://localhost:8080/api/users?page=0&size=2"
},
"self": {
"href": "http://localhost:8080/api/users?page=0&size=2"
},
"next": {
"href": "http://localhost:8080/api/users?page=1&size=2"
},
"last": {
"href": "http://localhost:8080/api/users?page=2&size=2"
}
},
"page": {
"size": 2,
"totalElements": 5,
"totalPages": 3,
"number": 0
}
}
We also have in the response the links to go to the next page, to the first and last one. Convenient no??
Conclusions
We have seen what the Richardson Maturity Model is and therefore the various REST levels. In the next article we will create a Web Service RESTful level 3 without using Spring Data Rest, so you can customize it the way you want. We will use the Spring HATEOAS library.