Konrad Kowalski (rootsher)Principal Platform & Reliability Architect000001010000110111010101101001010111110001100100

GraphQL: klient pyta o kształt danych

data
kategoria
Backend
także w
Frontend · System Design
czytanie
1 min / 298 słów

REST zwykle wiąże odpowiedź z zasobem.

RPC wiąże ją z operacją.

GraphQL wiąże ją z zapytaniem klienta.

graphql
query {
  user(id: 42) {
    name
    orders {
      id
      total
    }
  }
}

Klient definiuje kształt potrzebnych danych.

Jeden endpoint, wiele zapytań

Typowy request wygląda tak:

http
POST /graphql
Content-Type: application/json

Body zawiera dokument GraphQL.

HTTP nie mówi już:

text
jaki zasób pobieramy?

To opisuje warstwa GraphQL.

text
HTTP
  |
  v
GraphQL document
  |
  v
schema
  |
  v
execution

Schema jest kontraktem

Przykład:

graphql
type User {
  id: ID!
  name: String!
  orders: [Order!]!
}

type Query {
  user(id: ID!): User
}

Schema definiuje graf typów oraz operacje dostępne klientowi.

Zapytanie jest walidowane względem schematu przed wykonaniem.

Daje to bardzo mocny kontrakt i świetne możliwości narzędziowe.

Resolvery

Każde pole może być rozwiązywane przez kod backendu.

Uproszczenie:

text
Query.user
  |
  v
User.orders
  |
  v
Order.total

To pozwala składać dane z wielu źródeł bez ujawniania tej struktury klientowi.

Ale prowadzi do jednego z klasycznych problemów GraphQL.

N+1

Załóżmy:

graphql
query {
  users {
    orders {
      id
    }
  }
}

Naiwna implementacja może wykonać:

text
1 query -> users
N queries -> orders for each user

Czyli N+1.

GraphQL nie powoduje tego automatycznie, ale jego model resolverów bardzo łatwo taki problem ujawnia.

Typowym rozwiązaniem jest batching i request-scoped caching, np. przez DataLoader-like pattern.

Elastyczność kosztuje

W REST backend zna koszt endpointu z dużym wyprzedzeniem.

W GraphQL klient może budować różne drzewa zapytań.

Dlatego trzeba kontrolować:

  • depth,
  • complexity,
  • pagination,
  • expensive fields,
  • authorization per field,
  • query limits.

API staje się bardziej elastyczne, ale wykonanie mniej przewidywalne.

Caching też jest trudniejszy

HTTP cache dobrze rozumie:

http
GET /users/42

Dużo gorzej radzi sobie z:

http
POST /graphql

gdzie ten sam URL może reprezentować tysiące różnych zapytań.

GraphQL zwykle przesuwa większą część cache do warstwy aplikacyjnej klienta.

Klient może normalizować dane według identyfikatorów:

text
User:42
Order:17
Order:18

To potężne, ale znów: większa elastyczność oznacza więcej mechanizmu po stronie aplikacji.

REST, RPC czy GraphQL?

Nie istnieje hierarchia:

text
REST < RPC < GraphQL

To różne modele.

text
REST
-> zasoby i semantyka HTTP

RPC
-> operacje i kontrakt procedur

GraphQL
-> graf danych i query language

Wszystkie mogą używać tego samego transportu HTTP i wszystkie mogą być wywoływane przez fetch().

To właśnie jest najważniejszy wniosek całej serii:

text
Fetch        = browser API
HTTP         = protocol
REST / RPC   = API architecture
GraphQL      = application query protocol

Te warstwy rozwiązują różne problemy.

Mieszanie ich prowadzi do dyskusji typu „GraphQL vs HTTP” albo „fetch vs REST”, które porównują rzeczy z innych poziomów abstrakcji.

Dopiero po rozdzieleniu tych warstw można sensownie projektować komunikację frontend ↔ backend.