23. října 2012

Verzování webových služeb, SOAP

Před časem jsem napsal lehký úvod do SOA governance. Chtěl bych se k tomuto tématu vracet a prezentovat principy, které postupně zavádíme na projektu. Momentálně nás daleko víc pálí věci, které spadají do design fáze služeb, takže řešíme věci jako verzování, reusabilita, granularita služeb apod.

Aspekt, který jsme řešili jako první (a nyní už i zavedli v praxi) je verzování služeb. Jelikož používáme řešení založené na SOAP webových službách (jak jinak v enterprise :-) podíval bych se právě na toto téma. Jestliže budu dále mluvit o webových službách (WS), mám tím vždy na mysli WS založený na SOAP.

Proč?

Otázka je, proč vlastně webové služby verzovat? Dejme tomu, že z nějakého důvodu chceme, na určitém prostředí, držet více verzí jedné služby. Tím důvodem může být např. to, že už máme nějaké stávající konzumenty dané služby a zároveň chceme touto službou poskytnout nové (business) funkcionality. Stávající klienti ovšem nemohou (nebo také odmítnou) přejít na novou verzi služby. Starou verzi služby tedy necháme funkční a zároveň nasadíme verzi novou. V případě SOA by toto mělo být definováno/podpořeno nějakou politikou, např. podpora více verzí služeb.



Kontrakt

Jak z hlediska SOA, tak z hlediska klienta se webová služba (a její konzumace) točí kolem kontraktu. Kontrakt, jako takový, se skládá z několika částí. Jednak definuje nějaké designové věci jako datové typy, zprávy a rozhraní (ve smyslu OOP). Dále definuje technologické záležitosti, tj. jaké se používají protokoly, jaké jsou endpointy služby. A konečně jsou to nefunkční požadavky.

Pro SOAPovské služby je tímto kontraktem WSDL (Web Service Definition Language). Pokud pomineme nefunkční požadavky (které stejně nejsou definovány ve WSDL namespacu, ale jinde), má WSDL následující strukturu (zeleně jsou designové části, červeně implementační):

  • types - element definující pomocí XML Schema datové typy používané ve zprávách.
  • message - abstraktní definice přenášených dat (zpráv), sestavená z typů definovaných v předešlém elementu. Každá zpráva se skládá z jedné a více logických částí (parts). Pokud je částí ve zprávě více, jde o službu založenou na RPC.
  • portType - množina abstraktních operací, víceméně odpovídá rozhraní v OOP. Každá operace (metoda) by měla odkazovat vstupní a výstupní zprávu. Operace může být jedním ze čtyř typů: Request-response (klasika), One-way (obsahuje pouze vstupní zprávu), Notification (obsahuje pouze výstupní zprávu) a Solicit-response (endpoint pošle zprávu a očekává odpověď).
  • binding - sváže definovaný portType s konkrétním protokolem (SOAP, HTTP, JMS) a formátem zpráv (např. Document/literal, RPC/encoded).
  • port - definuje endpoint služby.
  • service - množina souvisejících portů.

Sémantika verzování

Není potřeba znovu-vymýšlet-kolo. Archetypem verzování je formát <major>.<minor>.<micro>. U služeb nás micro verze nebude moc zajímat - je vyhrazená pro implementační změny, které neovlivňují vyšší řády verze a nijak se tedy nepromítají do kontraktu. minor verze je vyhrazená pro změny rozhraní, které jsou zpětně kompatibilní. No a major verze indikuje změnu rozhraní, která není zpětně kompatibilní, tj. rozbíjí kontrakt.

Změny, které jsou zpětně kompatibilní:
  • přidání nové operace do služby,
  • přidání nového XML typu do schématu.

Změny, které nejsou zpětně kompatibilní:
  • odebrání operace ze služby,
  • přejmenování operace,
  • změna XML typů a atributů zprávy,
  • změna namespace.

Jak?

Jako best-practice se uvádí, že verzování by pro konzumenty mělo být explicitní, tj. uvádět číslo verze v elementech, URL apod. Co všechno v kontraktu verzovat, může být předmětem diskuzí na konkrétním projektu a technologiích, nicméně dá se vyjít z těchnto pravidel:
  1. Vkládat major a minor verzi do názvu WSDL souboru: MyService-v1.2.wsdl.
  2. Vkládat major verzi do targetNamespace WSDL souboru:
    <definition
      targetNamespace=
          "http://sw-samuraj.cz/ws/MyService-v1"
      xmlns="http://schemas.xmlsoap.org/wsdl/">
  3. Vkládat major a minor verzi do portType elementu:
    <portType name="MyServicePort-v1.2">
  4. Vkládat major a minor verzi do service elementu:
    <service name="MyService-v1.2">
  5. Vkládat major verzi do endpointu služby:
    <soap:address
      location=
        "http://sw-samuraj.cz/myService/v1"/>
Pokud bychom si ukázali celé WSDL, vypadalo by takto:
<?xml version="1.0" encoding="UTF-8"?>
<definitions
  targetNamespace=
      "http://sw-samuraj.cz/ws/MyService-v1"
  xmlns="http://schemas.xmlsoap.org/wsdl/"
  xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema">

  <types>
    <xsd:schema
      targetNamespace=
            "http://sw-samuraj.cz/ws/MyService-v1">
      <!-- schema omitted -->
    </xsd:schema>
  </types>

  <!-- messages omitted -->

  <portType name="MyServicePort-v1.2">
    <!-- operations omitted -->
  </portType>

  <!-- binding omitted -->

  <service name="MyService-v1.2">
    <port binding="MyServiceSOAP"
          name="MyServiceSOAP">
      <soap:address
        location="http://sw-samuraj.cz/myService/v1"/>
    </port>
  </service>

</definitions>

Závěr

Verzování poměrně úzce souvisí s dalším SOA governance aspektem a sice životním cyklem služeb (což je téma, na které se podíváme někdy příště). To že držíme na prostředí (typicky produkci) více verzí jedné služby má samozřejmě svoje náklady a ideálním stavem je, když služba běží pouze v jedné (nejaktuálnější) verzi.

Se správou více verzí nám může pomoci vhodný nástroj - SOA governance repository, která drží o konrétní verzi služby různá meta-data a mmj. také to, v jaké životní fázi se služba nachází, nebo kdo ji konzumuje (a koho musíme notifikovat o penzionování služby).

4. října 2012

Architektonické principy RESTu

Jedním ze zdrojů, který jsem použil jako přípravu na certifikaci Java EE 6 Web Services Developer (o které jsem psal nedávno), je kniha RESTful Java with JAX-RS od Billa Burkeho. Bill je určitě vhodným autorem, poněvadž byl/je leaderem projektu RESTEasy od JBossu, což je certifikovaná implementace JAX-RS.

Co se týká knihy, můžu ji doporučit. Je dobře napsaná, Bill nijak netlačí svoje RESTEasy, ale objektivně zmiňuje i další alternativy - referenční implementaci Jersey a Apache CXF. Mě osobně trochu mrzelo, že celá druhá polovina knihy je workbook, jakýsi guide k příkladovému projektu - přece jenom, jak se pohybuju v architektonických sférách, tak to pro mne není až tak přínosný. Nicméně někomu jinému může tato "konrétně implementační" část pomoci dostat se (rychleji) do problému.

Ohledně RESTu jsem byl, až do přečtení knihy, panensky nepoznamenán. Teoreticky již nyní mám načteno (i z jiných zdrojů), ovšem RESTový projekt, který by mne pokřtil ohněm, mě teprve čeká. (To že jsem jednou restovou architekturu navrhl v nabídce, se nepočítá) V tomto postu (aby mi něco z toho v hlavě zůstalo) bych se zaměřil na to, co pro mne bylo nejpřínosnější a nejzajímavější: zcela jednoznačně to jsou architektonické principy RESTu.

Že termín representational state transfer pochází z dizertačky Roye Fieldinga (2000), bylo na webu napsáno již milionkrát, takže to tady nebudu omílat a skočím rovnou rovnýma nohama do principů, na kterých stojí.

Adresovatelnost

Celý REST se točí kolem zdrojů (resources). Každý takový zdroj by měl být dosažitelný pomocí jedinečného identifikátoru (URI). Tohle je celkem jednoznačný u dokumentů, aplikací a služeb. Ale když vezmeme v potaz např. Java objekty deployované na aplikačním serveru, tak u nich chybí jednotný/jednoznačný způsob identifikace, který se navíc bude lišit podle různých implementací/dodavatelů (např. JNDI).

Pro identifikaci zdroje se používá URI, které má standardizovanou podobu:

  • scheme://host:port/path?queryString#fragment

přičemž scheme je protokol (např. HTTP), host je DNS nebo IP adresa, port je... port, path je virtuální hierarchická struktura, queryString je množina párů klíč=hodnota, oddělených znakem & a fragment je identifikátor, který označuje nějakou část zdroje (např. nadpis nebo obrázek v dokumentu).

Jednotné rozhraní

Tak jako se REST točí kolem zdrojů, tak se také točí kolem HTTP, jehož jednotlivý metodám připisuje specifický účel a význam.

  • GET je určena pro read-only operace, jako je načtení seznamu zdrojů (dokumentů), nebo obsahu jednoho zdroje. Je jednak idempotentní (lze ji volat opakovaně se stále stejným výsledkem) a jednak bezpečná (safe, nezpůsobuje žádné vedlejší efekty).
  • PUT umožní daný resource (tělo zprávy/requestu) uložit na serveru na určitém URL. Předpokládá, že klient zná potřebný identifikátor. Metoda je idempotentní, technicky bývá implementovaná jako insert nebo update.
  • DELETE odstraní daný resource. Je idempotentní.
  • POST je funkčně hodně podobná metodě PUT. Zásadní rozdíl je, že je to jediná metoda která není idemopotentní. Čili s každým dalším requestem měním stav daného zdroje/služby.
  • HEAD je podobná metodě GET, s tím rozdílem, že response nevrací body, ale pouze response code a HTTP hlavičky.
  • OPTIONS vrací komunikační možnosti a schopnosti serveru pro dané URI.

Orientace na reprezentaci

Při komunikaci pomocí HTTP metod dostáváme a posíláme na dané URI nějakou reprezentaci zdroje. Podle toho, čím je daný zdroj reprezentován, tak dostáváme/posíláme XML, JSON, YAML apod.

Metodou GET například můžu z nějakého URI dostat takovouto reprezentaci:
<product id="42">
    <name>Kindle Touch</name>
    <currency>USD</currency>
    <price>139</price>
</product>
Pokud bych chtěl zdroj na serveru změnit, pak pošlu metodou PUT (nebo POST) na stejné URI následující reprezentaci (změna ceny):
<product id="42">
    <name>Kindle Touch</name>
    <currency>USD</currency>
    <price>99</price>
</product>
Jestli bych použil PUT nebo POST, by záleželo na designu této služby. Obecně, protože PUT je, podle definice, idempotentní, tak se používá pro update, POST idempotentní není a používá se pro insert.

Nebo jinak definováno - s prvním PUT requestem se založí na serveru zdroj a s každým dalším stejným PUT requestem se tento zdroj aktualizuje. Pokud request obsahuje stejná data, zdroj se nijak nemění (to je ta idempotentnost). Naopak s každým POST requestem by měl na serveru vždy vzniknout nový zdroj. To v reálu často nebývá pravda - POST funguje jako update (stále stejného zdroje), což ale není čistý RESTový design.

Bezstavová komunikace

Bezstavovost znamená, že žádná klientská data nejsou spravována na serveru, tzn. žádná klientská session. Pokud aplikace potřebuje udržovat stavovost, spravuje si ji sama (jako kdyby to byl tlustý klient) a pouze propaguje na server změněné zdroje.

Mezi běžně uváděné benefity bezstavovosti patří: jednoduchost business logiky, škálovatelnost a cacheování resourců.

HATEOAS

Význam této ošklivé zkratky je Hypermedia As The Engine Of Application State. S pochopením tohoto principu jsem měl největší problém. Myšlenka je jednoduchá - datový formát, hypermedia (rošíření hypertextu), poskytuje speciální informaci, jak měnit stav zdroje. A dotaženo do konce - klient by měl komunikovat se serverem pouze pomocí hypermedia a nepotřebuje k tomu žádnou další (technologickou) informaci.

V případě webových služeb je HATEOAS přítomen v podobě odkazů uvnitř (XML/JSON) dokumentů, které představují zdroje. U XML dokumentů se pro odkazy často používá specifikace Atom a z ní konkrétně element link.
<product id="42">
    <link rel="self"
          href="http://amazon.com/product/42"
          type="application/xml"/>
    <link rel="payment"
          href="http://amazon.com/checkout/42"
          type="application/xml"/>
    <name>Kindle Touch</name>
    <currency>USD</currency>
    <price>139</price>
</product>
Jak pojmenovávat jednotlivé relace uvnitř odkazů je částečně standardizováno, takže se používají např. následující (samopopisná) jména: chapter, edit, first, icon, last, next, payment, previous, self, stylesheet ad.

Zamyšlení

Musím přiznat, že v rámci teoretického načtení, se mi RESTová architektura hodně líbí, zejména svojí čistotou a jednoduchostí. Je otázka (času), jak se tento můj pohled změní při použití RESTu v praxi. Tu praxi, bohužel, zatím vidím na nějaký menší projektík, protože jak se poslední dobou věnuju SOA v enterprise sféře, tak musím konstatovat, že REST je v této oblasti opomíjen jak dodavateli řešení, tak zákazníky. Ale snad se časem začne blýskat na lepší časy.