Recently, a lot of controversy broke out on Habré about how to properly prepare the REST API.
Instead of raging in the comments, think: do you really need REST?
Is this a conscious choice or a habit?
Maybe your particular RPC-like API project will work better?
')
So what is
JSON-RPC 2.0 ?
This is a simple stateless
protocol for creating an RPC (Remote Procedure Call) API.
It usually looks like this.
You have one single endpoint on the server that accepts requests with the body of the form:
{"jsonrpc": "2.0", "method": "post.like", "params": {"post": "12345"}, "id": 1}
And gives answers like:
{"jsonrpc": "2.0", "result": {"likes": 123}, "id": 1}
If an error occurs - an error response:
{"jsonrpc": "2.0", "error": {"code": 666, "message": "Post not found"}, "id": "1"}
And it's all!
The bonus supports 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
id
field, the API client can send anything, so that after receiving responses from the server, match them with requests.
Also, the client can send “notifications” - requests without the “id” field, which do not require a response from the server:
{"jsonrpc":"2.0","method":"analytics:trackView","params":{"type": "post", "id":"123"}},
There are libraries for the client and the server, probably, for all popular languages.
If not, it doesn't matter. The protocol is so simple that it will take a couple of hours to write your implementation.
Working with the RPC client that I first got on npmjs.com looks like this:
client.request('add', [1, 1], function(err, response) { if (err) throw err; console.log(response.result);
Profits
Consistency with the business logic of the project
First, you can avoid hiding complex operations behind a meager set of HTTP verbs and redundant URIs.
There are subject areas where there must be more operations in the API than entities.
Offhand - projects with complex business processes, gamedev, instant messengers and similar realtime-things.
Yes, even take a content project like Habr ...
Pressing the "↑" button under the post is not a change in the resource, but a challenge to the whole chain of events, up to the issuance of 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-shny API will look better.
Secondly, the response codes in HTTP are always smaller than the types of business logic errors that you would like to return to the client.
Someone always returns 200-ku, someone puzzles, trying to compare errors with HTTP-codes.
In JSON-RPC, the entire integer range is yours.
JSON-RPC - standard, not a set of recommendations
Very simple standard.
Request data can be: |
---|
REST | RPC |
---|
Request URI | --- |
In GET Parameters | --- |
HTTP headers | --- |
In request body | In request body |
Response data can be: |
---|
REST | RPC |
---|
In the HTTP response code | --- |
HTTP headers | --- |
In the body of the answer (the format is not standardized) | In the response body (format is standardized) |
POST /server/{id}/status
or
PATCH /server/{id}
?
It doesn't matter anymore. It remains to
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. But…
Do you know what and where you need to pass in the request to the Github API to get the reactions object along with the issue?
Accept: application/vnd.github.squirrel-girl-preview
Is it 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 is safely transferred to JSON-RPC over Websocket. Yes, at least TCP.
The body of a JSON-RPC request can be directly queued in raw form in order to process it later.
No more problems from smearing business logic across the transport layer (HTTP).
HTTP 404 |
---|
REST | RPC |
---|
There is no resource with this identifier. | --- |
No API here | No API here |
Performance
JSON-RPC will come in handy if you have:
- Batch requests
- Notifications that can be processed asynchronously
- Web sockets
Not that this could not be done without JSON-RPC. But with him - a little easier.
Underwater rocks
HTTP caching
If you are going to cache your API responses at the HTTP level, RPC may not be suitable.
This usually happens if you have a public, mostly read-only API.
Something like getting a weather forecast or currency rate.
If your API is more "dynamic" and is intended for "internal" use - everything is ok.
access.log
All requests to the JSON-RPC API in the logs of the web server look the same.
It is solved by logging at the application level.
Documenting
There is no
swagger.io level tool for JSON-RPC.
Apidocjs.com will
do , but it is much more modest.
However, you can document this simple API even in a markdown file.
Stateless
“REST” is about architecture, but not HTTP verbs - you will object. And you will be right.
Roy Fielding’s original dissertation did not indicate which verbs, headings, and HTTP codes should be used.
But it has a magic word, which is useful even when designing an 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 stored entirely on the client side.
By doing RPC API on top of web sockets, you may be tempted to force the application server to store a little more data about a client session than is necessary.
How stateless should an API be in order not to cause problems? For contrast, let's remember a truly statefull protocol - FTP.
: [ 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.
Session state is stored on the server. The FTP server remembers that the client has already been authenticated at the beginning of the session, and remembers the directory in which the client is currently located.
Such an API is difficult to develop, debug and scale. Do not do this.
Eventually
Take JSON-RPC 2.0 if you decide to make an RPC API over HTTP or web sockets.
You can, of course, invent your bike, but why?
Take GraphQL if you really need it.
Take gRPC or something like that for communication between (micro) services, if your YaP supports it.
Take REST if you need it. Now you at least choose it consciously.