Změny API

Deprekace GraphQL bez čísla verze

5 min čtení

REST API může publikovat /v2/ vedle /v1/ a nechat volající migrovat vlastním tempem. GraphQL má jedno schéma na jednom endpointu, a každý klient, mobilní aplikace na loňském buildu a interní dashboard nasazený dnes ráno, dotazuje stejný graf. Není žádné URL, které by se dalo forknout. Deprekace pole znamená označit ho jako deprekované na místě, ve schématu, na kterém už všichni závisí, což činí disciplínu jinou než u REST, i když základní problém, říct volajícím, že něco zmizí, je stejný jako obecně pokrývá deprekace API.

Jak GraphQL označí pole jako deprekované, když není verze ke zvýšení?

Direktivou @deprecated, aplikovanou přímo na pole:

type Product {
  price: Float @deprecated(reason: "Use priceV2 for multi-currency support.")
  priceV2: Money
}

Pole zůstává dotazovatelné. Nezmizí, nevrátí 404, nezmění chování; jen nese strojově čitelnou poznámku, kterou většina nástrojů GraphQL, GraphiQL, Apollo Studio, lintery schémat, ukáže komukoli, kdo prochází schéma nebo proti němu píše dotaz. To je celý mechanismus. Neexistuje žádný samostatný endpoint pro deprekaci, žádná hlavička, žádný doprovodný dokument vyžadovaný specifikací, což je zároveň lákadlem i pastí: direktivu je snadné přidat a snadné ignorovat, protože nic nenutí klienta se na ni podívat.

Vidí vůbec někdo důvod deprekace?

Jen ti, kdo používají schéma přímo, přes introspekci nebo editor vědomý schématu, a to je menší publikum než obvyklí čtenáři changelogu API. Mobilní aplikace postavená proti dotazu před šesti měsíci má ten dotaz už zapečený ve svém binárním souboru; bude dál žádat price a dál dostávat odpověď, deprekované nebo ne, dokud někdo aplikaci nepřestaví s novým polem a nevydá aktualizaci. Direktiva říká vývojářce píšící nový kód, aby nepoužívala staré pole. Pro klienta, který je už nasazený a běží, nedělá nic.

MechanismusKoho zasáhne
Direktiva @deprecatedVývojářky procházející schéma nebo píšící nové dotazy
Selhání CI linteru schématuTým vlastnící klientskou kódovou základnu, pokud nějaký spouští
Záznam v changeloguKdokoli, kdo ho čte, včetně klientského týmu bez linteru
Nic (pole prostě funguje)Už postavený klient používající staré pole

Mělo by deprekované pole přesto dostat záznam v changelogu?

Ano, a odvede víc práce než samotná direktiva, protože changelog zasáhne lidi, které direktiva zasáhnout nemůže: partnerský tým, který konzumuje graf, aniž by procházel jeho schéma, klienta postaveného proti měsíce staré cachované kopii schématu, kohokoli, kdo by si toho všiml jen čtením prózy. Changelog API obecně pokrývá, co záznam dluží volajícímu; záznam GraphQL dluží jednu věc, kterou REST zřídka musí vyslovit, protože volající REST ji odvozují z čísla verze: jestli staré pole dnes ještě funguje, ještě funguje s varováním, nebo skutečně přestalo vracet data. Samotná direktiva na nic z toho neodpoví čtenářce, která schéma nikdy neotevřela.

Kdy je skutečně bezpečné odstranit pole ze schématu?

Až když logy dotazů ukážou, že už o něj nikdo nežádá, což je otázka použití, ne kalendáře. Pole může nést @deprecated rok a přesto být nosné pro jednoho klienta, který nikdy nebyl přestavěn; odstranění ho podle pevného harmonogramu, jak to často dělá REST Sunset, rozbije toho klienta bez jakéhokoli varování, na které by mohl reagovat, protože GraphQL mu nedává nic, na co reagovat, kromě direktivy, kterou nikdy nečetl. Logujte použití na úrovni pole, než se zavážete k datu odstranění, a jakýkoli nenulový počet dotazů berte jako pauzu, ne odpočítávání.

Nese přidání pole stejné riziko jako v REST API?

Menší, u nového pole, protože klient GraphQL dostane jen pole, o která výslovně žádá. Přidání priceV2 vedle price nemůže rozbít existující dotaz způsobem, jakým přidání pole do REST JSON odpovědi může rozbít striktní deserializátor, protože nic nenutí klienta žádat nové pole. Přidání hodnoty do existujícího enumu je výjimka, kterou stojí za to zmínit ve stejné větě: klient, který vyčerpávajícím způsobem větví na každou hodnotu enumu, což silně typované jazyky podporují, se rozbije ve chvíli, kdy přijde nová hodnota, ať už ji nějaký dotaz žádal, nebo ne. Bezpečnost platí jen pro pole a union členy, do kterých se klient sám přihlásí; neplatí pro uzavřenou množinu, kterou klientův kód vyjmenovává ručně.

Co potřebuje záznam changelogu GraphQL, co nepotřebuje záznam REST?

Tvar dotazu, ne jen jméno pole, protože „pole price je deprekované” postrádá přesně tu část, kterou volající skutečně potřebuje: které typy a které dotazy se ho dotýkají. Užitečný záznam jmenuje typ, pole, náhradní pole a, pokud to lze vygenerovat, skutečné dotazy v produkci, které stále žádají starou formu. Ta poslední část, propojení oznámení o deprekaci se skutečným použitím, je to, co volající REST dostanou zdarma z logů serveru na URL a volající GraphQL ne, protože každý dotaz zasáhne stejný endpoint bez ohledu na to, o co žádá.

Může direktivu @deprecated nést i něco jiného než pole?

Hodnoty enumu, stejnou direktivou u definice samotné hodnoty místo u pole:

enum ShippingMethod {
  STANDARD
  EXPRESS
  OVERNIGHT @deprecated(reason: "Use EXPRESS with priority: true instead.")
}

Specifikace definuje @deprecated přesně pro dvě místa, definici pole nebo hodnotu enumu, a nic jiného ke dni stabilního vydání; deprekace na úrovni argumentu nebo vstupního pole existuje jen v pozdějším návrhovém jazyce specifikace, ne v tom, co dnes implementuje většina serverů. Hodnota enumu takto označená zůstává legální hodnotou, kterou server může dál vracet nebo přijímat, stejný neprolomující slib, jaký dává deprekované pole, a právě to umožňuje ji bezpečně vydat dřív, než hodnotu doopravdy odstraníte.

FAQ

Podporuje GraphQL něco jako hlavičku Sunset pro celý endpoint? Ne, protože obvykle existuje jen jeden endpoint. Časování deprekace žije na úrovni pole, v textu důvodu direktivy @deprecated a v jakémkoli changelogu nebo migračním průvodci, který tým vedle publikuje, ne v hlavičce odpovědi, kterou by klient mohl číst programově.

Může být deprekované pole odstraněno a později znovu přidáno s jiným typem? Jen jako nové jméno pole. Znovuzavedení stejného jména pole se změněným typem je přesně ten breaking change, kterému má cyklus deprekace zabránit; dejte náhradě její vlastní jméno, jak to dělá priceV2, a nechte staré úplně vymřít, než se jméno uvolní k opětovnému použití.

Měl by text důvodu @deprecated odkazovat na záznam changelogu? Ano, když to nástroje schématu podporují. Pole důvodu přijímá obyčejný string, a URL uvnitř tohoto stringu je nejkratší cesta od vývojářky zírající na výstup introspekce k plnějšímu vysvětlení, které může dát záznam changelogu.

Je změna schématu GraphQL někdy zpětně kompatibilní způsobem, jakým REST není? Aditivní změny polí, ano, z výše uvedeného důvodu: klienti dostanou jen to, o co žádají. Nové hodnoty enumu jsou výjimka, protože klient vyjmenovávající uzavřenou množinu se může rozbít na hodnotě, kterou nečekal. Odstranění a změny typů jsou přesně tak breaking jako jejich REST ekvivalenty.


Technická tvrzení v tomto článku nikdo nezávisle neověřil. Pokud tu něco nesedí, dej nám vědět a opravíme to.

Související na changeloop: Dokumentace pro vývojáře, Srovnání nástrojů pro changelog

changeloop
Tým, který vyvíjí changelog uzavírající smyčku. Uživatelé o něco požádají, tvůj tým to doručí, ten, kdo žádal, se to dozví.