Ambrosius Topor

Notiz — 2024-01-04

Reasoning using JSON-RPC

While everybody seems to use either (some sort of) HTTP API or GraphQL for their APIs, I have decided to utilize JSON-RPC as a connector. In this post I want to elaborate on the reasoning for my decision.

An Example: A CMS

Having used HTTP APIsFn myself for several years, there where certain situations when things started to look odd.

Lets say you want to develop a CMS. One of the elements are pages; things start simple:

GET /pages
GET /page/1

As we can see, there is an endpoint to retrieve all pages (maybe optionally filtered by some criteria — one could use a query parameter for that), and another one to retrieve only a single page (by its identifier).

For creation, modification, deletion, you would use the usual HTTP verbs: POST, PUT or PATCH, DELETE.

The Problem

Now lets say you want editors to be able to sort pages manually.
A naive approach would be to update each page item individually (potentially making a lot requests):

PATCH /page/1

{
    position: 3
}

An alternative would be to use a separate "resource attribute" endpoint:

PATCH /page/1/position

3

Another approach would be to use a "virtual resource" endpoint to perform this operation in one step (as there is no resource, I'm using the POST HTTP verb in this case):

POST /pages/set-positions

[
    {
        id: 5
        position: 3
    }
    {
        id: 8
        position: 1
    }
]

Of course you could use an array:

POST /pages/set-positions

[8, null, 5]

While all these approaches work, there is—at least in my opinion—a feeling that something is wrong.

Meet JSON-RPC

What if we would instead describe clearly what our intention is, instead of trying to navigate around shortcomings of the HTTP API method?
This is where JSON-RPCFn (or generally a remote procedure call specification) comes into play.

Given the preeding example, a call would look like this:

{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "set_pages_position",
    "params": {
        "changes": [
            { "id": 5, "position": 3 },
            { "id": 8, "position": 1 }
        ]
    }
}

(In this case, JSON PatchFn might be a good utilization to describe changes)
As JSON-RPC also supports batch processing (in one call), this could also be split to a more reusable solution (use on a single page, or on multiple pages):

[
    {
        "jsonrpc": "2.0",
        "id": 1,
        "method": "set_page_position",
        "params": {
            "id": 5,
            "position": 3
        }
    },
    {
        "jsonrpc": "2.0",
        "id": 2,
        "method": "set_page_position",
        "params": {
            "id": 8,
            "position": 1
        }
    }
]

Like there is OpenAPI for HTTP APIs, to describe/specify JSON-RPC, there is OpenRPCFn available.

Benefit: Transport Agnostic

The above mentioned examples would usually target a HTTP endpoint specific to handling JSON-RPC requests:

POST /json-rpc

But as JSON-RPC is transport agnostic, you could also use other methods to perform actions (e.g. use a file based strategy for Electron desktop applications; I have been using this in my "Electronize Web Applications"Fn approach). If you don't care about latency, you could also decide to use an Avian Carrier transportation typeFn.

It also makes it easier to test, as there is no need to mock server calls with all the different HTTP verb variations.

Benefit: Explicitness

Using well thought method names, one can quickly grasp the intention behind it. Contrast this with an HTTP API, where you (probably) have to inspect several places (endpoint, HTTP verb) to figure out the operation to be performed.

This also makes it easy to implement a permission system, based solely on the method names.

References

Comments

There are no comments yet.

Thanks for your contribution!
Your comment will be visible once it has been approved.

An error occured—please try again.

Add Comment