Předmluvy a doménový model

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:

  1. Je zde rozhraní API
  2. /list cesta
  3. GET metoda
  4. 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
  • 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
  • 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ěď.
  • 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í.

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 2/32 =1/16 =0,06 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 .