Is there a way to classify REST APIs based on their architectural maturity?
The Richardson Maturity Model developed by Leonard Richardson is a heuristic that is used to indicate how mature a web service is in regards to its REST Architectural style.
The model classifies APIs into 4 different levels (0-3)
- Level 0 - The Swamp of POX
- Level 1 - Resources
- Level 2 - HTTP Verbs
- Level 3 - Hypermedia Controls
The model identifies Level 3 as the Glory of REST
Level 0 - The Swamp of POX
In this level, HTTP is used only as a transport layer for remote interactions without using any inherent mechanisms of the web, meaning it only tunnels the request and nothing more. This style is usually associated with RPC (Remote Procedure Calls).
The acronym POX stand for Plain Old XML, this is because XML was used primarily as the payload of the request/response of the RPC however this idea can really be extended to any other formats, we will be using JSON for the examples.
Lets assume we have a social media app that lets users follow other users. Before following any specific user, we first want to show a list of all users. The app in this case will expose services at two separate endpoints.
- An endpoint for listing of all users
- An endpoint for following other users
First we will initiate a request to the first endpoint to list all users from Canada
#Request
POST /listUsers HTTP/1.1
{
"country" : "Canada"
}
After the service receives the request, it will retrieve a list of all users living in Canada
#Response
HTTP/1.1 200 OK
{
[
{ "name" : "John Smith", "id" : 45 },
{ "name" : "Jane Doe", "id" : 22 }
]
}
The next step now is to call the endpoint for following the desired user
#Request
POST /followUser HTTP/1.1
{
"id" : 22
}
the response could be either a success or an error, in case of success it will look something like this:
#Response
HTTP/1.1 200 OK
{
"status" : "success"
}
in case of an error it will look something like this:
#Response
HTTP/1.1 200 OK
{
"status" : "error",
"message" : "already following"
}
Notice that the request HTTP Method is always POST and the service HTTP status code is always 200 regardless of the state of the process.
Level 1 - Resources
In this level, the service starts working with resources denoted in the URI of the request. Note that resource identification is part of one of the main REST constraints which is having a Uniform Interface
The first request will be to the /users endpoint to get the list of all users, the response will be similar to the one before
#Request
POST /users HTTP/1.1
{
"country" : "Canada"
}
#Response
HTTP/1.1 200 OK
{
[
{ "name" : "John Smith", "id" : 45 },
{ "name" : "Jane Doe", "id" : 22 }
]
}
Now following the desired user by his id
#Request
POST /users/22 HTTP/1.1
{}
The response could be either a success or an error
#Response
HTTP/1.1 200 OK
{
"status" : "success"
}
#Response
HTTP/1.1 200 OK
{
"status" : "error",
"message" : "already following"
}
Notice that the HTTP Method as well as the HTTP status code remain unchanged just like Level 0 (POST and 200)
Level 2 - HTTP Verbs
Up until now, we have been strictly using HTTP POST, the idea was using HTTP only to tunnel the interactions through it and not utilizing any of its web mechanisms ( i.e: verbs and status codes ).
In this level we make sure to be as close as possible to the intent of the request in regards to the HTTP Verbs
The first request is now GET request to /users since we are logically reading or getting data, if we need to filter out based on a specific country we can now utilize query parameters
#Request
GET /users?country=canada HTTP/1.1
The response will be a list of users from Canada just like Level 0 and 1
#Response
HTTP/1.1 200 OK
{
[
{ "name" : "John Smith", "id" : 45 },
{ "name" : "Jane Doe", "id" : 22 }
]
}
Now when following another user we use POST, you can think of it as if we are logically creating a new follow request
#Request
POST /users/22 HTTP/1.1
{}
The response could be either a success or an error, in the case of success, it is denoted by the corresponding HTTP status code
#Response
HTTP/1.1 200 OK
{ }
In case of an error, a proper HTTP status code can be utilized as well
#Response
HTTP/1.1 409 CONFLICT
{
"message" : "already following"
}
Level 3 - Hypermedia controls
Level 3 introduces using something you might have heard about here or there which is HATEOAS ( Hypermedia As The Engine Of Application State ). Essentially HATEOAS makes the API respond to requests with additional information and resource links that make exploring the API and accessing additional resources easier. Most of the time it can infer to the client as to what "actions" can be done next and where to access them if needed.
As before the request to get all users
#Request
GET /users?country=canada HTTP/1.1
Now we have the API respond with relevant hypermedia controls
#Response
HTTP/1.1 200 OK
{
[
{
"name" : "John Smith",
"id" : 45 ,
"links" : [
{
"rel" : "rels/user/follow",
"href" : "/users/45",
}
]
},
{
"name" : "Jane Doe",
"id" : 22,
"links" : [
{
"rel" : "rels/user/follow",
"href" : "/users/22",
}
]
},
]
}
The rest of the requests/responses are pretty much the same as the previous level
#Request
POST /users/22 HTTP/1.1
{}
success/failure
#Response
HTTP/1.1 200 OK
{ }
#Response
HTTP/1.1 409 CONFLICT
{
"message" : "already following"
}
For many engineers, Level 2 seems to be enough and will put the API in a very solid state. You can imagine the complexity HATEOAS would introduce at both ends of the stack. However it has some benefits that shouldn't be just ignored.
HATEOAS provides the ability to seamlessly update the URI Schema of the APIs without worrying about or breaking clients as well as providing a self documenting API.
Closing Thoughts
Overall i think RMM in itself is not a model that defines how a truly RESTful API should look like, but it provides good guidelines that if followed can lead to a solid API Design head start.
Personally, i believe Level 3 is not worth the added complexity unless used in a very niche case where having a discoverable API is one of the key requirements of the project at hand.
I think introducing RMM especially to beginners in backend engineering is very important since it can make them think about good API Design and generally what makes an API RESTful by forcing them to atleast think about some of the constraints of REST even though it doesn't define it.
Top comments (4)
Great article! I think HATEOAS is something I'm going to be considering from now on.
Try writing a single page Javasript client for it first :)
Small typo:
Level 3 - HTTP Verbs
Should be:
Level 2 - HTTP Verbs
thank you!