Přejít k navigační liště

Zdroják » Webový vývoj » GraphQL: Alternativa k REST API zblízka

GraphQL: Alternativa k REST API zblízka

Články Webový vývoj

Když se dnes navrhuje nové API, většina vývojářů automaticky sáhne po RESTu. Je to pochopitelné: REST je jednoduchý, dobře zdokumentovaný, podporují ho všechny jazyky a frameworky a většina z nás s ním pracuje léta. Přesto se v posledních letech stále častěji objevuje otázka, zda je REST vždy tou nejlepší volbou. Nejčastěji zmiňovanou alternativou je GraphQL, dotazovací jazyk pro API, který vznikl ve Facebooku a který dnes používají firmy jako GitHub, Shopify nebo Netflix.

Nálepky:

V tomto článku se podíváme, jak GraphQL funguje, jaké problémy řeší, kde naopak přináší nové komplikace a podle čeho se rozhodnout, zda se pro váš projekt hodí.

Trocha historie

GraphQL vznikl ve Facebooku v roce 2012. Firma tehdy přepisovala svou mobilní aplikaci a narazila na problém: zobrazení jediné obrazovky, například novinek od přátel, vyžadovalo data z mnoha různých zdrojů. S klasickým REST API to znamenalo buď spoustu samostatných požadavků, nebo vytváření specializovaných endpointů pro každou obrazovku. Obojí bylo na pomalých mobilních sítích nepraktické a z dlouhodobého hlediska těžko udržovatelné.

Inženýři proto navrhli jazyk, ve kterém si klient sám popíše, jaká data potřebuje, a server mu vrátí přesně je. V roce 2015 Facebook zveřejnil specifikaci a referenční implementaci v JavaScriptu. O tři roky později se projekt přesunul pod nezávislou GraphQL Foundation, kterou zastřešuje Linux Foundation. Důležité je, že GraphQL není knihovna ani databáze, ale specifikace. Implementace existují pro prakticky všechny rozšířené jazyky, od JavaScriptu přes Javu, C# a Python až po Go nebo PHP.

Základní myšlenka: klient říká, co chce

Nejlépe se rozdíl mezi REST a GraphQL ukáže na příkladu. Představme si blog, kde chceme zobrazit článek, jméno jeho autora a titulky tří posledních komentářů.

V REST API bychom pravděpodobně zavolali několik endpointů:

GET /api/articles/42
GET /api/users/7
GET /api/articles/42/comments?limit=3

Každá odpověď navíc obsahuje všechna pole, která server u daného zdroje vrací, i když je nepotřebujeme. Uživatel může mít v odpovědi e-mail, datum registrace, avatar a další údaje, přestože nás zajímá jen jméno.

V GraphQL pošleme jediný dotaz:

query {
  article(id: 42) {
    title
    content
    author {
      name
    }
    comments(last: 3) {
      text
    }
  }
}

A dostaneme odpověď, jejíž struktura přesně kopíruje tvar dotazu:

{
  "data": {
    "article": {
      "title": "GraphQL zblízka",
      "content": "…",
      "author": { "name": "Jana Nováková" },
      "comments": [
        { "text": "Skvělý článek!" },
        { "text": "Díky za příklady." },
        { "text": "Kdy bude pokračování?" }
      ]
    }
  }
}
Code language: JSON / JSON with Comments (json)

Tím GraphQL řeší dva klasické problémy REST API. Prvním je takzvaný over-fetching, tedy stahování dat, která klient nepotřebuje. Druhým je under-fetching, kdy jeden endpoint nevrátí vše potřebné a klient musí posílat další požadavky.

Schéma jako smlouva

Srdcem každého GraphQL API je schéma. Popisuje všechny typy dat, jejich pole a vztahy mezi nimi. Zapisuje se v jazyce SDL (Schema Definition Language):

type Article {
  id: ID!
  title: String!
  content: String!
  author: User!
  comments(last: Int): [Comment!]!
}

type User {
  id: ID!
  name: String!
  email: String
}

type Comment {
  id: ID!
  text: String!
  author: User!
}

type Query {
  article(id: ID!): Article
  articles(first: Int, after: String): [Article!]!
}
Code language: JavaScript (javascript)

Vykřičník označuje pole, které nesmí mít hodnotu null, hranaté závorky označují seznam. Typ Query je vstupním bodem pro čtení dat.

Schéma plní roli smlouvy mezi frontendem a backendem. Díky silnému typovému systému server každý dotaz před spuštěním zvaliduje a nesmyslný požadavek odmítne ještě předtím, než se dotkne databáze. Schéma je navíc introspektivní: klient se může serveru zeptat, jaké typy a pole nabízí. Na tom stojí nástroje jako GraphiQL nebo Apollo Sandbox, které poskytují automatické doplňování, dokumentaci a validaci přímo v prohlížeči. Dokumentace tak vzniká „zadarmo“ a nemůže se rozejít se skutečností, protože vychází přímo z běžícího API.

Dotazy, mutace a odběry

GraphQL rozlišuje tři typy operací.

Dotazy (queries) slouží ke čtení dat, jak jsme viděli výše. Mohou obsahovat proměnné, aliasy a fragmenty, tedy znovupoužitelné části výběru polí:

query ArticleDetail($id: ID!) {
  article(id: $id) {
    ...ArticleFields
  }
}

fragment ArticleFields on Article {
  title
  author { name }
}
Code language: PHP (php)

Mutace (mutations) slouží ke změně dat. Obdobně jako u dotazů si klient určí, jaká data chce po provedení změny zpátky:

mutation {
  addComment(articleId: 42, text: "Výborně napsáno") {
    id
    text
    author { name }
  }
}
Code language: JavaScript (javascript)

Odběry (subscriptions) umožňují přijímat data v reálném čase, typicky přes WebSocket. Klient se přihlásí k odběru určité události, například nového komentáře, a server mu data pošle ve chvíli, kdy k ní dojde.

Jak to funguje na serveru: resolvery

Schéma popisuje, jaká data API nabízí. Odkud se berou, určují resolvery. Resolver je obyčejná funkce, která vrací hodnotu pro konkrétní pole. Zjednodušený příklad v JavaScriptu s knihovnou Apollo Server:

const resolvers = {
  Query: {
    article: (_, { id }, { db }) => db.articles.findById(id),
  },
  Article: {
    author: (article, _, { db }) => db.users.findById(article.authorId),
    comments: (article, { last }, { db }) =>
      db.comments.findLatest(article.id, last),
  },
};
Code language: JavaScript (javascript)

GraphQL server při zpracování dotazu prochází strom požadovaných polí a pro každé z nich zavolá příslušný resolver. Díky tomu je jedno, zda data pocházejí z relační databáze, z jiné mikroslužby, nebo dokonce ze staršího REST API. GraphQL se proto často nasazuje jako sjednocující vrstva nad několika různými zdroji dat.

Problém N+1 a DataLoader

Právě popsaný mechanismus má jednu zákeřnou vlastnost. Představme si dotaz na seznam dvaceti článků včetně jejich autorů. Resolver pro articles provede jeden dotaz do databáze, ale resolver pro author se pak zavolá dvacetkrát, pokaždé s vlastním dotazem. Z jednoho požadavku klienta se tak stane 21 dotazů do databáze. Tomu se říká problém N+1.

Standardním řešením je vzor DataLoader (a stejnojmenná knihovna). Ten požadavky na data v rámci jednoho cyklu zpracování shromáždí a pošle je do databáze najednou:

const userLoader = new DataLoader(async (ids) => {
  const users = await db.users.findByIds(ids);
  return ids.map((id) => users.find((u) => u.id === id));
});

// v resolveru
author: (article) => userLoader.load(article.authorId),
Code language: JavaScript (javascript)

Místo dvaceti samostatných dotazů se provede jediný dotaz typu WHERE id IN (…). DataLoader zároveň funguje jako krátkodobá cache v rámci jednoho požadavku, takže stejného autora nenačítá opakovaně. Kdo s GraphQL začíná a na DataLoader zapomene, zpravidla brzy zjistí, že jeho API je výrazně pomalejší než původní REST.

Cachování: slabší místo GraphQL

REST těží z toho, že je postavený přímo na HTTP. Každý zdroj má vlastní URL, požadavky na čtení používají metodu GET a odpovědi lze snadno cachovat v prohlížeči, na proxy serveru nebo v CDN pomocí standardních hlaviček jako Cache-Control a ETag.

GraphQL typicky posílá všechny požadavky metodou POST na jediný endpoint, například /graphql. Z pohledu HTTP infrastruktury tak všechny požadavky vypadají stejně a klasické cachování přestává fungovat. Existuje několik způsobů, jak to řešit:

  • Cache na straně klienta. Knihovny jako Apollo Client nebo Relay udržují normalizovanou cache, v níž je každý objekt uložen podle svého typu a ID. Pokud dva různé dotazy vrátí stejného uživatele, klient ho uloží jen jednou a při změně aktualizuje všechna místa, kde se zobrazuje.
  • Persisted queries. Klient místo celého textu dotazu pošle jen jeho hash. Takový požadavek lze poslat metodou GET a běžně ho cachovat v CDN.
  • Cache na úrovni resolverů nebo polí. Server může ukládat výsledky jednotlivých resolverů, případně využít direktivy, které určují dobu platnosti konkrétních polí.

Všechna tato řešení fungují, ale vyžadují víc práce a víc rozmýšlení než u REST API, kde cachování získáte téměř automaticky.

Bezpečnost a výkon

Svoboda, kterou GraphQL dává klientovi, má i odvrácenou stranu. Klient může poslat libovolně složitý dotaz, třeba hluboce vnořený:

query {
  article(id: 42) {
    author {
      articles {
        author {
          articles {
            author { name }
          }
        }
      }
    }
  }
}

Takový dotaz může server snadno přetížit, ať už omylem, nebo záměrně. Produkční GraphQL API by proto mělo mít několik ochranných mechanismů:

  • Omezení hloubky dotazu, například na pět nebo deset úrovní.
  • Analýzu složitosti, kdy každé pole má přidělenou „cenu“ a dotaz, který překročí limit, server odmítne. Tento přístup používá například GitHub.
  • Povinné stránkování u všech seznamů, aby klient nemohl najednou požádat o miliony záznamů.
  • Timeouty a rate limiting, ideálně založené na složitosti dotazu, nikoli jen na počtu požadavků.
  • Seznam povolených dotazů (allowlist) pro interní API, kde server přijímá jen dotazy předem registrované vlastními klienty.

Za zvážení stojí i vypnutí introspekce v produkci u neveřejných API. Nejde o skutečnou ochranu, ale útočníkovi to ztíží průzkum.

Samostatnou kapitolou je autorizace. Protože se data načítají přes mnoho resolverů, je potřeba důsledně hlídat oprávnění na úrovni jednotlivých polí nebo objektů, ne jen na vstupu do API. Osvědčuje se umístit autorizační logiku do doménové vrstvy, kterou resolvery volají, a ne ji rozptýlit po jednotlivých resolverech.

Verzování a vývoj API

U REST API se změny často řeší verzováním: vedle /api/v1/ vznikne /api/v2/ a obě verze se nějakou dobu udržují souběžně. GraphQL volí jiný přístup. Protože klient vždy výslovně uvádí, která pole chce, lze do schématu nová pole a typy přidávat bez rizika, že se tím rozbije existující klient.

Pole, která chceme odstranit, se nejprve označí direktivou @deprecated:

type User {
  name: String!
  fullName: String! @deprecated(reason: "Použijte pole name.")
}
Code language: CSS (css)

Nástroje pak vývojáře na zastaralé pole upozorní a server může sledovat, zda ho ještě někdo používá. Teprve když jeho využití klesne na nulu, lze ho bezpečně odstranit. Schéma se tak vyvíjí plynule, bez skokových verzí.

Ekosystém a nástroje

Za roky existence vznikl kolem GraphQL bohatý ekosystém. Na straně serveru jsou populární Apollo Server, GraphQL Yoga nebo Mercurius v Node.js, Hot Chocolate v .NET, graphql-java a Spring for GraphQL ve světě Javy, Strawberry a Graphene v Pythonu nebo gqlgen v Go. Ve světě PHP se často používá knihovna webonyx/graphql-php a nad ní postavený Lighthouse pro Laravel.

Na straně klienta dominují Apollo Client a Relay, lehčí alternativou je například urql. Velmi užitečné jsou nástroje pro generování kódu, jako je GraphQL Code Generator. Ze schématu a dotazů vytvoří typy pro TypeScript, takže frontend získá typovou kontrolu napříč celým řetězcem od databáze až po komponentu.

Pro větší organizace je zajímavá federace. Umožňuje, aby jednotlivé týmy spravovaly vlastní části schématu (subgrafy) ve svých službách a gateway je spojila do jednoho grafu, který klient vidí jako celek. Typickým příkladem je Apollo Federation.

Kdy sáhnout po GraphQL

GraphQL se vyplatí hlavně v těchto situacích:

  • Více různých klientů. Webová aplikace, mobilní aplikace a třeba chytré hodinky potřebují různá data. Místo specializovaných endpointů pro každého klienta stačí jedno schéma.
  • Složitá, provázaná data. Pokud vaše doména připomíná graf (uživatelé, jejich objednávky, produkty, recenze a vztahy mezi nimi), GraphQL ji vyjadřuje přirozeně.
  • Rychlý vývoj frontendu. Frontendový tým může měnit, jaká data zobrazuje, bez čekání na úpravy backendu.
  • Agregace více zdrojů. GraphQL jako gateway nad mikroslužbami nebo staršími systémy výrazně zjednoduší život klientům.

Kdy zůstat u REST

Naopak REST bývá lepší volbou v těchto případech:

  • Jednoduché CRUD API s několika zdroji, kde by GraphQL přidal jen zbytečnou složitost.
  • Veřejná API s důrazem na cachování, například otevřená data, kde je klíčová efektivní práce CDN.
  • Práce se soubory. Nahrávání a stahování velkých souborů GraphQL přímo neřeší a obvykle se kvůli tomu stejně sahá po samostatném REST endpointu.
  • Integrace mezi servery, kde obě strany přesně znají potřebná data a flexibilita dotazů nepřináší žádnou výhodu.
  • Tým bez zkušeností a projekt s napjatým termínem. Křivka učení u GraphQL není strmá, ale výkon, bezpečnost a cachování vyžadují znalosti, které se nezískají za týden.

Je také dobré připomenout, že nejde o volbu buď, anebo. Mnoho firem provozuje obojí: GraphQL pro vlastní frontendové aplikace a REST pro veřejné integrace nebo webhooky.

Závěr

GraphQL není náhradou REST API ve všech situacích, ale je to promyšlený nástroj, který elegantně řeší konkrétní problémy: nadbytečná nebo chybějící data, množství požadavků, obtížné verzování a nesoulad mezi potřebami frontendu a nabídkou backendu. Silný typový systém, introspekce a vyspělé nástroje navíc výrazně zlepšují vývojářský zážitek.

Zároveň ale přináší vlastní výzvy. Cachování je složitější, bez DataLoaderu hrozí problém N+1 a otevřenost dotazovacího jazyka vyžaduje pečlivé zabezpečení. Pokud tyto oblasti zvládnete, získáte API, které se snadno používá, přirozeně se vyvíjí a dobře slouží různým klientům.

Nejlepší způsob, jak si udělat vlastní názor, je vyzkoušet si GraphQL na malém projektu. Stačí jednoduché schéma, pár resolverů a GraphiQL v prohlížeči. Rychle zjistíte, zda vám tento způsob práce s daty vyhovuje, a budete mít mnohem lepší podklad pro rozhodnutí u příštího většího projektu.

Komentáře

Odebírat
Upozornit na
guest
0 Komentářů
Nejstarší
Nejnovější Nejvíce hlasů

Technologická úzkost drtí české firmy. Data ukazují chaos v digitalizaci, festival Digifest s prestižními záštitami nabízí cestu ven

Přesycenost trhu softwarem, desítky vyzkoušených a opuštěných nástrojů, hodiny ztracené v formulářích a neustálý strach z toho, že firmě ujede vlak. Český byznys prochází obdobím technologické úzkosti. Přestože podle aktuálních dat vyčleňuje AI rozpočet naprostá většina společností, realita v českých kancelářích má k opravdové transformaci daleko. Odpověď na to, jak proměnit technologie v reálný zisk a udržet krok s konkurencí, přináší 5. ročník festivalu a konference Digifest, který se koná 14.října v pražském Cubexu.

theme.json není jen konfigurace. Je to API pro design systému WordPressu

Soubor theme.json se často bere jako „ten JSON, kam se píšou barvy a velikosti písma“. Jenže kdo s ním pracuje déle, zjistí, že jde o něco zásadnějšího. Je to veřejné, verzované a vrstvené rozhraní, přes které spolu komunikuje téma, editor, pluginy i samotný uživatel. Když ho tak začnete chápat, změní se i to, jak navrhujete a udržujete šablony.