
REST? Take a dumb JSON-RPC

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
id
client 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}/status
or 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.