Cíle a principy návrhu GraphQL schématu
GraphQL schéma je kontrakt mezi klienty a serverem. Definuje tvary dat (typy), způsob jejich čtení (Query), změn (Mutation) a událostní proudy (Subscription). Návrh schématu a resolverů má řídit potřeby domény, nikoli fyzický model databáze. Klíčové charakteristiky kvalitního návrhu jsou: explicitní typy, jasná nulovatelnost, stabilní identita uzlů, dobře definované hranice (argumenty, filtry, stránkování) a pozorovatelnost (telemetrie, chybové kódy, latence).
Doménové modelování: od Ubiquitous Language k SDL
- Ubiquitous Language: nejprve popište subdomény, entity a jejich vztahy v jazyce byznysu. Vyhněte se databázovým pojmům v rozhraní.
- Agregáty a hranice: určete, které uzly mají globální identitu (např.
User,Order) a které jsou hodnotové objekty (Money,Address). - GraphQL SDL jako kontrakt: zapisujte typy s popisky (
""" … """) a direktivami pro nástroje (např.@deprecated).
Základní stavebnice schématu: typy, rozhraní, unie
- Object typy: reprezentují doménové entity. Příklad:
type User { id: ID!, email: String!, name: String, roles: [Role!]! } - Rozhraní (Interfaces): pro polymorfismus a sdílená pole. Příklad:
interface Node { id: ID! } - Unie (Union): pro variantní návraty bez společných polí. Příklad:
union SearchResult = User | Organization | Article - Vlastní skaláry:
scalar DateTime,scalar URL; vždy specifikujte formát a validaci. - Input typy: určeny pro argumenty, aby byly stabilní a rozšiřitelné. Příklad:
input UserFilter { email: String, role: Role, createdFrom: DateTime }
Nulovatelnost a kontrakt kvality dat
Operátor ! vyjadřuje, že hodnota nesmí být null. Používejte jej uvážlivě: přísná nulovatelnost zvyšuje spolehlivost klientů, ale vyžaduje pečlivé resolver strategie a deterministické chování při chybách. Držte se zásady: non-null pro identitu a klíčová pole, nullable pro volitelné a odvozené.
Kořenové operace: Query, Mutation, Subscription
- Query: navrhujte jako use-case orientované čtení (např.
userById(id: ID!): User,search(query: String!, first: Int, after: Cursor): SearchConnection!). - Mutation: používejte command slovesa a input typy (
createUser(input: CreateUserInput!): CreateUserPayload!) pro stabilitu a možnost rozšiřování oclientMutationId. - Subscription: explicitně popište semantiku streamu a filtry (např.
orderStatusChanged(orderId: ID!): OrderStatusEvent!); dbejte na autorizaci.
Stránkování a kolekce: offset vs. kurzory
- Offset: jednoduchý pro malé, stabilní seznamy; citlivý na změny v pořadí.
- Cursor-based (Relay Connection):
edges { node, cursor },pageInfo { hasNextPage, endCursor }; odolnější k mutacím v čase. Doporučeno pro velké seznamy. - Filtry a řazení: udržujte
filteraorderByjakoinputtypy; umožníte evoluci bez lámání kontraktu.
Resolver architektura: vrstvy a odpovědnosti
- Čisté resolvery: tenké adaptéry bez business logiky; orchestrují přístup ke službám, mapují data do tvaru schématu.
- Servisní vrstva: doménové služby, které řeší pravidla a transakce; reusable mimo GraphQL.
- DataLoader/batching: eliminace N+1 dotazů agregací požadavků podle klíče a cache na úroveň požadavku.
- Kontext: injektujte uživatele, tenant, locale, trace ID; nikoliv request-specifická cache s globálním dopadem.
Eliminace N+1 problému a strategie načítání
- DataLoader per request: batchujte by ID a cacheujte výsledky během jednoho dotazu.
- Projekce a selekce polí: přenášejte do datové vrstvy informaci, jaká pole jsou potřeba (např. field selection → SQL projekce).
- Joiny vs. následné dotazy: pro malé vztahy se vyplatí join/projekce; pro velké seznamy preferujte kurzorové listování.
Chybový model a návratové payloady
- Partial success: GraphQL umožňuje vrátit data i s chybami; proto navrhujte deterministickou nullability a chyby v
errorsse strojově čitelnýmextensions.code. - Mutation payload: vracejte
{ success: Boolean!, user: User, errors: [UserError!]! }s polem pro validace (field-level). - Business kódy: standardizujte
extensions.code(např.UNAUTHORIZED,FORBIDDEN,NOT_FOUND,CONFLICT).
Autentizace a autorizace
- Auth v kontextu: ověřte token, načtěte identitu a role dříve, než běží resolvery.
- Autorizace jako knihovna: nechte resolvery volat politiky (ABAC/RBAC) nebo použijte direktivy (např.
@auth(role: ADMIN)) mapované na validační logiku. - Field-level ochrana: citlivá pole (např.
User.email) kontrolujte na úrovni field resolveru, ne pouze na kořeni.
Evoluce schématu a kompatibilita
- Rozšiřování bez lámání: přidávejte volitelná pole, nové typy, nové unie; nepřidávejte
!tam, kde dříve nebyl. - Deprecation: používejte
@deprecated(reason: "...")a zveřejňujte plán odstranění. - Verzování: preferujte evoluční verzi v rámci jednoho endpointu; v2 endpoint pouze při zásadní refaktorizaci.
Federace a modulární schéma
- Schema stitching / federation: dělte monolit na doménové subgrafy (Users, Orders, Catalog) a skládejte přes gateway; dbejte na globální identitu a reference.
- Kontrakty mezi týmy: každý tým vlastní část schématu; gateway vynucuje kompozici, limity a observabilitu.
Výkon, cache a perzistentní dotazy
- Persisted Queries: pinujte hashované dotazy; snižujete riziko DoS via query text a zlepšujete TTFB.
- Cache vrstvy: response cache pro idempotentní dotazy, object cache (např. per
id), edge cache sCache-Controlpro @cacheable operace. - Limity a složitost: vypočítejte cost dotazu (hloubka, multiplikátory seznamů), zavedte rate limiting a timeouty per resolver.
Bezpečnost a odolnost
- Input validace: schvalte délky řetězců, formáty, rozsahy čísel; chraňte se proti Regex DoS a hluboké rekurzi.
- Alias a fragmenty: limitujte počet aliasů, hloubku fragmentů a cyklické reference.
- Observabilita: trace-id v kontextu, metriky latencí a počtu resolver volání, sampling a strukturované logy s poli
operationNameavariables(bez tajemství).
Testování a kvalita
- Jednotkové testy resolverů: mockujte služby a kontext; testujte autorizaci a chybové větve.
- Integrační testy operací: spouštějte skutečné GraphQL dotazy proti test serveru s in-memory DB.
- Kontraktační testy: validujte, že publikované SDL odpovídá očekávání klientů; využijte schema registry a breaking changes kontrolu v CI.
Praktický návrhový vzor pro mutace
Používejte input a payload dvojici pro každou mutaci, aby bylo snadné přidávat pole bez rozbití klientů. Příklad: input CreateUserInput { email: String!, name: String, role: Role = USER } a type CreateUserPayload { success: Boolean!, user: User, errors: [UserError!]! }.
Ukázkový výřez SDL pro blogovou doménu
interface Node { id: ID! }
type User implements Node { id: ID!, email: String!, name: String }
type Post implements Node { id: ID!, title: String!, body: String!, author: User! }
input PostFilter { authorId: ID, query: String }
type PostEdge { node: Post!, cursor: String! }
type PostConnection { edges: [PostEdge!]!, pageInfo: PageInfo! }
type PageInfo { hasNextPage: Boolean!, endCursor: String }
type Query { post(id: ID!): Post, posts(first: Int = 20, after: String, filter: PostFilter): PostConnection! }
input CreatePostInput { title: String!, body: String! }
type CreatePostPayload { success: Boolean!, post: Post, errors: [UserError!]! }
type Mutation { createPost(input: CreatePostInput!): CreatePostPayload! }
Resolvery: vzorce chování
- Kořenové resolvery (
Query/Mutation): validujte vstupy, kontrolujte práva, delegujte na doménové služby. - Field resolvery (např.
Post.author): používejte DataLoader pro batchloadManyautora podleauthorId. - Connection resolvery: implementujte kurzory jako bezpečné, neprůhledné (např. base64 s
id|sortKey), vracejte konzistentnípageInfo.
Lokalizace, měny a jednotky
- Locale v kontextu: předávejte jazyk a časové pásmo přes kontext a respektujte je v resolverech.
- Typy pro hodnoty: místo
Floatpoužívejte strukturální typy (Money { amount: Decimal!, currency: Currency! }) a pevné jednotky (Length,Weight).
Verifikovatelnost a dokumentace
- Popisy (docstrings): u každého typu a pole sjednoťte terminologii a uveďte příklady.
- Schema registry a changelog: publikujte SDL, trackujte změny a rozesílejte výstrahy při možném breaking change.
- Explorace: povolte GraphiQL/Explorer pouze v bezpečných prostředích; maskujte tajemství ve
variables.
Checklist pro finální revizi schématu
- Jsou ID stabilní a globálně jedinečné? Je
Nodekonzistentně implementováno? - Je nullability konzistentní a odpovídá skutečnému chování?
- Mají kolekce kurzorové stránkování a definované řazení?
- Jsou mutace idempotentní tam, kde je to žádoucí, a vrací payload s chybami?
- Jsou resolvery bez N+1 problémů, s batchingem a metrikami?
- Jsou autorizace a rate limity vynuceny na správných vrstvách?
Závěr: navrhněte kontrakt pro lidi, ne pro databázi
Úspěšné GraphQL schéma odráží doménu a potřeby klientů, ne fyzické tabulky. Dobře navržené typy, disciplinovaná nulovatelnost, promyšlené stránkování, čisté resolvery s batchingem, robustní chybový model a průběžná observabilita vedou k stabilnímu, výkonnému a bezpečnému API, které se může evolučně vyvíjet bez lámání klientů.