RESTful style API design
1. How do you understand RESTful
In 2000, Dr. Roy Thomas Fielding proposed several architectural styles of software applications in his famous doctoral thesis Architectural Styles and the Design of Network-based Software Architectures. As one of them, REST is generally introduced in Chapter 5 of this paper.
REST is the abbreviation of "REpresentational State Transfer", which can be translated into "expressive state transition", but in most cases, we only say REST or RESTful. Fielding positioned REST as the architecture style of "Distributed Hypermedia System" in his paper, and it mentioned a concept called "HATEOAS (Hypermedia as the engine of application state)".
We use an end-user oriented Web application to briefly explain this concept: the so-called Application State here represents the state of the client of the Web application. For simplicity, it can be understood as session state. Resources are presented in the form of Hypermedia in the browser. By clicking the link in hypermedia, you can obtain other relevant resources or process the current resources accordingly. The obtained resources or the response to resource processing are also presented on the browser again in the form of hypermedia. It can be seen that hypermedia has become the engine driving the conversion of client session state.
With the help of hypermedia, a special way of resource presentation, the transformation of application state is reflected in the transformation of presentation resources in the browser. If hypermedia is further abstracted into a general Representation mode, the application state becomes a REpresentational State. The transition between application states becomes a REpresentational State Transfer, which is REST.
summary
REST is a very general concept, which represents an architectural style.
2. How to design a good API
2.1 version number
In restful APIs, API interfaces should be compatible with previous versions as much as possible. However, in the actual business development scenario, with the continuous iteration of business requirements, the existing API interface may not support the adaptation of the old version. At this time, if the API interface of the server is forcibly upgraded, the old functions of the client will fail. In fact, the Web end is deployed on the server, so it can be easily upgraded to adapt to the new API interface of the server. However, other clients such as Android end, IOS end and PC end run on the user's machine. Therefore, it is difficult for the current product to adapt to the API interface of the new server, resulting in functional failure. In this case, Users must upgrade the product to the latest version to use it normally.
In order to solve the incompatibility of this version, a practical way to design RESTful API is to use version number. Generally, we will keep the version number in the url and be compatible with multiple versions at the same time.
[GET] /v1/users/{user_id} // API interface for querying user list in version v1 [GET] /v2/users/{user_id} // API interface for querying user list in version v2 /v1/users 11.1.0 Force users to upgrade /v2/users 11.2.0 /v3/users 11.3.0 /v4/users 12.1.0
Now, without changing the API interface for querying the user list of version v1, we can add the API interface for querying the user list of version v2 to meet the new business requirements. At this time, the new function of the client product will request the API interface address of the new server. Although the server will be compatible with multiple versions at the same time, maintaining too many versions at the same time is a great burden for the server, because the server needs to maintain multiple sets of code. In this case, the common practice is not to maintain all compatible versions, but only the latest compatible versions, such as the latest three compatible versions. After a period of time, when the vast majority of users upgrade to a newer version, discard some old API interface versions of the less used server, and require users who use the very old version of the product to upgrade forcibly.
Note that "API interface for querying user list without changing version v1" mainly means that it seems to be unchanged for the caller of the client. In fact, if the business changes too much, the service side developers need to use the adapter mode for the old version of the API interface to adapt the request to the new API interface.
2.2 resource path
The design of RESTful API takes resources as the core, and each URI represents a resource. Therefore, the URI cannot contain verbs, only nouns. Note that adjectives can also be used, but try to use them less. Generally speaking, no matter whether the resource is single or multiple, the nouns of API should be named in the plural. In addition, when naming nouns, use lowercase, numbers and underscores to distinguish multiple words. This is designed to be consistent with the naming scheme of json objects and attributes. For example, an interface for querying system tags can be designed as follows.
[GET] /v1/tags/{tag_id} Ranking complex s Multiple words are underlined Get interfaces for all users /v1/users Get a id User interface /v1/users/{gender}/{amount_id}
At the same time, the path of resources should be as follows from root to child
/{resources}/{resource_id}/{sub_resources}/{sub_resource_id}/{sub_resource_property}
Let's look at a design of "adding user's role", in which "user" is the main resource and "role" is the sub resource.
[POST] /v1/users/{user_id}/roles/{role_id} // Add user's role
Sometimes, when a resource change is difficult to be named using the standard RESTful API, you can consider using some special actions names.
/{resources}/{resource_id}/actions/{action}
For example, the naming of the "password modification" interface is difficult to completely use nouns to build paths. At this time, action naming can be introduced.
[PUT] /v1/users/{user_id}/password/actions/modify // Password modification
2.3 request mode
You can operate the resources of the server through GET, POST, PUT, PATCH, DELETE, etc. Of which:
- GET: used to query resources
- POST: used to create resources
- PUT: used to update all the information of the server's resources = = update
- PATCH: partial information used to update the resources of the server = = update
- DELETE: used to DELETE the resources of the server.
Here, use the case of "user" to review and operate the resources of the server through GET, POST, PUT, PATCH, DELETE, etc.
[GET] /users # Query user information list [GET] /users/1001 # View a user's information [POST] /users # New user information [PUT] /users/1001 # Update user information (all fields) [PATCH] /users/1001 # Update user information (some fields) [DELETE] /users/1001 # Delete user information
$.ajax({ url:"/users", type:"POST", data:{ username:"xiaolu", password:"123", ... } }); $.ajax({ url:"/users/1001", type:"PUT", data:{ username:"xiaolu", password:"123", ... } });
@RestController @RequestMapping("users") public class UserController{ @RequestMapping(value="{user_id}",method=RuquestMapping.PUT) public String user(@PathVirable("user_id") Long user_id,TbUser tbUser){ tbUserService.update(tbUser); return "user_list"; } } /users/1001
2.4 query parameters
The RESTful API interface should provide parameters to filter the returned results. Where offset specifies the start position of the return record. In general, it will perform paging queries in combination with limit, where limit specifies the number of returned records.
[GET] /{version}/{resources}/{resource_id}?offset=0&limit=20
At the same time, orderby can be used to sort, but it only supports the sorting of a single character. If there are multiple field sorting, it needs to be supported by extending other parameters in the business.
[GET] /{version}/{resources}/{resource_id}?orderby={field} [asc|desc]
In order to better select whether to support the total number of queries, we can use the count field. Count indicates whether the returned data contains the total number of entries. Its default value is false.
[GET] /{version}/{resources}/{resource_id}?count=[true|false]
offset, limit and orderby described above are public parameters. In addition, there are many personalized parameters in the business scenario. Let's take an example.
[GET] /v1/categorys/{category_id}/apps/{app_id}?enable=[1|0]&os_type={field}&device_ids={field,field,...}
Note that do not over design and only return the query parameters required by the user. In addition, you need to consider whether to create database indexes on query parameters to improve query performance.
2.5 status code
It is important to use appropriate status codes, instead of returning all status codes 200 or using them indiscriminately. Here, some status codes commonly used in the actual development process are listed for reference.
Status code | describe |
---|---|
200 | Request succeeded |
201 | Created successfully |
400 | Bad request |
401 | Not verified |
403 | Rejected |
404 | Unable to find |
409 | Resource conflict |
500 | Server internal error |
2.6 abnormal response
When a non 2xx HTTP error code response occurs on the RESTful API interface, the global exception structure response information is adopted.
HTTP/1.1 400 Bad Request Content-Type: application/json { "code": "INVALID_ARGUMENT", "message": "{error message}", "cause": "{cause message}", "request_id": "01234567-89ab-cdef-0123-456789abcdef", "host_id": "{server identity}", "server_time": "2014-01-01T12:00:00Z" }
2.7 request parameters
When designing the RESTful API of the server, we also need to limit the request parameters. For example, for an interface that supports batch queries, we should consider the maximum number of queries supported.
[GET] /v1/users/batch?user_ids=1001,1002 // Batch query of user information Parameter description - user_ids: user ID String, up to 20 allowed.
In addition, when designing new or modified interfaces, we also need to clearly tell the caller which parameters are required, which are optional, and their boundary value restrictions in the document.
[POST] /v1/users // Create user information Request content { "username": "duo", // Required, user name, max 10 "realname": "sszzll", // Required, user name, max 10 "password": "123456", // Required, user password, max 32 "email": "topsale@vip.qq.com", // Optional, email, max 32 "weixin": "sszzll", // Optional, wechat account, max 32 "sex": 1 // Required, user gender [1-male, 2-female, 99 unknown] }
2.8 response parameters
For different operations, the results returned by the server to the user should meet the following specifications.
[GET] /{version}/{resources}/{resource_id} // Returns a single resource object [GET] /{version}/{resources} // Returns a list of resource objects [POST] /{version}/{resources} // Returns the newly generated resource object [PUT] /{version}/{resources}/{resource_id} // Returns the complete resource object [PATCH] /{version}/{resources}/{resource_id} // Returns the complete resource object [DELETE] /{version}/{resources}/{resource_id} // Status code 200, return the complete resource object. // Status code 204, return an empty document
If it is a single piece of data, the JSON string of an object is returned.
HTTP/1.1 200 OK { "id" : "01234567-89ab-cdef-0123-456789abcdef", "name" : "example", "created_time": 1496676420000, "updated_time": 1496676420000, ... }
If it is list data, an encapsulated structure is returned.
HTTP/1.1 200 OK { "count":100, "items":[ { "id" : "01234567-89ab-cdef-0123-456789abcdef", "name" : "example", "created_time": 1496676420000, "updated_time": 1496676420000, ... }, ... ] }
2.9 a complete case
Finally, we use a complete case to integrate the previous knowledge. Here, use the case of "get user list".
[GET] /v1/users?[&keyword=xxx][&enable=1][&offset=0][&limit=20] Get user list Function Description: get user list Request method: GET Parameter description - keyword: Keywords for fuzzy search.[Optional filling] - enable: Enable status[1-Enable 2-Disable]. [Optional filling] - offset: Gets the position offset, starting at 0.[Optional filling] - limit: The number of pieces returned each time. The default is 20, and the maximum is no more than 100. [Optional filling] Response content HTTP/1.1 200 OK { "count":100, "items":[ { "id" : "01234567-89ab-cdef-0123-456789abcdef", "name" : "example", "created_time": 1496676420000, "updated_time": 1496676420000, ... }, ... ] } Failure response HTTP/1.1 403 UC/AUTH_DENIED Content-Type: application/json { "code": "INVALID_ARGUMENT", "message": "{error message}", "cause": "{cause message}", "request_id": "01234567-89ab-cdef-0123-456789abcdef", "host_id": "{server identity}", "server_time": "2014-01-01T12:00:00Z" } error code - 403 UC/AUTH_DENIED Restricted authorization
3. How to understand idempotency of RESTful API (read)
3.1 what is idempotency
HTTP idempotent methods refer to HTTP methods that will not have different results no matter how many times they are called. Whether you call it once, or call it a hundred times, or a thousand times, the result is the same.
GET /tickets # Get ticket list GET /tickets/12 # View a specific ticket POST /tickets # Create a new ticket PUT /tickets/12 # Update ticket 12 PATCH /tickets/12 # Update ticket 12 DELETE /tickets/12 # Delete ticekt 12
1) HTTP GET method
The HTTP GET method is used to obtain resources. No matter how many times the interface is called, the result will not change, so it is idempotent.
GET /tickets # Get ticket list GET /tickets/12 # View a specific ticket
Just querying the data will not affect the change of resources, so we think it is idempotent.
It is worth noting that idempotency refers to the effect on the result rather than the resource itself. How to understand? For example, this HTTP GET method may get different return contents each time, but it does not affect resources.
May you ask, is this the case? Of course. For example, if we have an interface to get the current time, we should design it as
GET /service_time # Get the current time of the server
It will not affect the resource itself, so it satisfies idempotency.
2) HTTP POST method
The HTTP POST method is a non idempotent method, because multiple calls will generate new resources.
POST /tickets # Create a new ticket
Because it will affect the resource itself, a new resource will be generated for each call, so it does not meet the idempotency.
3) HTTP PUT method
Is the HTTP PUT method idempotent? Let's take a look
PUT /tickets/12 # Update ticket 12
Because it directly replaces the data of the entity part with the resources of the server, we call it many times, which will only have an impact once, but the HTTP method with the same result meets idempotency.
4) HTTP PATCH method
The HTTP PATCH method is non idempotent. The HTTP POST method and HTTP PUT method may be easy to understand, but the HTTP PATCH method only updates some resources. How can it be non idempotent?
Because the entity provided by PATCH needs to be parsed and executed on the server according to the definition of program or other protocols, so as to modify the resources on the server. In other words, a PATCH request will execute a program. If it is submitted repeatedly, the program may execute multiple times, which may have additional impact on the resources on the server, which can explain why it is non idempotent.
Maybe you don't understand that yet. Let's take an example
PATCH /tickets/12 # Update ticket 12
At this time, our server's processing of the method is that when calling a method, updating some fields and adding one to the operation record of this ticket record, does the resource of each call change this time, so it may be a non idempotent operation.
5) HTTP DELETE method
The HTTP DELETE method is used to delete resources, which will be deleted.
DELETE /tickets/12 # Delete ticekt 12
One and more calls have the same impact on resources, so they also meet idempotency.
3.2 how to design high-quality RESTful API conforming to idempotence
1) HTTP GET vs HTTP POST
Maybe you will think of an interview question. What is the difference between GET and POST methods of HTTP requests? You may answer: submit data through the URL in GET mode, and the data can be seen in the URL; In POST mode, data is submitted in HTML HEADER. However, we now look at the problem from the perspective of RESTful resources. The HTTP GET method is idempotent, so it is suitable for query operations. The HTTP POST method is non idempotent, so it is used to represent new operations.
However, there are exceptions. Sometimes we may need to transform the query method into HTTP POST method. For example, the extra long (1k) GET URL is replaced by the POST method, because GET is limited by the length of the URL. Although it is not idempotent, it is a compromise.
2) HTTP POST vs HTTP PUT
For HTTP POST method and HTTP PUT method, our general understanding is that POST means to create resources and PUT means to update resources. Of course, this is the correct understanding.
However, in fact, both methods are used to create resources, and the more essential difference is idempotency. The HTTP POST method is non idempotent, so it is used to represent the creation of resources. The HTTP PUT method is idempotent, so it means that it is more appropriate to update resources.
3) HTTP PUT vs HTTP PATCH
At this point, you see, there will be another problem. HTTP PUT method and HTTP PATCH method are both used to express update resources. What is the difference between them? Our general understanding is that PUT means to update all resources and PATCH means to update some resources. First of all, this is the first criterion we abide by. According to the above description, the PATCH method is non idempotent, so we also need to consider it when designing the RESTful API of our server. If we want to explicitly tell the caller that our resources are idempotent, my design prefers to use the HTTP PUT method.