This article is about RESTful maturity. It’s not a definition of levels of REST itself, but a simple approach with examples with HTTP, Spring.
Content
- What’s an API ?
- What’s REST?
- Steps toward REST
- REST API Level 0
- REST API Level 1
- REST API Level 2
- REST API Level 3
- REST API Level 4
- REST API Level 5
What’s an API ?
“defined methods of communication between various software components”
What’s REST?
REST or RESTful is not a standard, but a WebAPI
- Client-Server Architecture
- Stateless
- Cacheability
- Layered System
- Uniform Interface
“By using a stateless protocol and standard operations, REST systems aim for fast performance, reliability, and the ability to grow, by re-using components that can be managed and updated without affecting the system as a whole, even while it is running.”
Steps toward REST
First levels are now industry standards conventions, the lasts levels are more business oriented.
See more at https://martinfowler.com/articles/richardsonMaturityModel.html
REST API Level 0
Using HTTP 1.1 protocol !
- Client-Server : request-response protocol
- Uniform : web standard
- Layered System : HTTP hide underlying technology (database, cache, file, etc)
CREATE : POST /api/user READ : POST /api/user UPDATE : POST /api/user DELETE : POST /api/user
Example : SOAP over HTTP
Request
Response
https://www.tutorialspoint.com/soap/soap_examples.htm
REST API Level 1
1 Resource = 1 URI (like /api/users/…) !
- Stateless : only 1 URI, so no state in path (no /user/creation, just /users)
- Uniform: URI is universal, URI don’t change
CREATE : POST /api/users/create READ : GET /api/users, GET /api/user/read UPDATE : POST /api/users/update DELETE : POST /api/users/delete SEARCH : GET or POST /api/users?sort=name
Details
- static path : no /user and /users and /user-new …just /users
- redirection : no /users and /users/ …just /users
- case insensitive : no /User/RoleGroup …just /user/role-group
- format/parameters: no /users/xml/sort-by-name …just /user?format=xml&sort=name
Example : Same HTTP action for two different actions!
Update Request
POST /api/users HTTP/1.1 Host api.host.com ...
Delete Request
POST /api/users-delete HTTP/1.1 Host api.host.com ...
REST API Level 2
Actions inside URI (like /api/users {GET, PUT, POST, DEL}) !
- Uniform: using HTTP method for CRUD methods
- Cacheability : cache on READ, Idempotence on Actions
CREATE : PUT /api/users READ : GET /api/users/42 UPDATE : POST /api/users/42 DELETE : DEL /api/users/42 SEARCH : GET /api/users?sort=name
Details
- no action in URL : no /users/delete or /users-delete …just DEL /users
- list/array : no unique response object …just a list of IDs, then client will get datas (if not in cache) by calling resource/IDs
- Cache: resources ID in response instead of data
- Idempotence : actions can be applied multiple times without changing the result (ex: delete user/42 multiple time will only delete 1 row in database)
Example : GitHub Single Object (Uniforme)
Request
GET /users/DamienFremont HTTP/1.1 Host api.github.com
Response (then cached for next usages)
HTTP/1.1 200 OK { "login": "DamienFremont", "id": 123456, "url": "https://api.github.com/users/DamienFremont", // ... }
Example : GitHub Collection (Cacheability)
Request
GET /users/DamienFremont/followers HTTP/1.1 Host api.github.com
Response
HTTP/1.1 200 OK [ "https://api.github.com/users/jeanmichel72", "https://api.github.com/users/alien42", // ... ]
instead of this (not cacheable because some parts could change over time. ex: user update)
HTTP/1.1 200 OK [ { "login": "jeanmichel72", "id": 123456, ...}, { "login": "alien42", "id": 123456, ...}, // ...
REST API Level 3
RESTful or HATEOAS (Hypermedia As Engine of Application State) !
- Uniform : Self-descriptive
CREATE : PUT /api/users/create READ : GET /api/users/42 UPDATE : POST /api/users/42 DELETE : DEL /api/users/42 SEARCH : GET /api/users?sort=name Self-descriptive Response Body: HTTP/1.1 200 OK { "_embedded" : { "users" : [ { "username" : "admin", ... "_links" : { "self" : { "href" : "http://localhost:8080/api/users/1" }, "user" : { "href" : "http://localhost:8080/api/users/1" } } }, { ... } ] }, "_links" : { "self" : { "href" : "http://localhost:8080/api/users{?page,size,sort}", }, "search" : { "href" : "http://localhost:8080/api/users/search" } }, "page" : { "size" : 20, "totalElements" : 27, "totalPages" : 2, "number" : 0 } }
Details
- Self-descriptive : API index summarizes all the resources
- No question nor interpretation required about navigability, URI, URL, format
- No need for documentation
Example : Spring Self-descriptive Generated API
Spring API Request
<span data-mce-type="bookmark" id="mce_SELREST_start" data-mce-style="overflow:hidden;line-height:0" style="overflow:hidden;line-height:0" ></span> GET /api/ HTTP/1.1 Host localhost:8080
Spring API Response
HTTP/1.1 200 OK { "_links" : { "users" : { "href" : "http://localhost:8080/api/users{?page,size,sort}", "templated" : true } } }
REST API Level 4
API Versionning !
- Uniform : Self-descriptive
CREATE : PUT /api/users/create READ : GET /api/users/42 UPDATE : POST /api/users/42 DELETE : DEL /api/users/42 SEARCH : GET /api/users?sort=name ...Self-descriptive Response body : { "_links" : { "users" : { "href" : "http://localhost:8080/api/users{?page,size,sort}" } } } ...Version Request URI: GET /api/v2/user.json HTTP/1.1 Host api.host.com
Details: 2 solutions
- Path-based approch, in which the client defines the version in URL:
http://localhost:9001/api/v1/user http://localhost:9001/api/v2/user
- Content-Type header, in which the client defines the version in Accept header:
http://localhost:9001/api/v1/user with Accept: application/vnd.app-1.0+json OR application/vnd.app-2.0+json
Note: Since i am working a lot with rest documentation tools, i prefer to use the first approach.
Example : Implementation with Spring
import io.swagger.annotations.*; import org.springframework.stereotype.*; import org.springframework.web.bind.annotation.*; @Controller @Api(value = "user", description = "Operations about users") public class UserController { @ResponseBody @RequestMapping(method = RequestMethod.GET, value = "/api/v1/user") @ApiOperation(value = "Returns user", notes = "Returns the user", tags = <span data-mce-type="bookmark" id="mce_SELREST_start" data-mce-style="overflow:hidden;line-height:0" style="overflow:hidden;line-height:0" ></span>{"GET", "User"}) public String getUserV1() { return "User V1"; } @ResponseBody @RequestMapping(method = RequestMethod.GET, value = "/api/v2/user") @ApiOperation(value = "Returns user", notes = "Returns the user", tags = {"GET", "User"}) public String getUserV2() { return "User V2"; } }
Or you can duplicate/redirect to the old V1 classes.
REST API Level 5
Add behaviors to API responses !
CREATE : PUT /api/users/create READ : GET /api/users/42 UPDATE : POST /api/users/42 DELETE : DEL /api/users/42 SEARCH : GET /api/users?sort=name ...Self-descriptive Response body : { "_links" : { "users" : { "href" : "http://localhost:8080/api/users{?page,size,sort}" } } } ...Version Request URI: GET /api/v2/user.json HTTP/1.1 Host api.host.com ...Behavior in Response body HTTP/1.1 201 OK { "_links" : { "self" : { "href" : "http://localhost:8080/api/users/1" } }
Details
- user/app can discover API by using it
- next actions are directly provided to client, so no need to store all static URL in client source code, and after a CREATE you can directly use the resource with the newly provided READ URL
Example : User CREATE return READ/UPDATE/DELETE behaviours
Spring API Request
PUT /api/user HTTP/1.1 Host localhost:8080 { "firstname" : "Damien", "lastname" : "FREMONT" }
Spring API Response, with URL and newly created ID
HTTP/1.1 201 OK { "_links" : { "self" : { "href" : "http://localhost:8080/api/users/1" }, "user" : { "href" : "http://localhost:8080/api/users/1" }, } }
Conclusion
REST API self-descriptive can be enhanced with tools; like Swagger for autodocumentation and Spring for implementation.
Swagger
Spring REST Data generated API is a good example of RESTful, with uniform URI, discoverability via JSON response meta-data.
Spring Implementation
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface UserRepository extends JpaRepository&lt;User, Long&gt; { User findByUsername(String username); }
Spring API Request
GET /api/ HTTP/1.1 Host localhost:8080
Spring API Response
HTTP/1.1 200 OK { "_links" : { "profiles" : { "href" : "http://localhost:8080/api/profiles{?page,size,sort}", "templated" : true }, "users" : { "href" : "http://localhost:8080/api/users{?page,size,sort}", "templated" : true } } }
Spring API Request
GET /api/users HTTP/1.1 Host localhost:8080
Spring API Response
HTTP/1.1 200 OK { "_embedded" : { "users" : [ { "dateCreated" : null, "lastUpdated" : null, "username" : "admin", "enabled" : true, "accountNonExpired" : true, "credentialsNonExpired" : true, "accountNonLocked" : true, "roles" : [ ...], "_links" : { "self" : { "href" : "http://localhost:8080/api/users/1" }, "user" : { "href" : "http://localhost:8080/api/users/1" } } }, { ... } ] }, "_links" : { "self" : { "href" : "http://localhost:8080/api/users{?page,size,sort}", "templated" : true }, "profile" : { "href" : "http://localhost:8080/api/profile/users" }, "search" : { "href" : "http://localhost:8080/api/users/search" } }, "page" : { "size" : 20, "totalElements" : 27, "totalPages" : 2, "number" : 0 } }
References
https://en.wikipedia.org/wiki/Application_programming_interface
https://fr.wikipedia.org/wiki/Representational_state_transfer
http://www.baeldung.com/rest-versioning
https://stackoverflow.com/questions/20198275/how-to-manage-rest-api-versioning-with-spring
https://martinfowler.com/articles/richardsonMaturityModel.html
On GitHub
https://github.com/DamienFremont/blog/tree/master/20171123-rest-api-maturity-levels-from-0-to-5
I don’t have single idea what the hell is HATEOAS and this hypermedia inside rest response. If method is “GET: api/dog/33” and response is “{name: WallE, age:7, likes:chocolate}”, what else should it write in repsonse?