9. května 2017

REST contract-first: Swagger & Gradle

U webových služeb mám rád přístup contract-first. Jsem 100% přesvědčen, že tak vzniká lepší design i lepší API.

V případě SOAP webových služeb je to celkem běžné. (Teda pokud webové služby "nedesignují" programátoři - to pak většinou skončí "vyzvracením" interního kódu na veřejnost.)

Ohledně REST-ových služeb mi to přijde jako minoritní způsob. To je jednak škoda a jednak problém. Ono to nakonec vždycky nějak funguje. Ale jen výjimečně pak vznikají API, které vývojáři milují.

Jak tedy na REST contract-first službu? Následuje popis, který jsem použil na stávajícím projektu a se kterým jsem - po vychytání (Swagger) much - spokojen.

Jak by to mělo fungovat?

Mám dost jasnou představu, jak by celistvý přístup contract-first měl fungovat. Pokud máte jiný postup, nebo se mnou nesouhlasíte, budu rád, když se podělíte v komentářích. Můj zobecněný přístup vypadá takto:
  1. Napsat kontrakt v nějaké rozumně standardizované a obecně přijímané specifikaci.
  2. Vygenerovat potřebný kód, zejména model a rozhraní (API).
  3. Vygenerovaný kód by měl být v adresáři, kde build tool očekává zdrojové kódy.
  4. Generování a kompilace vygenerovaného kódu je součástí standardního build lifecycle (není potřeba je spouštět samostatně).
  5. Ideálně, generování a kompilace probíhá jen tehdy, pokud došlo ke změně specifikace.
  6. Implementaci rozhraní si píšu sám, ručně.

Swagger

Swagger je soubor nástrojů, které se točí kolem OpenAPI specifikace. Za OpenAPI si můžete představit "něco jako WSDL pro REST". Pro naše potřeby nás budou zajímat dva nástroje: Swagger Editor pro napsání specifikace a Swagger Codegen pro generování kódu ze specifikace.

Swagger Editor

Swagger Editor je webový editor, který si můžete vyzkoušet on-line, ale pro reálnou práci bude lepší ho mít lokálně. Je trochu otravné, že kvůli tomu musíte nainstalovat Node.js, ale jinak má lokální verze víc šikovných funkcionalit.

Swagger Editor

Mimochodem, za celým Swaggerem stojí společnost SmartBear, která dělá SoapUI, takže očekávejte něco podobného - je to proklatě použitelné a v detailech mrzce nedotažené. A mizerná dokumentace.

Swagger specifikace

Swagger specifikace může mít dva formáty: JSON, nebo YAML. Jak na zmíněném projektu, tak v článku jsem zvolil YAML - jednak je to čitelnější pro lidi a pak, aspoň na projektu, to nebudou číst jenom programátoři.

Takže, contract-first. Začneme jednoduchou Swagger specifikací, která nám odpoví na otázku Života, Vesmíru a vůbec:


Swagger Codegen

Swagger Codegen je Java knihovna s CLI rozhraním. Swagger se chlubí, že pro generování kódu podporuje 20 serverových a 40 klientských řešení. Moje ukázka je ve Springu, ale klidně si vyberte svoji oblíbenou platformu.

Swagger Codegen CLI

Jak už jsem to naťuknul výše, ne všechno je ve Swaggeru perfektní - kolegové si dost stěžovali na kvalitu a použitelnost generovaných artefaktů pro JAX-RS a pro C#.

Já jsem sice s výsledkem spokojený a dělá to přesně, co jsem chtěl, ale bylo potřeba to poladit - strávil jsem cca 2 dny experimentováním, než jsem se dostal do stavu "akceptováno". Dva dny se mohou zdát hodně, ale jednak to byla zábava a jednak se to v blízké budoucnosti bohatě vrátí.

No, komand-lajna je sice boží, ale my se s ní patlat nebudeme, jsme přece profíci - použijeme Gradle.

Gradle

Pokud na Gradle Plugin Portálu zadáte heslo Swagger, vyjede vám 7 pluginů. Trochu jsem se bál, abych si nemusel napsat vlastní Gradle plugin, jako se mi to stalo u JAX-WS, protože žádný plugin nedělal, co jsem očekával. Ale nakonec po pročtení GitHubu a vyzkoušení jsem vybral plugin org.hidetake.swagger.generator, který šel rozumně ohnout pro moje potřeby.

Konfigurace zmíněného pluginu v build skriptu build.gradle může vypadat následovně (po ořezání ostatních věcí, které nás z hlediska Swaggeru nezajímají).


Podstatné věci na uvedeném skriptu:
  • V závislostech nám přibyla konfigurace swaggerCodegen.
  • Kvůli Swagger anotacím generovaným do API tříd je potřeba přidat závislost na swagger-annotations. To je sice otravný, ale asi nutná daň za použití Swaggeru. Naštěstí má ta knihovna jen 20 kB.
  • Cílová platforma, pro kterou generujeme, je definovaná atributem language.
  • Aby se nám negeneroval veškerý Swagger čurbes, omezíme generované artefakty atributem components, kdy říkáme, že chceme jenom model a api.
  • Adresář s vygenerovanými artefakty je potřeba přidat ke zdrojovým souborům pomocí sourceSets. (To už není plugin, ale čistý Gradle.)
  • A poslední řádek předsadí v lifecyclu task generateSwaggerCode před kompilaci Java kódu.

Bohužel, tím jsme s konfigurací ještě neskončili - tady to plugin mohl dotáhnout ještě dál. Sofistikovanější nastavení je potřeba dotáhnout v souboru config.json:


Tady stojí za zmínku dvě věci:
  • Jednak prázdný atribut sourceFolder, to aby nám Swagger nevygeneroval duplicitně zanořené adresáře.
  • A potom říkáme, že chceme vygenerovat jenom rozhraní (atribut interfaceOnly), aby nám Swagger rovnou negeneroval prázdné dummy kontrolery. (Možná použitelné jako stuby, pokud generujeme jenom jednorázově.)

A je to. Pokud spustíme build příkazem gradle build, dostaneme následující strukturu, kdy Swagger specifikace je v adresáři src/main/swagger a generovaný kód v adresáři src/generated/swagger:

Adresářová struktura projektu se Swagger definicí a generovaným kódem

Ukázkový projekt

Pro potřebu článku jsem vytvořil malý projekt na Bitbucketu, který vygeneruje potřebný kód a jde spustit v embeddovaném Jetty. Stačí naklonovan a spustit gradle jettyRun.

Měli byste ho vyzkoušet - minimálně vám odpoví na nejzákladnější otázku života. A vesmíru. A vůbec...

Související externí články


16 komentářů:

  1. Děkuji moc za pěkný článek. Je dobré o takových nástrojích vědět. :-)

    IMHO závislosti "swagger-annotations" lze nastavit scope "compileOnly" (odbodně jako "provided" u Maven) a nepropagovat jar na produkční classpath do war archivu. Nemám ale odskoušeno, jestli Spring genrované anotace nevyužije např. na generování WADL dokumentace.

    OdpovědětVymazat
    Odpovědi
    1. Vida, compileOnly mi nějak uniklo. No už bylo na čase, aby to tam přidali.

      Taky jsem nad tím přemýšlel - war plugin má konfiguraci providedCompile, která by šla použít. Ale říkal jsem si, že když už tam ty anotace jsou, tak bude lepší k nim mít knihovnu, kdyby to náhodou bylo někdy v budoucnu potřeba.

      Ještě jsem Swagger celý neprostudoval, tak mi není jasné, proč tam ty anotace vlastně cpe. Asi nějaký post-processing.

      Vymazat
  2. Apiary.io se hlásí - min. náš nástroj Dredd (https://github.com/apiaryio/dredd) by mohl přidat užitečnou vrstvu; on to API oproti kontraktu testuje. A nezajímá ho, v jakém je jazyce. A podporuje i Swagger.

    OdpovědětVymazat
    Odpovědi
    1. O Apiary.io vím, ale ještě jsem se k němu nedostal. Díky za tip na Dredd, vypadá to zajímavě.

      Vymazat
  3. Nevím, jak tyhle generátory fungují, ale jak se potom provádí v tom API změny? Když si jednou vygeneruji nějaký kód, je to předpokládám nějaký scaffolding, skeleton, který pak ještě doplňuji vlastní implementací. Dohodnu-li se se všemi na úpravě kontraktu, co potom? Přegeneruje mi to ten základ, na kterém už jsem ale něco postavil? Nebo jak?

    OdpovědětVymazat
    Odpovědi
    1. Vygeneruje jen rozhraní. Když implementace sedí, není co řešit. Pokud se změnila, upravím. Takhle jsme to dělali ve světě WSDL a funguje to.

      Vymazat
    2. I když teda teď mě napadá, že dělat rozhraní ke kontroleru nedává moc smysl.

      Vymazat
    3. @Honza Je to jak psal, Luboš - změní se specifikace kontraktu, přegenerují se (nejčastěji) model a (občas) rozhraní a nakonec se upraví implementace. Většinou by to měly být drobné inkrementální věci.

      Je pak otázka, jestli existuje nějaká governance a řeší se třeba verzování a zpětná kompatibilita.

      Vymazat
    4. @banter Proč nedává smysl mít ke kontroleru rozhraní? Zrovna to co leze ze Swaggeru pro Spring mi přijde rozumný - máš čistě oddělený kontrakt a implementaci.

      Vymazat
    5. Má to rozhraní víc implementací? Dělat implementace jedna k jedné nedává moc smysl, ne?

      Vymazat
    6. @banter Měl by ses opravdu mrknout na ten ukázkový projekt ;-)

      Otázka nestojí, jestli to rozhraní má víc implementací. Jde o to, že v rozhraní jsou jenom věci definované definované kontraktem (třeba springovská anotace @RequestMapping a v implementaci už nic z kontraktu není a jsou tam jen čistě implementační věci - springovský @Controller a implementační logika.

      Vymazat
    7. Jo, souhlasím. Ostatně v SOAP jsem to taky tak měli.

      Vymazat
  4. Myslím, že sis vybral správně Swagger, ale asi by stálo za to vysvětlit proč ne API Blueprint. A kvůli editoru nemusíš instalovat Node.js, můžeš spouštět v Dockeru.

    OdpovědětVymazat
  5. Do sekce Související externí články jsem přidal odkaz na jiný pohled na věc od calavera.info.

    OdpovědětVymazat
  6. Díky za pěkně zpracovaný článek. Má aktuální zkušenost: Jsem na druhé straně barikády s code-first dokumentací v podobě Spring REST Documentation. Nicméně je to code-first, závislé na Springu, apod. Což je značně omezující, ale pokud jsem na start-up projektu, kde vzniká API první a platforma je daná, tak preferuji. Za jiných podmínek výše popsané je pro mne volbou. U Spring REST Doc mám rád, že když mám něco zdokumentované, tak je jistota, že je to otestované a rovnou mi to vygeneruje stabs.

    OdpovědětVymazat
    Odpovědi
    1. Díky za tip, Spring REST Doc neznám, tak snad se na to časem podívám.

      Vymazat