Cíle a principy návrhu GraphQL schématu
GraphQL schéma je kontrakt mezi klienty a serverem. Definuje struktury dat (typy), způsoby jejich dotazování (Query), modifikace (Mutation) a eventové toky (Subscription). Návrh schématu a resolverů by měl vycházet z potřeb domény, nikoliv z fyzického modelu databáze. Klíčovými atributy kvalitního návrhu jsou: explicitní typy, jasná nulovatelnost, stabilní identita entit, 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 terminologii byznysu. Vyhněte se databázové terminologii ve veřejném rozhraní.
- Agregáty a hranice: určete, které uzly mají globální identitu (například
User,Order) a které jsou hodnotové objekty (Money,Address). - GraphQL SDL jako kontrakt: definujte typy s popisky (
""" … """) a direktivami pro nástroje (například@deprecated).
Základní stavební bloky 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): slouží pro polymorfismus a sdílené vlastnosti. Příklad:
interface Node { id: ID! } - Unie (Union): umožňují variantní návraty bez společných polí. Příklad:
union SearchResult = User | Organization | Article - Vlastní skaláry: například
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ě: striktní 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á pole.
Kořenové operace: Query, Mutation, Subscription
- Query: navrhujte je jako use-case orientované čtení (například
userById(id: ID!): User,search(query: String!, first: Int, after: Cursor): SearchConnection!). - Mutation: používejte příkazová slovesa a input typy (
createUser(input: CreateUserInput!): CreateUserPayload!) pro zachování stability a možnost rozšiřování oclientMutationId. - Subscription: explicitně definujte sémantiku streamu a filtry (např.
orderStatusChanged(orderId: ID!): OrderStatusEvent!); dbejte na autorizaci.
Stránkování a kolekce: offset vs. kurzory
- Offset: jednoduchý způsob vhodný pro malé a stabilní seznamy; však citlivý ke změnám pořadí položek.
- Cursor-based (Relay Connection): s strukturou
edges { node, cursor }apageInfo { hasNextPage, endCursor }; odolnější vůči mutacím během zpracování. Doporučeno pro velké seznamy. - Filtry a řazení: udržujte
filteraorderByjakoinputtypy, což umožní evoluci rozhraní bez narušení kontraktu.
Resolver architektura: vrstvy a odpovědnosti
- Čisté resolvery: tenké adaptéry bez business logiky; orchestrují přístup ke službám a mapují data do formátu schématu.
- Servisní vrstva: doménové služby řešící pravidla a transakce; použitelné i mimo GraphQL kontext.
- DataLoader/batching: eliminace N+1 problému agregací požadavků podle klíče a caching na úrovni jednoho dotazu.
- Kontext: injektujte uživatele, tenant, locale, trace ID; nikoliv cache s globálním dopadem specifickou pro request.
Eliminace N+1 problému a strategie načítání
- DataLoader na dotaz: batchujte dotazy podle ID a cacheujte výsledky v rámci jednoho požadavku.
- Projekce a selekce polí: předávejte do datové vrstvy informaci o požadovaných polích (např. výběr polí → SQL projekce).
- Joiny vs. následné dotazy: u malých relací se vyplatí spojování/projekce; u velkých seznamů preferujte kurzorové stránkování.
Chybový model a návratové payloady
- Částečný úspěch: GraphQL umožňuje vrátit data i s chybami; proto navrhujte deterministickou nullabilitu a chyby v
errorsse strojově čitelnýmextensions.code. - Mutation payload: vracejte objekt
{ success: Boolean!, user: User, errors: [UserError!]! }s polem pro validaci na úrovni polí. - Business kódy: standardizujte hodnoty
extensions.code(napříkladUNAUTHORIZED,FORBIDDEN,NOT_FOUND,CONFLICT).
Autentizace a autorizace
- Autentizace v kontextu: ověřte token, načtěte identitu a role před spuštěním resolverů.
- Autorizace jako knihovna: nechte resolvery volat politiky (ABAC/RBAC) nebo použijte direktivy (např.
@auth(role: ADMIN)) mapované na validační logiku. - Ochrana na úrovni polí: citlivá pole (například
User.email) kontrolujte v resolvere pole, ne pouze na kořenové úrovni.
Evoluce schématu a kompatibilita
- Rozšiřování bez porušení kompatibility: 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 postupnou evoluci v rámci jednoho endpointu; verzi v2 zaveďte pouze při zásadní refaktorizaci.
Federace a modulární schéma
- Schema stitching / federation: rozdělte monolit na doménové subgrafy (Users, Orders, Catalog) a skládejte je přes gateway; dbajte na globální identitu a reference.
- Kontrakty mezi týmy: každý tým spravuje část schématu; gateway vynucuje kompozici, limity a observabilitu.
Výkon, cache a perzistentní dotazy
- Persisted Queries: ukládejte hashované dotazy; snižujete riziko DoS útoků pomocí textu dotazu a zlepšujete čas do první odpovědi (TTFB).
- Cache vrstvy: response cache pro idempotentní dotazy, object cache (například per
id), edge cache s hlavičkouCache-Controlpro @cacheable operace. - Limity a složitost: kalkulujte nákladnost dotazu (hloubka, multiplikátory pro seznamy), zaveďte rate limiting a timeouty na jednotlivé resolvery.
Bezpečnost a odolnost
- Validace vstupů: kontrolujte délku řetězců, formáty, rozsahy čísel; chraňte se proti Regex DoS a hluboké rekurzi.
- Alias a fragmenty: omezujte počet aliasů, hloubku fragmentů a cyklické reference.
- Observabilita: trace-id v kontextu, metriky latence a počtu volání resolverů, sampling a strukturované logy s poli
operationNameavariables(bez citlivých údajů).
Testování a kvalita
- Jednotkové testy resolverů: mockujte služby a kontext; testujte autorizaci a chybové scénáře.
- Integrační testy operací: spouštějte skutečné GraphQL dotazy proti testovacímu serveru s in-memory databází.
- Kontraktační testy: ověřujte, že zveřejněné SDL odpovídá očekáváním klientů; využijte schema registry a kontrolu breaking changes v CI pipeline.
Praktický návrhový vzor pro mutace
Používejte input a payload dvojici pro každou mutaci, aby bylo možné 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 oprávnění, delegujte na doménové služby. - Field resolvery (například
Post.author): používejte DataLoader pro dávkové načítání více autorů podleauthorId. - Connection resolvery: implementujte kurzory jako bezpečné, neprůhledné (například base64 kódování
id|sortKey), vracejte konzistentnípageInfo.
Lokalizace, měny a jednotky
- Locale v kontextu: přenášejte 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 použití.
- Schema registry a changelog: publikujte SDL, sledujte změny a zasílejte upozornění na možné breaking changes.
- Explorace: zpřístupněte GraphiQL/Explorer pouze v bezpečných prostředích; maskujte citlivá data 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á reálnému chování?
- Mají kolekce kurzorové stránkování a definované řazení?
- Jsou mutace idempotentní tam, kde je to žádoucí, a vracejí payload s chybami?
- Jsou resolvery odolné vůči N+1 problémům, s dávkováním a metrikami?
- Jsou autorizace a rate limity vynuceny na správných úrovních?
Závěr: navrhněte kontrakt pro lidi, ne pro databázi
Úspěšné GraphQL schéma reflektuje doménu a potřeby klientů, nikoli fyzickou strukturu tabulek. Pečlivě navržené typy, disciplinovaná nullabilita, promyšlené stránkování, čisté resolvery s dávkováním, robustní chybový model a průběžná observabilita vedou k stabilnímu, výkonnému a bezpečnému API, které může být evolučně rozšiřováno bez narušení klientských aplikací.


























