Konrad Kowalski (rootsher)Principal Platform & Reliability Architect000100111000110011001011101010101001010101000110

RPC: When an API Models Operations Instead of Resources

date
category
Backend
also in
Software Architecture · System Design
reading
2 min / 372 words

REST starts from a resource:

http
POST /orders
PATCH /orders/42

RPC starts from an operation:

http
POST /createOrder
POST /approveOrder
POST /cancelOrder

RPC stands for Remote Procedure Call.

The mental model is simple:

text
local function call
  |
  v
network
  |
  v
remote execution

A procedure instead of a resource

Locally we could write:

ts
approveInvoice(42);

RPC tries to keep similar semantics across the network boundary:

http
POST /rpc/approveInvoice

{
  "invoiceId": 42
}

The backend maps the request onto a domain operation.

That often fits systems where commands matter more than CRUD.

RPC does not eliminate HTTP

RPC is an application model.

The transport can still be HTTP:

text
RPC method
  |
  v
HTTP request
  |
  v
TCP/QUIC

It is even possible to use a single endpoint:

http
POST /rpc

{
  "method": "approveInvoice",
  "params": {
    "invoiceId": 42
  }
}

That is how the JSON-RPC style works, among others.

The upside: domain semantics are explicit

Compare:

http
PATCH /payments/42

{
  "status": "retried"
}

with:

http
POST /payments/42/retry

or with RPC:

text
retryPayment(42)

The second form says more about intent.

A status is not always input data. It is often the result of performing an operation.

RPC can model such cases better.

The price: part of the HTTP semantics is gone

If everything is:

http
POST /rpc

HTTP knows very little about the meaning of the operation.

It cannot easily tell:

  • whether the operation is safe,
  • whether it is idempotent,
  • whether the response can be cached,
  • whether a retry is safe.

The semantics move from the HTTP layer into the application contract.

That is why RPC needs a very good contract.

Typed RPC

Modern RPC systems often go further:

text
schema
  |
  v
code generation
  |
  v
typed client
  |
  v
typed server

gRPC uses Protocol Buffers for that.

Other tools generate the client straight from endpoint or type definitions.

That shortens the distance between:

ts
client.approveInvoice({ id: 42 })

and an ordinary function call.

But the network remains the network:

  • a timeout can happen,
  • a request can arrive even when the response is lost,
  • a retry can perform the operation again,
  • the client and the server can hold different versions of the contract.

RPC simplifies the interface, but it does not remove the semantics of a distributed system.

The next limitation

REST and RPC share one property:

the backend defines the shape of the endpoint and of the response.

If different screens need different subsets and combinations of data, the number of endpoints or response variants starts to grow.

At that point the model can be inverted.

Instead of asking a particular endpoint for a predetermined payload, the client can state exactly which data it needs.

That is the entry point to GraphQL.