Tato série je o sdílení některých výzev a lekcí, které jsem se naučil během vývoje Prism, a o tom, jak některé funkční koncepty vedou k lepšímu produktu.
Poznámka: Od ledna 2021 již ve Stoplight nepracuji a nemám kontrolu nad aktuálním stavem kódu. Na mém účtu GitHub je větev, která představuje stav projektu, když jsem ze společnosti odešel.
V tomto konkrétním příspěvku začnu vysvětlovat, co je Prism, podrobně popíšu některé jeho klíčové vlastnosti a proberu trochu jeho domény a zamýšleného publika.
Doufám, že vám to pomůže porozumět technickým volbám, které jsem udělal a kterým se budu věnovat v dalších článcích.
Co je Prism
stoplightio / prism
Proměňte jakýkoli soubor OpenAPI2/3 a Postman Collection na server API se zesměšňováním, transformacemi a ověřováním.
Prism je falešný server pro OpenAPI 2 (od nynějška OAS2), OpenAPI 3 (od nynějška OAS3) a Postman Collections (od nynějška na PC).
Pro ty z vás, kteří s tím nejsou obeznámeni, OAS2/3 a PC jsou v podstatě specifikace definující standardní a jazykově agnostické rozhraní k (možná RESTful) API.
Abych byl trochu pragmatičtější:
openapi: 3.0.0
paths:
/list:
get:
description: "Returns a list of stuff"
responses:
'200':
description: "Successful response"
Tento soubor YAML je dokument OpenAPI 3.0, který tvrdí, že:
- Je zde rozhraní API
- Má
/list
cesta - Má
GET
metoda - Když je
GET
požadavek na/list
je vytvořen koncový bod,200
je jednou z možných odpovědí, které můžete get, jehož podrobnosti (jako je tvar užitečného zatížení, vrácená záhlaví) nebyly specifikovány.
O těchto formátech se nebudeme příliš rozepisovat; pokud máte zájem, můžete si přečíst oficiální specifikace:
- OpenAPI 2.0
- OpenAPI 3.0
- Sbírky pošťáků
Navzdory tomuto jednoduchému příkladu můžeme říci, že všechny specifikace umožňují (s určitými nuancemi) specifikovat docela komplikované scénáře, od ověřování, ověřování požadavků a odpovědí až po web hooky, zpětná volání a generování příkladů.
Falešný server není nic jiného než malý program, který přečte dokument s popisem a spustí server, který se bude chovat tak, jak dokument nařizuje.
Zde je příklad spuštění Prism se standardním dokumentem OAS3:
Zvláštnosti hranolu
Technická rozhodnutí a kompromisy byly řízeny funkcemi. Zde jsou ty nejrelevantnější týkající se této série:
100% TypeScript
Prism je celý napsán v TypeScriptu. Především proto, že zásobník Stoplight je z velké části založen na NodeJS a TypeScript.
Používáme maximální úroveň přísnosti, kterou TypeScript umožňuje.
Spousta vlastního softwaru
Prism nepoužívá žádný z webových frameworků, které obvykle najdete na trhu a které se používají pro webové aplikace, takže nenajdete Express, nenajdete Hapi, nic.
Původně byl napsán pomocí Fastify; a v té době jsem na projektu nepracoval. Nakonec jsem se rozhodl jej odstranit ve prospěch malého obalu na vrcholu běžného http
server, který NodeJS nabízí.
V případě, že se ptáte, hlavním důvodem je to, že většina rámců se zaměřuje na 80 % případů použití, což je zcela legitimní.
Na druhou stranu, Prism usiluje o 100% kompatibilitu s typy dokumentů, které podporuje, a například některé z nich mají podporu některých velmi… kreativních parametrů, které žádný parser na trhu nepodporuje.
Další příklad? OpenAPI 2 a 3 používají šablonování cest, ale ne stejné jako šablony URI specifikované v RFC6570. Z tohoto důvodu musel být definován vlastní analyzátor a extraktor.
Tento specifický případ, spolu s dalšími, které vyžadovaly napsání speciálního kódu, nás vedl k postupnému rozebírání a zanedbávání různých funkcí Fastify, až jsem si uvědomil, že ho vůbec nepoužíváme, ne-li k naslouchání na TCP portu; naopak jsme s tím jen bojovali, protože byl příliš zaujatý v určitých věcech, jako jsou chyby.
Více o motivacích najdete v příslušném vydání GitHubu
Vlastní vyjednavač
Prism obsahuje zakázkově vyrobený vyjednávač – což je ta část softwaru, která přijala příchozí požadavek HTTP, výsledky jeho ověření (záhlaví, tělo, zabezpečení) a dokument specifikace cílového rozhraní API vrátí nejvhodnější definici odpovědi, kterou pak může použít generátor vrátit instanci odpovědi klientovi.
Samotný vyjednavač je poněkud komplikovaný, ale myslím, že jsme odvedli dobrou práci v obou dokumentech jeho rozhodovacího procesu:
Diagram se také do značné míry odráží v kódu jako rozdělení funkcí.
Ověření vstupu, výstupu a zabezpečení
Jednou z klíčových vlastností Prism je rozsáhlé ověřování.
Na základě poskytnutého dokumentu Popis API ověří Prism různé části příchozího požadavku HTTP, počínaje deserializací těla podle content-type
záhlaví a poté kontrolu výsledného objektu pomocí poskytnutého schématu JSON (pokud existuje).
Totéž platí pro parametry dotazu (protože ano, OpenAPI definuje kódování i pro parametry dotazu), hlavičky a nakonec požadavky na zabezpečení.
Výsledek ověření vstupu ovlivní chování vyjednavače i vyjednavače.
Ukazuje se, že validace je velmi komplikovaná část Prism, a přestože jsme ji několikrát přepracovávali, stále se nám to nedaří.
Tok požadavku na hranol
Cesta požadavku HTTP od zásahu vašeho aplikačního serveru k vrácení odpovědi klientovi je formulována.
Často o tom nepřemýšlíme, protože webové rámce obvykle odvádějí velmi dobrou práci při abstrahování veškeré složitosti.
Vzhledem k tomu, že Prism nepoužívá žádné frameworky, měl jsem v zásadě možnost reimplementovat téměř celý pipeline – a začal jsem mít postřehy.
Zde je to, co Prism dělá, když přichází požadavek:
- Směrování
- Path Match s podporou šablon, kde také extrahujeme proměnné z cesty a vracíme
404
v případě, že selže - Shoda metody, vrací
405
v případě, že selže - Ověření serveru, což je kontrola
HOST
hlavička požadavku proti serverům uvedeným v dokumentu specifikace, vracející404
v případě, že selže
- Path Match s podporou šablon, kde také extrahujeme proměnné z cesty a vracíme
- deserializace/ověření vstupu
- Parametry cesty se ověřují podle toho, co je uvedeno v souborech specifikací (zda je to povinné, ať už jde o číslo nebo řetězec)
422/400/default
- Řetězec dotazu je deserializován podle pravidel uvedených v souboru specifikace a vrací
422/400/default
v případě, že dojde k selhání deserializace - Záhlaví jsou ověřena podle formátu JSON, který definuje OAS2/3; převedeme je na specifikaci draft7 a spustíme na ní ajv, vrátíme
422/400/default
v případě selhání ověření. - Tělo je ověřeno podle formátu JSON, který definuje OAS2/3; převedeme jej na specifikaci draft7 a spustíme
ajv
na něj vrátí422/400/default
v případě selhání ověření. - V závislosti na bezpečnostních požadavcích specifikovaných ve směrované operaci Prism zkontroluje přítomnost určitých hlaviček a pokud to bude možné, pokusí se také ověřit, zda jejich obsah respektuje obecný formát požadovaný pro takové bezpečnostní požadavky. Vrátí
401/400/default
- Parametry cesty se ověřují podle toho, co je uvedeno v souborech specifikací (zda je to povinné, ať už jde o číslo nebo řetězec)
- Vyjednavač/Proxy
- Vyjednavač začne a hledá vhodnou definici odpovědi na základě výsledku ověření, požadovaného typu obsahu, akceptovaných typů médií a tak dále. Vrátí
2XX/406/500/User Defined Status code
v závislosti na definici nalezené odpovědi. - Pokud je proxy zapnutý, Prism přeskočí vyjednavače a odešle výsledek na nadřazený server a zaznamená si vrácenou odpověď.
- Vyjednavač začne a hledá vhodnou definici odpovědi na základě výsledku ověření, požadovaného typu obsahu, akceptovaných typů médií a tak dále. Vrátí
- Narušení a serializace výstupu
- Záhlaví odpovědí, ať už jsou vygenerována z definice odpovědi, extrahovaná z
example
nebo vrácené z požadavku proxy se ověří podle definice odpovědi a vrátí500
(chyba v požadavku nebo záhlaví porušení), v případě, že se neshodují - Tělo odpovědi, ať už bylo vygenerováno z definice odpovědi, extrahované z
example
nebo vrácené z požadavku proxy, je ověřeno podle definice odpovědi a vrací500
(chyba v požadavku nebo záhlaví porušení), v případě, že se neshodují.
- Záhlaví odpovědí, ať už jsou vygenerována z definice odpovědi, extrahovaná z
Zde přichází první klíčový postřeh:téměř každý krok, který Prism provede, může selhat a každé selhání má specifický sémantický význam a je přiřazen přesný stavový kód.
Když jsem to naposledy kontroloval, na více než 32 „cestách odchodu“ 30 z nich byly chyby a pouze dvě z nich byly „úspěšně vrácená odpověď“. Trochu počítání:
2/32=1/16=0,06
To v zásadě říká, že v případě rovnoměrně rozložených výskytů výstupních cest bude úspěšných pouze 6 % požadavku.
Jsou výskyty výstupní cesty rovnoměrně rozloženy? I když na to nemám konkrétní odpověď (ale doufejme, že budeme, protože shromažďujeme statistiky v hostované verzi Prism) – máme nějaké empirické důkazy, o kterých budu mluvit v dalším odstavci, které můžeme mít na paměti. .
Uživatel Prism
Prism je vývojářský nástroj, a přestože jej lze použít jako runtime komponentu, používají jej především návrháři API a vývojáři klientů během vývojové fáze API.
Toto je velmi důležitý detail, protože typický vývojář, který používá Prism, má zcela jiné cíle než běžný vývojář API. Následující tabulka shrnuje některé rozdíly, které jsem identifikoval u vývojáře aplikací
Vývojář klientských aplikací | Vývojář rozhraní API |
---|---|
Jasné poslání | Nemám ponětí o tom, co dělají |
Pravděpodobně si přečtěte dokumentaci API | Experimentální fáze |
Pravděpodobně odesílání platných dat | Pravděpodobně odesílání odpadu |
Cíle k úspěchu | Kód a specifikace se mění každou sekundu |
Když vyvíjíte aplikaci, pravděpodobně usilujete o úspěch – a tak budete vytvářet všechny požadavky, které potřebujete, s pravděpodobně platnými daty, pravděpodobně podle postupu uvedeného v dokumentaci.
Na druhou stranu, když zesměšňujete API s Prism, jste hluboko ve fázi návrhu. Pravděpodobně budete upravovat dokument několikrát za minutu (a Prism dokument znovu načte). Pravděpodobně budete neustále posílat neplatná data, protože jste právě zapomněli, co jste v dokumentu napsali. Vyzkoušíte podivné kombinace věcí, které se nikdy nemají stát.
Již jsme uvedli několik odstavců, že v případě rovnoměrně rozložených výskytů výstupní cesty bude úspěšných pouze 6 % požadavku.
Nyní, když jsme trochu objasnili typického uživatele Prism, je fér říci, že výskyty výstupních cest nejsou zjevně rozloženy rovnoměrně, a přestože nemůžeme uvést přesné číslo, můžeme tvrdit, že se silně přiklání na stranu chyb. .
V podstatě, když odešlete požadavek do Prism, s největší pravděpodobností dostanete jako odpověď chybu.
Poté, co jsem o tom hodně přemýšlel, napsal jsem tuto větu, která byla klíčovým faktorem k radikální změně architektury Prism.
Úkolem Prism je vracet chyby.
V příštím článku si povíme něco o abstrakci používané ke správnému modelování takových případů použití a jak jsem ji našel náhodou .