Návrh GraphQL schématu a resolver: Implementace

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í o clientMutationId.
  • 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 filter a orderBy jako input typy; 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 errors se strojově čitelným extensions.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 s Cache-Control pro @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 operationName a variables (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 batch loadMany autora podle authorId.
  • 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 Float použí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 Node konzistentně 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ů.

Pridaj komentár

Vaša e-mailová adresa nebude zverejnená. Vyžadované polia sú označené *