REST? Take a dumb JSON-RPC

    Recently on Habré there has been a lot of controversy over how to properly prepare the REST API.

    Instead of raging in the comments, think: do you really need REST at all?
    Is this a conscious choice or habit?

    Perhaps it is your RPC-like API project that is best suited?

    So what is JSON-RPC 2.0 ?
    This is a simple stateless protocol for creating an API in the style of RPC (Remote Procedure Call).
    It usually looks as follows.

    You have one single endpoint on the server that accepts requests with a body of the form:

    {"jsonrpc": "2.0", "method": "post.like", "params": {"post": "12345"}, "id": 1}

    And gives the answers of the form:

    {"jsonrpc": "2.0", "result": {"likes": 123}, "id": 1}

    If an error occurs - the error response:

    {"jsonrpc": "2.0", "error": {"code": 666, "message": "Post not found"}, "id": "1"}

    And it's all!

    Bonus support batch operations:

    Request:
    [
      {"jsonrpc":"2.0","method":"server.shutdown","params":{"server":"42"},"id":1},
      {"jsonrpc":"2.0","method":"server.remove","params":{"server":"24"},"id":2}
    ]
    Response:
    [
      {"jsonrpc":"2.0","result":{"status":"down"},"id":1}
      {"jsonrpc":"2.0","error":{"code":1234,"message":"Server not found"},"id": 2}
    ]
    

    In the field, the idclient API can send anything so that after receiving responses from the server, match them with requests.

    Also, the client can send “notifications” - requests without an “id” field that do not require a response from the server:

    {"jsonrpc":"2.0","method":"analytics:trackView","params":{"type": "post", "id":"123"}},
    

    There are probably libraries for client and server for all popular languages.
    If not, it doesn’t matter. The protocol is so simple that it takes a couple of hours to write your implementation.

    Working with the RPC client, which I first came across on npmjs.com, looks like this:

    client.request('add', [1, 1], function(err, response) {
      if (err) throw err;
      console.log(response.result); // 2
    });
    

    Profits


    Consistency with the business logic of the project

    First, you can not hide complex operations behind a meager set of HTTP verbs and redundant URIs.

    There are subject areas where there should be more operations than APIs in the API.
    Offhand - projects with complex business processes, gamedev, instant messengers and similar realtime stuff.

    Yes, even take a content project like Habr ...

    Pressing the "↑" button under the post is not a resource change, but a call to a whole chain of events, right up to issuing icons or invites to the post author.

    So is it worth masking post.like(id)for PUT /posts/{id}/likes?

    It is also worth mentioning CQRS , with which the RPC-based API will look better.

    Secondly, there are always fewer response codes in HTTP than the types of business logic errors that you would like to return to the client.

    Someone always returns 200, someone puzzles trying to correlate errors with HTTP codes.

    In JSON-RPC, the entire range of integer is yours.

    JSON-RPC is a standard, not a set of recommendations.

    A very simple standard.
    Request data can be:
    REST Rpc
    In request URI ---
    In GET parameters ---
    In HTTP headers ---
    In request body In request body

    Response data may be:
    REST Rpc
    In the HTTP response code ---
    In HTTP headers ---
    In the body of the response (format not standardized) In the body of the response (standardized format)


    POST /server/{id}/statusor PATCH /server/{id}?
    It doesn't matter anymore. It remains POST /api.

    There are no best practices from the forums, there is a standard.
    There is no disagreement in the team, there is a standard.

    Of course, a well-implemented REST API can be fully documented. However ...

    Do you know what and where you need to pass in the request to the Github API to get the reactions object along with issue?
    Accept: application/vnd.github.squirrel-girl-preview
    Is this good or bad? Decide for yourself, google yourself. There is no standard.

    HTTP independence

    In theory, REST principles can be applied not only to APIs over HTTP.
    In practice, everything is different.

    JSON-RPC over HTTP painlessly migrates to JSON-RPC over Websocket. Yes, at least TCP.
    The body of a JSON-RPC request can be directly queued in raw form to be processed later.

    There are no more problems with spreading business logic by transport layer (HTTP).

    HTTP 404
    REST Rpc
    There is no resource with this identifier ---
    No API here No API here




    JSON-RPC performance comes in handy if you have:
    - Batch requests
    - Notifications that can be processed asynchronously
    - Web sockets

    It's not that all this could not be done without JSON-RPC. But with him - a little easier.

    Underwater rocks


    HTTP Caching

    If you intend to cache your API responses at the HTTP level, RPC may not work.
    This usually happens if you have a public, mostly read-only API.
    Something like getting a weather forecast or exchange rate.

    If your API is more "dynamic" and intended for "internal" use - everything is ok.

    access.log

    All requests to the JSON-RPC API in the web server logs look the same.
    It is solved by application-level logging.

    Documentation

    There is no swagger.io level tool for JSON-RPC .
    Apidocjs.com will do , but it's much more modest.
    However, you can document such a simple API even in a markdown file.

    Stateless

    “REST” is about architecture, not HTTP verbs, you argue. And you will be right.

    Roy Fielding’s original dissertation does not indicate which verbs, headers, or HTTP codes to use.

    But there is a magic word in it that will come in handy even when designing the RPC API. "Stateless."
    Each client request to the server must contain all the information necessary to fulfill this request, without storing any context on the server side. Session state is entirely stored on the client side.
    By making the RPC API on top of web sockets, you might be tempted to force the application server to store a little more client session data than necessary.

    How stateless should the API be so as not to cause problems? For contrast, remember the truly statefull protocol - FTP. Session status is stored on the server. The FTP server remembers that the client has already authenticated at the beginning of the session, and remembers in which directory this client is “located”. Such an API is difficult to develop, debug and scale. Do not do so.

    Клиент: [открывает TCP-соединение]
    Сервер: 220 ProFTPD 1.3.1 Server (ProFTPD)
    Клиент: USER anonymous
    Сервер: 331 Anonymous login ok, send complete email address as your password
    Клиент: PASS user@example.com
    Сервер: 230 Anonymous access granted, restrictions apply
    Клиент: CWD posts/latest
    Сервер: 250 CWD command successful
    Клиент: RETR rest_api.txt
    Сервер: 150 Opening ASCII mode data connection for rest_api.txt (4321 bytes)
    Сервер: 226 Transfer complete
    Клиент: QUIT
    Сервер: 221 Goodbye.






    Eventually


    Take JSON-RPC 2.0 if you decide to make the RPC API on top of HTTP or web sockets.
    You can, of course, come up with your bike, but why?

    Take GraphQL if you really need it.

    Take gRPC or something similar for communication between (micro) services, if your PL supports it.

    Take REST if you need it. Now, at least you will choose it consciously.

    Also popular now: