Konrad Kowalski (rootsher)Principal Platform & Reliability Architect100111000100011100011100011010001010101100111111

RPC: kiedy API modeluje operacje zamiast zasobów

data
kategoria
Backend
także w
Software Architecture · System Design
czytanie
1 min / 290 słów

REST zaczyna od zasobu:

http
POST /orders
PATCH /orders/42

RPC zaczyna od operacji:

http
POST /createOrder
POST /approveOrder
POST /cancelOrder

RPC oznacza Remote Procedure Call.

Mental model jest prosty:

text
local function call
  |
  v
network
  |
  v
remote execution

Procedura zamiast zasobu

Lokalnie moglibyśmy napisać:

ts
approveInvoice(42);

RPC próbuje zachować podobną semantykę przez granicę sieci:

http
POST /rpc/approveInvoice

{
  "invoiceId": 42
}

Backend mapuje request na operację domenową.

To często lepiej odpowiada systemom, w których najważniejsze są komendy, a nie CRUD.

RPC nie eliminuje HTTP

RPC jest modelem aplikacyjnym.

Transport nadal może być HTTP:

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

Możliwe jest nawet używanie jednego endpointu:

http
POST /rpc

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

Tak działa między innymi styl JSON-RPC.

Zaleta: semantyka domeny jest jawna

Porównaj:

http
PATCH /payments/42

{
  "status": "retried"
}

z:

http
POST /payments/42/retry

albo RPC:

text
retryPayment(42)

Druga forma mówi więcej o intencji.

Status nie zawsze jest danymi wejściowymi. Często jest skutkiem wykonania operacji.

RPC potrafi lepiej modelować takie przypadki.

Cena: tracimy część semantyki HTTP

Jeżeli wszystko jest:

http
POST /rpc

HTTP wie bardzo mało o znaczeniu operacji.

Nie może łatwo wywnioskować:

  • czy operacja jest safe,
  • czy jest idempotentna,
  • czy odpowiedź nadaje się do cache,
  • czy retry jest bezpieczny.

Semantyka przenosi się z warstwy HTTP do kontraktu aplikacji.

Dlatego RPC wymaga bardzo dobrego kontraktu.

Typed RPC

Nowoczesne systemy RPC często idą dalej:

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

gRPC używa do tego Protocol Buffers.

Inne narzędzia generują klienta bezpośrednio z definicji endpointów lub typów.

To zmniejsza dystans między:

ts
client.approveInvoice({ id: 42 })

a zwykłym wywołaniem funkcji.

Ale sieć nadal pozostaje siecią:

  • może wystąpić timeout,
  • request może dotrzeć mimo utraty odpowiedzi,
  • retry może wykonać operację ponownie,
  • klient i serwer mogą mieć różne wersje kontraktu.

RPC upraszcza interfejs, ale nie usuwa semantyki systemu rozproszonego.

Kolejne ograniczenie

REST i RPC mają wspólną cechę:

backend definiuje kształt endpointu i odpowiedzi.

Jeżeli różne ekrany potrzebują różnych podzbiorów i kombinacji danych, liczba endpointów lub wariantów odpowiedzi zaczyna rosnąć.

Można wtedy odwrócić model.

Zamiast pytać konkretny endpoint o z góry ustalony payload, klient może powiedzieć dokładnie, jakich danych potrzebuje.

To jest punkt wejścia do GraphQL.