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

Zdroják » JavaScript » Modelování REST APIs

Modelování REST APIs

V rámci své diplomové práce jsem se rozhodl podívat na aplikování Model-Driven Development na RESTová APIčka, a výsledkem mého snažení je kombinace editoru pro datové struktury a generátor kódu, který využívá vytvořený model pro generování REST API. Co všechno se mi podařilo, se dovíte v článku.

Aneb jak generuji pro produkci připravená, testovatelná a krásně strukturovaná RESTová APIčka během několika minut.

Na úvod

Pracuji v malém startupu ve střední části Norska jako front-end developer a moje práce byla vždycky spojená s prohlížečem. Jak už to ale u startupů bývá, občas zkrátka dostanete za úkol něco, co zrovna není váš denní chleba, protože to někdo udělat musí.

Jednoho dne za mnou přišel kolega a řekl mi, že je potřeba udělat REST API k našemu projektu a jelikož všichni ostatní jsou vytíženi na souběžném iOs projektu, bude to na mně. Moc nadšený jsem z toho nebyl, nicméně nedalo se nic dělat. Jako útěchu jsem dostal starší REST API napsanou v NodeJS z jiného projektu, kterou jsem měl předělat, aby vyhovovala potřebám právě rozjetého projektu. Jelikož jsem znal tak maximálně teorii, nějaký čas mi trvalo, než jsem vlastně přišel na to, jak celé APIčko funguje, kde se validují data, kde se generují chybové hlášky etc.

Pustil jsem se do díla a výsledek byla čirá tragédie. APIčko fungovalo, nicméně výsledný kód byl zcela nepřehledný a bylo zkrátka vidět, že s kódem pracoval někdo, komu programování na straně serveru uplně nevoní. Ve stejnou dobu jsem ve škole poslouchal o Model-Driven Development a jednoho dne mě zkrátka napadlo, že ten poslední rok na škole bych mohl strávit tím, že bych se ponořil do modelování RESTových APIček, abych se už nikdy nedostal do situace, kdy se za svůj kód musím stydět.

Jako cíl jsem si tedy dal, že se pokusím uplatněním MDD vygenerovat REST API, která:

  • má pěkně naformátovaný kód,
  • je rozdělené do složek přesně tak jako ta, se kterou jsem musel pracovat,
  • se dá jednoduše otestovat, abych věděl, co jsem vlastně vygeneroval,
  • se dá vygenerovat i někým, kdo nemá nejmenší páru o programování na straně serveru.

Co to vlastně je ten Model-Driven Development?

Model-Driven development je stručně metoda vývoje software, kdy se model stane součástí samotného kódu a nikoliv pouze jen dokumentace. Pokud zkrátka svou aplikaci modelujete, s trochou nadsázky vytváříte zároveň aplikaci samotnou. Váš model se potom prožene transformací a stane se z něj kód, který s trochou štěstí funguje a lze ho použít v produkci.

Templatizace kódu

Templatizace kódu

Klíčovým prvkem tohoto procesu je samotná transformace. Standardní postup je takový, že se vytvoří nějaká sada šablon pro kód, která kromě samotného kódu obsahuje i meta-kód pro vkládání informací z vašeho modelu.

Kde však vezmu tu sadu šablon? Standardní postup je takový, že se vezme kód již vytvořeného softwaru a ten se rozdělí do tří skupin:

  1. Kód, který obsahuje tu a tam drobné změny a lze tyto změny algoritmizovat (Schematic Repetitive Code).
  2. Kód, který je vždycky stejný, tz. generický kód (Generic Code).
  3. Kód, který se zkrátka nedá vygenerovat a je potřeba ho dopsat, tzn. individuální kód. (Individual Code)

První skupina je přesně ta, kterou hledáte. Z této skupiny se stanou šablony. Druhá skupina se zkrátka jen zkopíruje do výsledné aplikace, no a tu třetí si zkátka už člověk musí napsat sám. Z toho plyne, že MDD se nehodí na domény, kde individuální kód tvoří 80% aplikace.

Editor

Jakožto první část své diplomky jsem vytvořil jednoduchý editor, abych byl vůbec schopný vytvořit svůj model. Na internetu jsem několik nástrojů našel, nicméně problém je v tom, že mému modelu musí rozumět i počítač a nejen programátor, který po mně převezme můj kód. Potřeboval jsem editor, který mi dovolí vygenerovat celý model v JSON formátu, abych tento výstup mohl použít k samotnému generování. Nástroje jako Gliffy nebo Google Docs tedy nepřichází v úvahu, jen pokud se snad někdo chce zabývat image processingem.

Editor pro vytváření modelů

Editor pro vytváření modelů

Editor je vcelku jednoduchý. Dovoluje mi vytvořit datové struktury, které mohu propojovat a vytvářet tak asociace. K tomu, a tento krok momentálně již nepovažuji za nejšťastnější, lze k datovým strukturám přidávat jednotlivé RESTové adresy, které mají být součástí výsledné REST API. Celý tento grafický model se pak dá jednoduše převést do JSON formátu.

Paráda! Máme model, a teď ta těžší část- vygenerovat z toho REST API.

Generátor

Samotný generátor jsem se rozhodl napsat jak jinak, než jako REST API v NodeJS. Trošku ironické, jelikož tohle všechno dělám, protože APIčka zkrátka pořádně psát neumím. Následující obrázek shrnuje proces generování.

Kompletní proces generován

Meta-Model

Podstatnou částí každého generátoru je meta-model. Meta-model je vlastně model modelu a specifukuje, co všechno může model obsahovat. Pokud tedy přirovnám model ke kytaře, meta-model  specifikuje, že model má asi nějaký počet strun a hmatník, ale rozhodně není přípustné, aby model měl činely a blány. Pokud tedy model něco takového obsahuje, neslučuje se s meta-modelem a nelze ho tak použít. Samotný model tedy nejprve porovnám k mému meta-modelu a pokud je vše v pořádku, přejdu k dalšímu kroku. Celý generátor je nezávislý na meta-modelu, takže pokud máte vlastní notaci, není problém vytvořit vlastní meta-model, zvalidovat příchozí model a o zbytek už se postará generátor.

Šablony

Dlouho jsem přemýšlel, který ze stovky šablonovacích enginů použiji. Nakonec jsem se rozhodl použít ten zcela nejprimitivnější – UnderscoreJS. Především je to proto, že Underscorovské templaty jsou čistý JavaScript. Není zde žádná speciální notace jako má napříkad MustacheJS. Dalším argumentem je, že Underscore převádí všechny šablony nejdříve na funkce, která je pak zavolána s nějakým objektem reprezentujícím model jako parametr. Tento objekt jsem po vzoru Angularu (promiň, Dane) nazval Scope. Rozlišuji tři druhy šablon:

  • normal (normální)
  • atomic (atomické)
  • duplicated (duplikované)

Normální jsou šablony reprezentují jeden soubor. Jedna normální šablona = jeden soubor. Atomické jsou šablony, které jsou přeložené na JavaScriptovou funkci a poté přidané do Scope, takže je lze volat v ostatních šablonách. Toto se hodí pro opakující se kód. Duplikované šablony jsou specifické potřebám REST APIs. Duplikované šablony vytvoří několik souborů na základě jedné šablony a nějakého pravidla. Duplikované šablony se tedy dají použít třeba pro datové modely, aby byly ve výsledku pěkně rozděleny, každý model do svého souboru.

Scope

Jak již bylo řečeno, Scope je objekt, které je použit při exekuci šablon. Tento objekt nativně obsahuje model vytvořený v editoru, utilitu pro převod mezi jednotným a množným číslem (Part vs. Parts), utilitu pro generování náhodných dat na základě RegExu a Scope helpery. Scope helper (zlaté doby Zend frameworku) je funkce, která obsahuje nějakou logiku a lze jí zavolat v šablonách. Místo sáhodlouhých zápisů v šablonách tedy stačí vytvořit scope helper, zavolat ho v libovolné šabloně a lze ho pak i otestovat pomocí unit testů.

Formát kódu

Když jsou všechny šablony vytvořené, generátor ještě upraví formátování, aby výsledný kód byl čitelný a programátor s ním mohl pracovat bez nadávání. Formátování zatím podporuje pouze JavaScript, nicméně moderní jazyky mají svoje vlastní nástroje (Golang, dívám se tvým směrem), takže toto není až zas takový problém.

V následující části se rozpovídám trochu o šabloně pro NodeJS REST APIs a jak jsou v ní řešené některé principy.

NodeJS Šablona

Abych demonstroval, že vše funguje jak má, vytvořil jsem šablonu pro NodeJS REST API.

Predikce chování

Šablona sama dokáže rozpoznat, co by měl jaký endpoint dělat na základě HTTP metody a URI. Pokud tedy máme kobinaci GET /ovens/:id, šablona sama ví, že tento endpoint by měl vracet jeden objekt se specifikovanym ID. Pokud je URI trošku složitější, například GET /ovens/34/parts/12, šablona postupuje následovně:

  • vyhledá v modelu z editoru entitu Oven
  • podívá se, jestli tato entita má property nazvanou parts
  • pokud ano, šablona se podívá na typ této property (v tomto případě Array[integer, Parts])
  • vyhledá v modelu entitu s názvem Parts

Pokud tento proces selže v jakémkoliv z popsaných kroků, šablona využije prázdnou funkci jako handler tohoto endpointu a programátor si chování musí doprogramovat ručně. Nic se nevytratí. Jen se to zkrátka nevygeneruje.

API Blueprint

Součástí této šablony je i šablona pro API Blueprint soubor. Jakožto velký fanda Apiary jsem toto musel implementovat. Výstup tedy spolu s REST API obsahuje i API Blueprint soubor, který lze použít pro všechny nástroje uvedené na ofciálních stránkách API Blueprint.

Ok, takže jsme něco vygenerovali, a jak vlastně zjistím, co přesně se vygenerovalo, co funguje a co se musí ručně dodělat?

Testování

Součástí šablony jsou i unit testy. V první řadě se tedy vyplatí spustit unit testy a zjistit, jestli vše funguje tak, jak má. Mnohem zajímavější je však použít vygenerovaný API Blueprint soubor a DreddJS. Tento nástroj parsuje API Blueprint soubor a udělá jednotlivá volání za vás. Následně pak otestuje realnou odpověď serveru s tou uvedenou v API Blueprint souboru a pokud je vše v pořádku, endpoint lze považovat za hotový. Pokud se odpovědi neshodují, Dredd tento endpoint označí červeně. Na základě reportu z Dredd pak lze otevřít kód a podívat se na konkrétní místa v kódu.

Dredd v akci

Na obrázku je vidět, že všechny endpointy se podařilo úspěšně vygenerovat až na dva. Pokud se podíváte na ukázkový model, je zřejmé, že entita Parts nemá atribut Pallets a tudíž generátor nebyl schopný určit chování těchto dvou endpointů.

GitHub Integrace

V následující části popíšu možnost využití příkazu Git i GitHub pro verzování vygenerováného kódu a jako zdroj šablon.

Git pro verzování vaší REST API

REST API máme vygenerovanou, nicméně co když v ní doprogramuju nějaké chování a pak za mnou přijde projekťák s tím, že klient chce také ukládat věk uživatele a je tedy třeba změnit model v editoru a REST API přegenerovat? V tomto případě je ideální využít Git. Generátor má speciální endpoint, který veme vygenerovaný kód, vytvoří novou větev ve specifikovaném repositáři a vygenerovaný kód do této větve nahraje. Lze tedy potom zmerdžovat (řiká se to vůbec v češtině takhle?) tuto větev s master branch a ručně si vybrat, co bude přepsané a co ne.

Verzování pomocí Git

Verzování pomocí Gitu

GitHub jako zdroj šablon

Generátor má také endpoint, který si před samotným procesem naklonuje specifikovaný repositář se šablonami a ten pak využije pro generování REST API. Lze tak vytvořit šablonu, udržovat ji v GitHub repositáři, společně jí s kolegy vylepšovat a násladně ji pak využít pro generování. Váš kód se tedy s každým generováním o trošku změní, aby reflektoval aktuální trendy. Díky této funkci nepotřebujete psát šablony, stačí pouze znovu využít, co už někdo za vás udělal a dal to na GitHub.

Vývoj šablon

Nechci lhát – vytvářet šablony je poněkud nudný proces. Snažil jsem se tedy tento proces ulehčit, jak jen to šlo. Generátor obsahuje Grunt soubor, který má nastavený watcher na složku se šablonami. Jakmile je registrovaná nějaká změna, grunt za mě veme univerzální model, který je součástí generátoru a pošle tento model generátoru. Generátor mé REST API pak přegeneruje. Pokud vygenerovanou REST API spustíte pomocí Nodemon, s každým přegenerováním se tedy REST API restartuje a změny provedené v šablonách jsou okamžítě k mání. Unit testy mám spuštěné s příznakem -w, takže to samé – jakmile je registrovaná změna, testy se znovu spustí.

Proces automatizace při psaní šablon

Proces automatizace při psaní šablon

Chtěl jsem do tohoto procesu ještě integrovat Dredd, nicméně mám s tím trošku problém, protože Dredd musí počkat, že se REST API pregeneruje a pak teprve může být tato úloha v spuštěná Gruntem.

Problémy

Vždycky, když se objeví nějaký nový framework nebo knihovna, první, co udělám, je, že začnu googlit „Why I should not use …“. Zde jsou tedy nějaké problémy, na které jsem narazil a je potřeba je vyřešit.

Především je to testování pomocí Dreddu. Dredd potřebuje pro testování IDčka existujících objektů v databázi. Ty se dají do API Blueprint souboru dopsat ručně nebo na to mám scope helper, který je na základě URI doplní, nicméně někdo je musí samozřejmě napsat do toho scope helperu. Tak či onak, je potřeba ruční práce, aby toto fungovalo. Hrál jsem si s možností hooks, kterou Dredd má, ale nejsem schopný to dotáhnout do konce. Zde je zkrátka nějaký ten prostor pro další zkoumání.

Formátování API Blueprint souboru je poměrně problém. Jakmile si člověk začne hrát s vnořenými šablonami, je velice složité držet se nějakého formátu. Dredd sice nadává, ale je schopný takovýto API Blueprint soubor schroustat. Nějaká auto-formater by se tedy hodil, nicméně API Blueprint je venku už nějakou dobu a nikdo se k tomu nemá, takže hádám, že tam jsou nějaké problémy/důvody, mně neznámé.

Obávám se, že predikce chování je poměrně slabý článek celého procesu a bude nejspíš potřeba rozšířit samotný model v editoru, aby toto fungovalo. Je potřeba se uvědomit, že přesnější a úspěšnější generace znaméná obsáhlejší model a to vyústí v komplexnější UI editoru.

Závěr

Pokud to tedy shrnu, s výsledkem mého snažení lze generovat REST APIs, které:

  • mají přijatelný formát kódu,
  • jsou testovatelné pomocí unit testů a s trochou manuální činnosti i pomocí API Blueprint,
  • jsou verzovatelné přes GitHub, takže se žádná ručně provedená změna neztratí.

Generátor pak samotné dokáže využívat GitHub jako zdroj šablon, takže lze vygenerovat REST API pro NodeJS, PHP, Golang a další během několika kliknutí. Vše důležité je ve vašem modelu. Ve firmě, ve které pracuji,  chceme tento generátor využít pro micro-servisy, ze kterých pak poskládáme kompletní REST API. Co se generování týče, čim menší kód člověk generuje, tim lepší a přesnější je výsledek. Poslední verze Golang integruje generování, o kterém si lze přečíst více na oficiálních stránkách Golang.

Můj generátor není na všechny druhy REST APIs. Pokud zvažujete vytvoření REST API jako produkt, asi to není uplně ta nejlepší volba. Pokud však píšete client-heavy appku a potřebujete k tomu nějakou tu REST API, moc se vám do toho nechcete a radši byste utratili budget na vyfintění klienta, pak by vám můj generátor mohl pomoci.

Zde jsou tři videa, kde lze vidět vše výše popsané. Předem se omlouvám za kvalitu komentáře a kvalitu anglického jazyka, nicméně po několika pokusech o preciznost jsem to zkrátka vzdal. Nakonec, cílová skupina těchno videí je akademická sféra.

Komentáře

Subscribe
Upozornit na
guest
10 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
Eda

„…výsledkem m0ho snažení…“

Chtělo by to trošku pilněji dělat korektury. Že je chyba ve článku, to pochopím. Ale přímo v perexu? :-)

Martin Hassman

Díky za upozornění. Budu si muset vyřešit, proč jsem přesvědčen, že to tam při vkládání článku ještě nebylo.

Petr Soběslavský

Zajímavý článek, sám teď řeším něco podobného. Dá se to nějak porovnat s tím, co dělá Swagger (http://swagger.io/)?

Lukáš Krahulec

Swagger generuje dokumentaci už existujícího API. Vedle dokumentace umožňuje i přímo se dotazovat na REST API pomocí vygenerovaných formulářů. Takže myslím že srovnání asi moc možné není, nástroj dělá něco jiného.

Lukáš Krahulec

Já Swagger používám pro dokumentaci již existujícího ASP.NET WebApi kódu. Využívám vlastně jen SwaggerUI a JSON pro něj mi generuje Swashbuckle podle data anotací v kódu při buildu projektu. Samozřejmě tento způsob je pro návrh API nepoužitelný.

Tomáš Srb

… je česky slučovat – čili větve nemerdžujeme, ale slučujeme! Ovšem když o tom tak přemýšlím, tak kdykoliv o tom mluvím, tak stejně mergeuju :-(, tak.

Enum a statická analýza kódu

Mám jednu univerzální radu pro začínající programátorty. V učení sice neexistují rychlé zkratky, ovšem tuhle radu můžete snadno začít používat a zrychlit tak tempo učení. Tou tajemnou ingrediencí je statická analýza kódu. Ukážeme si to na příkladu enum.