Web API design is actually a very important design topic. Many companies will have a company-level Web API design specification. Almost all projects will be designed in the detailed design stage. After project development, there will be an API document for testing and joint debugging. . This article attempts to summarize the four common API design styles and design considerations based on their own understanding.

Web API: RPC

This is the most common way, RPC is talking about the local method of calling remote, and is oriented to the process.

  • The organizational form of the API in the form of RPC is the class and method, or the domain and behavior.
  • So the name of the API is often a verb, such as GetUserInfo, CreateUser .
  • Because URIs are very numerous and often do not have some convention specifications, detailed documentation is required.
  • It is also because of the unfettered, HTTP method basically only uses GET and POST, the design is relatively simple.

There are no examples here, and it is estimated that more than 50% of the APIs are such compartments.

Web API: REST

Is an architectural style with four levels of maturity:

  • Level 0: Defines a URI, all operations are POST requests made to this URI.
  • Level 1: Create a separate URI for each resource.
  • Level 2: Use the HTTP method to define the actions that are performed on the resource.
  • Level 3: Use Hypermedia (HATEOAS).

Level 0 is actually the style of the class RPC, level 3 is the real REST, and most of the REST APIs are at level 2. Some key points of REST implementation include:

  • The RESTful form of API organization is resources and entities, all around resources (points of level 1). The design process includes:
    • Identify the resources provided by the API
    • Determine the relationship between resources
    • Determine the resource URI structure based on resource type and relationship
    • Determining the structure of the resource
  • It will define some standard methods (points of level 2) and then map the standard methods to implementations (such as HTTP Method):
Web API: REST

Web API: REST

    • GET: Get resource details or a list of resources. For a collection type URI (such as /customers ) is to get a list of resources, for the item type URI (such as /customers/1 ) is to get a resource.
    • POST: Create a resource, the request body is the content of the new resource. Often POST is used to add resources to a collection.
    • PUT: Create or modify a resource, the request body is the content of the new resource. Often PUT is used for adding or modifying individual resources. Implementation must be idempotent.
    • PATCH: Partially modify the resource, the request body is the part of the modification. PUT generally requires the entire resource to be modified, and PATCH is used to modify some content (such as an attribute).
    • DELETE: Remove the resource. Like GET, for a collection type URI (such as /customers ) is to delete all resources, for the item type URI (such as /customers/1 ) is to delete a resource.
  • Need to consider navigation between resources (points of level 3, such as using HATEOAS, HATEOAS is the abbreviation of Hypertext as the Engine of Application State). With resource navigation, the client may not even need to consult the documentation to find more resources that are useful to them, but HATEOAS has no fixed standards, such as:
{
    "content": [ {
        "price": 499.00,
        "description": "Apple tablet device",
        "name": "iPad",
        "links": [ {
            "rel": "self",
            "href": "http://localhost:8080/product/1"
        } ],
        "attributes": {
            "connector": "socket"
        }
    }, {
        "price": 49.00,
        "description": "Dock for iPhone/iPad",
        "name": "Dock",
        "links": [ {
            "rel": "self",
            "href": "http://localhost:8080/product/3"
        } ],
        "attributes": {
            "connector": "plug"
        }
    } ],
    "links": [ {
        "rel": "product.search",
        "href": "http://localhost:8080/product/search"
    } ]
}   

The Spring framework also provides support: https://spring.io/projects/spring-hateoas, such as the following code:

@RestController
public class GreetingController {

    private static final String TEMPLATE = "Hello, %s!";

    @RequestMapping("/greeting")
    public HttpEntity<Greeting> greeting(
            @RequestParam(value = "name", required = false, defaultValue = "World") String name) {

        Greeting greeting = new Greeting(String.format(TEMPLATE, name));
        greeting.add(linkTo(methodOn(GreetingController.class).greeting(name)).withSelfRel());

        return new ResponseEntity<>(greeting, HttpStatus.OK);
    }
}

Produces the following results:

Web API: REST result

Web API: REST result

  • In addition to the few points mentioned earlier, there are some minor points in the design of the REST API:
    • Must be stateless, independent, and indistinguishable
    • The API needs to have a consistent interface to decouple the client and service implementation. If you are based on HTTP, you must use the HTTP Method to manipulate the resource, and try to use the HTTP response code to handle the error.
    • Need to consider cache, version control, content negotiation, partial response, etc.

It can be said that the REST API design needs to be designed. It needs to carefully consider the API resources, the relationship between resources and navigation, the definition of URI, and so on. For a well-designed REST API, in fact, as long as the client knows the list of available resources, it is often easy to explore most of the APIs according to the conventions and navigation. Ironically, there are a lot of websites that give REST to the front-end and client-side interfaces. Crawler developers can easily explore all interfaces and even internal interfaces. After all, guessing that REST interfaces are much easier than RPC interfaces.

As a supplement, here are a few more aspects of the REST API design debate.

Create a resource using PUT or POST

For example, https://stackoverflow.com/questions/630453/put-vs-post-in-rest, in general, everyone basically agrees with the three aspects mentioned by Microsoft:

  • The client decides to use the PUT for the resource name, and the server determines the resource name with POST.
  • POST is to add resources to the collection
  • PUT implementation requires idempotent

Of course, some companies’ specifications are to create resources just POST, not PUT

Exception handling HTTP response status code

  • The REST recommendation is to consider using matching Http status codes as much as possible to correspond to the type of error, such as deleting the user’s operation:
    • User can’t find is 404
    • After the deletion is successful, 204
    • The user cannot delete because the account balance is 409 (the client’s problem is 4xx)
    • The other server exception is 500 (the server side problem is 5xx)
  • In general, the starting point of this specification is good, and it is difficult to achieve it. The reasons are as follows:
    • There is no uniform standard for the mapping of status codes to various error types. When engineers implement them, they are varied.
    • Implementation may require coupling state codes in the business logic, it is difficult to do in GlobalExceptionHandler, unless you first specify a dozen exceptions in advance
    • If an incorrect response state is used, it may cause some operations such as a reverse proxy to trigger an error, and it is not clear which level is wrong when the problem occurs.
    • The processing methods of various Http Clients corresponding to non-200 status codes are not consistent.
  • Regarding the controversy on this issue, the API implementations of major platforms have some compliance with this specification, and some are 500 or even 200. The relevant domestic and international discussions are:
    • Https://stackoverflow.com/questions/27921537/returning-http-200-ok-with-error-within-response-body
    • Https://www.zhihu.com/question/268409269/
    • Https://www.zhihu.com/question/58686782
    • Https://blog.cloud-elements.com/error-handling-restful-api-design-part-iii
  • Many big manufacturers at home and abroad have different implementations for this. In general, my suggestions are:
    • If we know that the API is REST and the API is used externally, we should use the appropriate status code to reflect the error (recommended to be used within 20), and explain it in the documentation, and need to refine the response body after the error. Error information (including code and message)
    • If the REST API is used internally, then the response code type can be converged to several when the client and the server negotiate a uniform standard, which is convenient to implement.
    • If the API is internally used in the form of RPC over HTTP, it can even degenerate to a business exception and also use a 200 response return

Return data if it needs to be packaged

I have seen many articles saying that REST still recommends that the returned data itself is entity information (or list information), and it is not recommended to layer the data (Result). If you need more information to add, you can put it in the HTTP header, such as https://developer.github.com/v3/projects/cards/ API:

GET /projects/columns/:column_id/cards

Status: 200 OK
Link: <https://api.github.com/resource?page=2>; rel="next",
      <https://api.github.com/resource?page=5>; rel="last"
[
  {
    "url": "https://api.github.com/projects/columns/cards/1478",
    "id": 1478,
    "node_id": "MDExOlByb2plY3RDYXJkMTQ3OA==",
    "note": "Add payload for delete Project column",
    "created_at": "2016-09-05T14:21:06Z",
    "updated_at": "2016-09-05T14:20:22Z",
    "archived": false,
    "column_url": "https://api.github.com/projects/columns/367",
    "content_url": "https://api.github.com/repos/api-playground/projects-test/issues/3",
    "project_url": "https://api.github.com/projects/120"
  }
]

The example of HATEOAS we gave before is that there are “content” and “links” levels in the response body, that is, the response body is not the resource itself, it is packaged. In addition to links, many times we will directly use the unified format. To define an API response structure, such as:

{
    "code" : "",
    "message" : "",
    "path" : ""
    "time" : "",
    "data" : {},
    "links": []
}

I personally like this way, I don’t like to use HTTP headers, because the variable deployment and network environment, if some of the request headers are modified or discarded, it will be very troublesome (and the troublesome Header Key case) Problem), the response body generally all agents will not move.

Does the URI design level exceed two levels?

Microsoft’s API design guide (posted at the end of the article) pointed out that avoiding too complex hierarchical resources, such as /customers/1/orders/99/products is too complex, can be degraded to /customers/1/orders and /orders/99/products The complexity of not URI should not exceed collection/item/collection . Some APIs of Google will have more levels, such as:

API service: spanner.googleapis.com
A collection of instances: projects/*/instances/*.
A collection of instance operations: projects/*/instances/*/operations/*.
A collection of databases: projects/*/instances/*/databases/*.
A collection of database operations: projects/*/instances/*/databases/*/operations/*.
A collection of database sessions: projects/*/instances/*/databases/*/sessions/*

I agree with Microsoft’s specifications at this point, and the level of too deep is not convenient to implement.

Web API: GraphQL

If RPC is process-oriented and REST is resource-oriented, then GraphQL is for data queries. “GraphQL is both a query language for the API and a runtime that satisfies your data queries. GraphQL provides a complete, easy-to-understand description of the data in your API, enabling the client to accurately get the data it needs. And without any redundancy, it also makes it easier for APIs to evolve over time and can be used to build powerful developer tools.”

With GraphQL, you don’t even need any interface documentation. After defining the schema, the server implements the schema. The client can view the schema and then construct the query request that you need to get the data you need.

Web API: GraphQL response

Web API: GraphQL response

For example, a schema defined as follows:

#
# Schemas must have at least a query root type
#
schema {
    query: Query
}

type Query {
    characters(
        episode: Episode
    ) : [Character]

    human(
        # The id of the human you are interested in
        id : ID!
    ) : Human

    droid(
        # The non null id of the droid you are interested in
        id: ID!
    ): Droid
}

# One of the films in the Star Wars Trilogy
enum Episode {
    # Released in 1977
    NEWHOPE
    # Released in 1980.
    EMPIRE
    # Released in 1983.
    JEDI
}

# A character in the Star Wars Trilogy
interface Character {
    # The id of the character.
    id: ID!
    # The name of the character.
    name: String!
    # The friends of the character, or an empty list if they
    # have none.
    friends: [Character]
    # Which movies they appear in.
    appearsIn: [Episode]!
    # All secrets about their past.
    secretBackstory : String @deprecated(reason : "We have decided that this is not canon")
}

# A humanoid creature in the Star Wars universe.
type Human implements Character {
    # The id of the human.
    id: ID!
    # The name of the human.
    name: String!
    # The friends of the human, or an empty list if they have none.
    friends: [Character]
    # Which movies they appear in.
    appearsIn: [Episode]!
    # The home planet of the human, or null if unknown.
    homePlanet: String
    # Where are they from and how they came to be who they are.
    secretBackstory : String @deprecated(reason : "We have decided that this is not canon")
}

# A mechanical creature in the Star Wars universe.
type Droid implements Character {
    # The id of the droid.
    id: ID!
    # The name of the droid.
    name: String!
    # The friends of the droid, or an empty list if they have none.
    friends: [Character]
    # Which movies they appear in.
    appearsIn: [Episode]!
    # The primary function of the droid.
    primaryFunction: String
    # Construction date and the name of the designer.
    secretBackstory : String @deprecated(reason : "We have decided that this is not canon")
}

Use the GraphQL Playground (https://github.com/prisma/graphql-playground) to view the graphql endpoints to see all supported queries:

Web API:GraphQL Playground 1

Web API:GraphQL Playground 1

in fact, it is __schema:

Web API:GraphQL Playground 2

Web API:GraphQL Playground 2

then we can define the query request according to the client’s UI needs, the service The data will be returned according to the structure given by the client:

Web API:GraphQL Playground 3

Web API:GraphQL Playground 3

GraphQL is the ability to clarify data through Schema. The server provides a unified and unique API entry. Then the client tells the server the specific data structure I want (basically it can be said that there is no need for API documentation). It is a bit of a client-driven server. meaning. Although the client is flexible, the implementation of the GraphQL server is more complicated and painful. GraphQL cannot replace several other design styles, not the legendary REST 2.0. See https://github.com/chentsulin/awesome-graphql for more information.

Web API: Server driver API

There is no English abbreviation on the tall, because this model or style is my own, that is, the API is used to drive the client through the API, and has been practiced in some previous projects. To put it bluntly, it is to include information on how to drive the client to do in the return result of the API, two levels:

  • Interactive driver: for example, including actionType and actionInfo, actionType can be toast, alert, redirectView, redirectWebView, etc., actionInfo is toast information, alert information, redirect URL, and so on. The benefits of the client’s interaction after requesting the API are:
    • Flexible: In an emergency, you can also use the redirect method for emergency. For example, if you need special circumstances, you need to urgently make logical changes. You can switch to H5 directly without sending a version. Even we can provide background to let products or operations configure interaction. Ways and information
    • Unification: Sometimes you will encounter different clients. iOS, Android, and front-end implementations are not uniform. If the API results can specify this part, you can completely avoid this problem.
  • Behavior-driven: A deeper server-side driver that implements a set of APIs as an entry point for the client to make calls, and then tells the client what to present and what to do by agreeing on a set of DSLs.

There have been two such projects that used a similar API design approach:

  • Loan review: We know that the credit review logic of the loan tends to change a lot, and it also involves some authorization from the client (such as carrier crawler), and the release of the app is often difficult (App review of Apple App Store and Android app stores) problem). If you use a server-driven architecture to tell the client what interface should be presented next, then there will be a lot of flexibility.
  • Client crawler: We know that if you use the server to do crawling, many times you will be blocked because of IP problems, so you need to find a lot of agents. For a project, we came up with the concept of a client-side shared agent. We use the mobile client to do distributed agents. The server-side driver schedules all clients. At this time, the client needs to follow the instructions of the server to make a request and then report the response.

In general, the external Web API does not use this server-side driver to design the API. For some special types of projects, we can consider using this server-driven approach to design the API, let the client become a non-logical performer, and execute the UI and interaction.

Web API: Which mode to choose

Web API: how to choose:RPC, REST, GraphQL, Server Driver

Web API: how to choose:RPC, REST, GraphQL, Server Driver

Https://user-gold-cdn.xitu.io/2019/2/15/168eff296f015115 This article gives a decision on the RPC, REST, GRAPHQL selection can refer to the above figure.

I think:

  • Consider the RPC-style API or RPC in the following situations:
    • Internally biased API
    • There is not much time to consider the design of the API or no architect
    • The provided API is difficult to resource, object abstraction
    • High performance requirements
  • Consider the REST style in the following situations:
    • Bias external API
    • The API provided is built around resources, objects, and management.
    • Cannot couple client implementation
  • Consider GraphQL in the following situations:
    • The client’s demand for data is variable
    • Data has the characteristics of the graph
  • Consider the server driver in the following situations:
    • Client-side updates are difficult and require extreme flexibility to control the client
    • Private API only

Web API: More  to consider

Many of the API design guidelines mention the following design considerations and also need to be considered at design time:

  • Version control, such as:
    • Version control via URI Path, such as https://adventure-works.com/v2/customers/3
    • Version control via QueryString, such as https://adventure-works.com/customers/3?version=2
    • Version control via Header, such as adding a request header api-version=1
    • Version control via Media Type, such as Accept: application/vnd.adventure-works.v1+json
  • Cache strategy, such as:
    • Respond to the client to use the Cache-Control to tell the client to cache time (max-age), policy (private, public)
    • Respond to ETag for resource versioning
  • Partial response: For example, a large binary file needs to consider implementing the HEAD Method to indicate that the resource allows segmentation downloads, as well as providing resource size information:
HEAD https://adventure-works.com/products/10?fields=productImage HTTP/1.1

HTTP/1.1 200 OK
Accept-Ranges: bytes
Content-Type: image/jpeg
Content-Length: 4580

Then provide the resource segmentation download function:

GET https://adventure-works.com/products/10?fields=productImage HTTP/1.1
Range: bytes=0-2499

HTTP/1.1 206 Partial Content
Accept-Ranges: bytes
Content-Type: image/jpeg
Content-Length: 2500
Content-Range: bytes 0-2499/4580
[...]
  • List design: You need to consider paging, projection, sorting, and query when designing the list type API. It is worth noting that there are more additional functions of the list API, and try to naming the unified specification.

Reference material

  • Microsoft API Design Guide: https://docs.microsoft.com/en-us/azure/architecture/best-practices/api-design (English: https://docs.microsoft.com/en-us/azure/ Architecture/best-practices/api-implementation )
  • Google Cloud API Design Guide: https://google-cloud.gitbook.io/api-design-guide/ (English: https://cloud.google.com/apis/design/ )
  • Overview of the Github API: https://developer.github.com/v3/
  • https://www.cnblogs.com/lovecindywang/p/10383756.html