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.
query {
user(id: 42) {
name
orders {
id
total
}
}
}
Klient definiuje kształt potrzebnych danych.
Jeden endpoint, wiele zapytań
Typowy request wygląda tak:
POST /graphql
Content-Type: application/json
Body zawiera dokument GraphQL.
HTTP nie mówi już:
jaki zasób pobieramy?
To opisuje warstwa GraphQL.
HTTP
|
v
GraphQL document
|
v
schema
|
v
execution
Schema jest kontraktem
Przykład:
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:
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:
query {
users {
orders {
id
}
}
}
Naiwna implementacja może wykonać:
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:
GET /users/42
Dużo gorzej radzi sobie z:
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:
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:
REST < RPC < GraphQL
To różne modele.
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:
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.