federation stárne špatně
GraphQL federation vypadá chytře u třetí služby, možná u čtvrté. Demo je působivé, každý tým má autonomii, produkt má jeden endpoint, frontend si řekne přesně o ta pole, která chce, všichni kývou, a za půl roku máte koordinační mašinu převlečenou za architekturu. Tenhle pattern jsem viděl už mockrát a ten sales pitch už nekupuju. Aspoň ne u startupů v mid-stage fázi, které jedou něco kolem deseti služeb, mají jeden platform tým a pár produktových squad, které se snaží shipovat reálné feature v reálných deadlinách.
Ta bolest není teoretická. Ta bolest je úterý odpoledne, někdo přidá nullable field do subgraphu, resolver pořád hází chybu na chybějícím join key, gateway se bez problému zkomponuje, staging projde, protože happy path query fungovala, a pak production začne vracet partial data a hromadu errors: [{ message: "Cannot return null for non-nullable field" }], které klient ignoruje, protože HTTP status je 200. To je špatný návrh systému. Objevování chyb jste přesunuli z compile time do distribuovaného runtime a celé jste to obalili protokolem, který spotřebitele svádí k tomu, aby si mysleli, že jsou v bezpečí, protože mají typy vygenerované ze supergraphu.
Schema governance se zvrhne rychle. Každá změna napříč týmy se změní v malý normalizační výbor: pojmenovávání argumentů, debaty o ownership entit, rozhodování, kam které pole patří, čekání na composition checky, replay query a pokusy pochopit N+1 chování, které je teď rozsekané přes několik resolver chainů. Gateway se promění v integrační džungli, místo, kde se vrství auth, caching, fanout, field-level joiny, převody shape a tribal knowledge, až do chvíle, kdy na to v pátek nikdo nechce sáhnout.
Spousta týmů si plete centralizaci query shape se zjednodušením systému. To není totéž. Jeden graph pořád může schovávat bordel, a v praxi ho skoro vždycky schovává.
náhrada postavená na kontraktech
Náhrada, kterou doporučuju, je záměrně nudná: OpenAPI 3.1 specifikace vlastněné po doménách, machine-generated klienti commitnutí v consumer repozitářích, consumer-driven contract testy, codegen v CI a velmi tenká edge vrstva, která řeší auth, rate limiting a request routing, ale netváří se jako nějaký orchestration mozek. Tohle má méně pohyblivých částí, lepší failure modes a výrazně čistší model ownershipu.
OpenAPI 3.1 konečně opravilo většinu historických důvodů, proč ho lidi shazovali. Zarovnání s JSON Schema je důležité, oneOf a discriminators jsou použitelné, nullable semantics přestaly být divné, examples a webhooks jsou first-class a tooling kolem TypeScriptu je o míle dál než před pár lety. Na Next.js frontendu nebo v React Native aplikaci generujeme klienty přes openapi-typescript a malý wrapper nad fetch, pak v CI pustíme tsc --noEmit, takže se rozbití consumera ukáže před merge, ne až po deploymentu.
Minimální setup vypadá takhle:
1openapi: 3.1.02info:3 title: billing-api4 version: 1.4.05paths:6 /customers/{customerId}/invoices:7 get:8 operationId: listCustomerInvoices9 parameters:10 - name: customerId11 in: path12 required: true13 schema:14 type: string15 format: uuid16 responses:17 '200':18 description: OK19 content:20 application/json:21 schema:22 $ref: '#/components/schemas/InvoiceList'23components:24 schemas:25 InvoiceList:26 type: object27 required: [items]28 properties:29 items:30 type: array31 items:32 $ref: '#/components/schemas/Invoice'33 Invoice:34 type: object35 required: [id, status, totalCents]36 properties:37 id: { type: string, format: uuid }38 status: { type: string, enum: [draft, open, paid, void] }39 totalCents: { type: integer }
Pak v CI:
1{2 "scripts": {3 "codegen": "openapi-typescript ./openapi/billing.yaml -o ./src/gen/billing.ts",4 "typecheck": "tsc --noEmit",5 "contracts": "pnpm pact:verify"6 }7}
Consumer tím získá statické garance vůči producer kontraktu a producer si pořád drží ownership implementačních detailů. Bez jakéhokoliv gateway composition divadla.
runtime chyby se přestanou schovávat
Nejsilnější argument pro tenhle přístup není o vkusu. Je o tom, kde se objeví chyby. Federation schovává příliš mnoho failů za úspěšný transport. Graph může vrátit 200 s půlkou stránky pryč, timeout resolveru může degradovat jednu větev query, zatímco zbytek projde, a klient musí číst errors[] i data zároveň, aby pochopil, co se vlastně stalo. Většina týmů tohle nemodeluje poctivě, jen pattern-matchuje na generated hooky nebo SDK metody a bere vyřešený promise jako validní data.
REST s explicitními kontrakty je mnohem méně magický, a právě proto se lépe provozuje. Když GET /customers/:id/invoices rozbije shape response, v consumer CI exploduje vygenerovaný TypeScript. Když producer změní chování, Pact verification spadne před deployem. Když route leží, dostanete 5xx a observability nástroje přesně vědí, co s tím. Error budgets, alerting, retries, chování CDN, cache keys, auth policies, tohle všechno už umí s HTTP pracovat bez přemlouvání.
Consumer-driven contract testy jsou důležité proto, že produceři jsou zoufale špatní v odhadu toho, na čem consumeři opravdu závisejí. Backend tým vám zcela upřímně řekne, že odstranění invoice.totalCents je safe, protože webová aplikace si umí spočítat zobrazované hodnoty z line itemů, a pak se ozve mobile, že to pole používá v offline view, a support dashboard má CSV export, který stojí na tom přesném integeru. Pact tohle chytí. Nám se osvědčilo ukládat pacty po consumerech, ověřovat je v provider pipeline a blokovat release, když verification spadne. Jednoduché pravidlo, žádné drama.
Jeden konkrétní pattern funguje dobře: breaking sémantické změny verzujte v path jen tehdy, když je to nutné, aditivní změny nechávejte na místě a deprecations zapisujte do specifikace i s daty. Lidé to přečtou. Tooling to vynutí. Nikdo nemusí v hlavě simulovat graph planner.
jak jsme zkrátili čas na změnu
Na jedné migraci ve steezr se stack o 12 službách dostal do klasického stavu: vpředu GraphQL gateway, za ní Django a Go služby, pár interních Node služeb na zpracování dokumentů, všude PostgreSQL, uprostřed Redis a frontend tým, který se naučil bát schema změn, protože každá netriviální feature překračovala hranice tří týmů. Mean time to change pro cokoliv, co se dotýkalo customer accounts, byl zhruba osm pracovních dní, někdy víc, a většina času padla na koordinaci, ne na kódování.
Nešli jsme do žádného dramatického přepisu. To by bylo nezodpovědné. Začali jsme inventurou reálného consumer trafficu z gateway logů, seskupili operace po doménách a napsali OpenAPI 3.1 specifikace pro patnáct nejdůležitějších workflow, které dělaly asi 80 % user-facing trafficu. Pak jsme před existující služby postavili tenkou HTTP edge vrstvu, Envoy na routing a propagaci auth kontextu, bez fancy BFF logiky, bez agregace, kromě několika endpointů, kde agregace byla skutečně stabilní a patřila do domény.
Klienti se generovali po consumerech. Web dostal čisté TypeScript typy a malý wrapper kolem fetch, native dostal stejný contract package přes shared workspace, interní nástroje na HTMX volaly endpointy napřímo, protože generované SDK nepotřebovaly. Do provider repozitářů jsme přidali contract verification joby. Consumer repozitáře pinovaly verze specifikací, regenerovaly v CI a failovaly hned při breaking change. Zjednodušený GitHub Actions job vypadal takhle:
1name: contract-check2on: [pull_request]3jobs:4 verify:5 runs-on: ubuntu-latest6 steps:7 - uses: actions/checkout@v48 - uses: pnpm/action-setup@v49 - uses: actions/setup-node@v410 with:11 node-version: 2212 cache: pnpm13 - run: pnpm install --frozen-lockfile14 - run: pnpm codegen15 - run: pnpm typecheck16 - run: pnpm contracts
Výsledek byl okamžitý. Mean time to change spadl během dvou měsíců na zhruba čtyři dny, hlavně proto, že frontend a backend se zase mohly hýbat nezávisle. Chyby byly viditelnější. Ownership byl jasnější. Práce na gateway přestala být specialist bottleneck.
námitky neobstojí
Standardní námitka je flexibilita frontendu: týmy chtějí ad hoc query shape, graph jim dává svobodu, REST vytváří endpoint sprawl. Tohle obvykle říkají týmy, které se nepodívaly poctivě na svůj traffic. Většina produkčních aplikací nemá nekonečnou variabilitu query, má malou sadu stabilních obrazovek a workflow, a ty se mapují docela čistě na resource nebo task-oriented endpointy. Jestli opravdu potřebujete custom projekce pro reportingový screen, přidejte sparse fieldsets nebo purpose-built read endpointy. Nestavte distribuovaný query planner pro celou firmu jen proto, že jeden dashboard chce pět joinů.
Další námitka je over-fetching. Jasně, existuje, ale skoro každý tým dramaticky přeceňuje jeho cenu ve srovnání s provozní cenou federation. Poslat o 1,5 KB JSONu navíc přes HTTP/2 nebo HTTP/3 je málokdy váš bottleneck. Skutečné bottlenecky jsou neomezený fanout přes subgraphy, skryté resolver waterfalls a query variabilita, která zabíjí cache. Radši vezmu o něco tlustší response než graph, kde potřebujete Apollo Router experty, aby vysvětlili, proč p95 po nenápadné schema změně vyskočilo ze 180 ms na 900 ms.
Pak je tu vyspělost toolingu. Lidi si myslí, že GraphQL tooling je příjemnější, protože introspection a codegen působí uhlazeně. OpenAPI tooling je v roce 2026 v pohodě, spíš víc než v pohodě, zvlášť pokud jsou vaši consumeři hodně na TypeScriptu. openapi-typescript, Redocly CLI, Stoplight Spectral, Pact, Dredd, pokud ho ještě chcete, Prism na mocking, tohle všechno funguje. Můžete lintovat specifikace, diffovat je v CI, generovat SDK, publikovat dokumentaci a vynucovat style rules. Na nic z toho nepotřebujete centrální platform tým v roli kněžstva.
Federation použijte tehdy, když opravdu máte hodně nezávislých domén, hodně nezávislých týmů a silnou platform skupinu, která bere graph governance jako produkt. Většina startupů tohle nemá. Má šest až patnáct služeb, jednu nebo dvě sdílené databáze, kterých se snaží zbavit, pár inženýrů v několika rolích najednou a žádný volný headcount na architektonickou marnivost.
rozumnější default
Můj default stack pro tenhle typ firmy je docela přímočarý: doménové HTTP API popsané v OpenAPI 3.1, generované TypeScript klienty používané v Next.js a React Native aplikacích, provider verification přes Pact, lint specifikací přes Spectral, codegen a tsc --noEmit v každém pull requestu, a na edge Envoy nebo NGINX na routing, auth a případně velmi lehké tvarování response tam, kde to dává smysl. Tohle odpovídá tomu, jak týmy reálně pracují. Stejně tak to odpovídá tomu, jak systémy selhávají v produkci, viditelně a debuggovatelně.
Praktické rozložení repozitáře pomůže hodně:
1/contracts2 /billing/openapi.yaml3 /accounts/openapi.yaml4/services5 /billing-api6 /accounts-api7/apps8 /web9 /mobile10/packages11 /api-clients
Držte kontrakty blízko kódu, publikujte vygenerované klienty jako workspace packages a breaking changes dělejte záměrně drahé. Dobrý CI gate je nudný: redocly lint, openapi-diff, openapi-typescript, tsc, Pact verification, hotovo. Když někdo změní response z totalCents: integer na total: string, automatizace by mu měla během pár minut jednu vrazit.
Tohle je jeden z těch architektonických callů, kde nuda vyhrává. Startupy v mid-stage fázi potřebují rychlost změn, předvídatelné chyby a systémy, kterým senior inženýr porozumí po hodině čtení repozitáře. Federation obvykle tlačí přesně opačným směrem. Typed OpenAPI plus consumer-driven contracts vás vrátí k systému postavenému kolem delivery, ne kolem ceremonie. A přesně tam měla většina týmů zůstat už od začátku.
