Od WSDL k webovej službe: pitveme a tvoríme vlastné WSDL

Novšia verzia

Článok má novšiu verziu na https://novotnyr.github.io/scrolls/od-wsdl-k-webovej-sluzbe-tvorime-vlastne-wsdl/

Úvod

Pri návrhu webových služieb nastáva sa môžeme vydať dvoma rozličnými cestami. Pri tom jednoduchšom postavíme webovú službu vo vhodnej technológii (JAX-WS, či PHP-WSDL Creator, a WSDL si necháme vygenerovať automaticky, čím dosiahneme tzv contract-last spôsob.

Opačný spôsob, contract-first spočíva v nadefinovaní WSDL, na základe ktorého si necháme vygenerovať jednak kostru pre server, a neskôr i klienta.

Postup od WSDL k triedam má mnohé výhody. Ota ‘tapik’ Buchta napr. uvádza:

  • tvůrce se musí nejdříve zamyslet nad strukturou dat, což nebývá tak úplně zvykem
  • to zabrání nutnosti neustále měnit rozhraní (a tím i WSDL) při změně datového modelu
  • tvůrce uvažuje v intencích dokumentů a operací nad nimi, což mu umožní přehoupnout se přes onen RPC XORBA zlozvyk
  • WSDL je čitelné a “hezké” a interoperabilní (pokud je navrženo jako document-wrapped (.NET nic jiného neumí))
  • celý návrh systému je pak mnohem lépe udržovatelný, modularizovatelný, rozšiřitelný, znovupoužitelný, lépe odladitelný

Dokumentácia k Spring Web Services, čo je štandardná knižnica pre contract-first spôsob, podporuje tento prístup ďalšími významnými bodmi, ktoré môžu spôsobovať problémy:

  • neprenositeľné dátové typy (za všetky spomeňme napr. mapy/slovníky) a cyklické objektové grafy
  • so zmenou dát či modelu sa môže zmeniť WSDL, čo má vplyv na robustnosť

V článku si ukážeme, ako je možné vybudovať službu práve týmto zložitejším spôsobom. Ukážeme si, ako možno pochopiť jednotlivé stavebné bloky WSDL, ako ich možno vytvárať ručne, a necháme si tiež vygenerovať zdrojový kód pre JAX-WS server.

Predpokladané znalosti

  • Veľmi vám uľahčí život, ak budete poznať zásady XML a menných priestorov, napr. z krátkeho tutoriálu.
  • Oplatí sa tiež poznať XML schému a zásady jej tvorby.

Príklad a štruktúra dát

V našom príklade budeme chcieť vybudovať webovú službu pre rezerváciu lístkov v kine. Klient zašle požiadavku, v ktorej špecifikuje názov filmu, dátum jeho premietania a počet lístkov, ktoré si chce zarezervovať.

Požiadavka by mohla vyzerať nasledovne:

<movieReservationRequest>
  <title>Krv, črevá a lietajúce hlavy</title>
  <date>2008-12-24</date>
  <numberOfTickets>4</numberOfTickets>
</movieReservationRequest>

Odpoveďou by mohol byť dokument obsahujúci číslo rezervácie a zoznam miest na sedenie, ktoré boli zarezervované:

<movieReservationResponse>
  <id>2323241</id>
  <reservedSeats>23</reservedSeats>
  <reservedSeats>12</reservedSeats>
  <reservedSeats>12</reservedSeats>
</movieReservationResponse>

Na základe týchto XML dokumentov si môžeme napísať XML schému, ktorá bude definovať štruktúru pre dáta na vstupe (požiadavky) a na výstupe (odpovede). Bez definície XML schémy nevieme špecifikovať formát vstupných a výstupných dokumentov vo WSDL.

Vzťah XML schéma-dokument je podobný vzťahu trieda-objekt z OOP. XML schéma (“trieda”) definuje predpis, teda štruktúru, hierarchiu a dátové typy jednotlivých elementov, a konkrétny XML dokument (“objekt”) predstavuje inštanciu tejto XML schémy.

XML schému možno navrhovať viacerými spôsobmi, ale pre nás je najvhodnejší štýl „matrioška” (russian doll). Pre každý z elementov priamo určíme jeho štruktúru.

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
      targetNamespace="urn:X-movies">

  <xsd:element name="movieReservationRequest">
    <xsd:complexType>
      <xsd:sequence>
        <xsd:element name="title" type="xsd:string" />
        <xsd:element name="date" type="xsd:date" />
        <xsd:element name="numberOfTickets" type="xsd:int" />
      </xsd:sequence>
    </xsd:complexType>
  </xsd:element>

  <xsd:element name="movieReservationResponse">
    <xsd:complexType>
      <xsd:sequence>
        <xsd:element name="id" type="xsd:int" />
        <xsd:element name="reservedSeats" 
                     type="xsd:int" 
                     minOccurs="1"
                     maxOccurs="unbounded" />
      </xsd:sequence>
    </xsd:complexType>
  </xsd:element>
</xsd:schema>

V koreňovom elemente dokumentu môže byť buď element movieReservationRequest (ak ide o požiadavku) alebo movieReservationResponse (ak pôjde o dokument z odpovede). Prvý z nich má tri podelementy: title (názov), date (dátum) alebo numberOfTickets (počet lístkov). Druhý z nich je podobný: má id (identifikátor rezervácie) a niekoľko elementov reservedSeats (zoznam miest).

Všetky deklarované elementy budú spadať do cieľového menného priestoru (target namespace) urn:X-movies.

Ukážkový dokument, ktorý spĺňa danú schému, vyzerá nasledovne:

<movieReservationRequest xmlns="urn:X-movies">
  <title>Krv, črevá a lietajúce hlavy</title>
  <date>2008-12-24</date>
  <numberOfTickets>4</numberOfTickets>  
</movieReservationRequest>

Dokumentu sme priradili implicitný menný priestor určený schémou (urn:X-movies).

Schému môžeme dokonca publikovať na konkrétnej webovej adrese, dostupnej cez webový prehliadač. Ak by sa schéma ocitla na adrese http://movie.novotnyr.sk/schema.xsd, môžeme mať dokument, ktorý vieme priamo validovať oproti schéme.

<movieReservationRequest
      xmlns="urn:X-movies"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="urn:X-movies http://movie.novotnyr.sk/schema.xsd">

    <title>Krv, črevá a lietajúce hlavy</title>
    <date>2008-12-24</date>
    <numberOfTickets>4</numberOfTickets>  
</movieReservationRequest>

V tomto dokumente sme deklarovali menný priestor pre inštanciu schémy xsi a samozrejme mapovanie menného priestoru na príslušnú schému: v tomto prípade elementy z menného priestoru urn:X-movies možno validovať oproti schéme zverejnenej na adrese http://movie.novotnyr.sk/schema.xsd.

WSDL súbor

Minimalistický WSDL súbor bez akýchkoľvek deklarácii vyzerá nasledovne:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<wsdl:definitions 
        xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" 
        xmlns:tns="urn:X-movies"
        targetNamespace="urn:X-movies"
        xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"  
        name="movie" 
>

</wsdl:definitions>

Najdôležitejšie sú definície prefixov menných priestorov:

  • prefix wsdl je používaný vo všetkých elementoch, ktorými deklarujeme základné stavebné prvky WSDL. V tomto prípade ho mapujeme na menný priestor http://schemas.xmlsoap.org/wsdl/, ktorý zodpovedá norme WSDL 1.1.
  • prefix tns a atribút targetNamespace obsahujú (rovnaký) cieľový menný priestor pre elementy deklarované v rámci tohto WSDL súboru. Pre jednoduchosť volíme rovnaký cieľový menný priestor ako v prípade XML schémy
  • prefix soap sa vzťahuje na menný priestor http://schemas.xmlsoap.org/wsdl/soap/

Popri tom definujeme logický názov (name) tohto WSDL súboru.

Schéma a dátové typy

V prvom kroku potrebujeme zadefinovať štruktúru vstupných a výstupných dát, čo zabezpečíme sekciou <wsdl:types>. Máme dve možnosti: buď uviesť deklaráciu štruktúry priamo, zanorením elementov XML schémy, alebo sa vieme odkázať na externú XML schému prítomnú v samostatnom súbore.

Priamo uvedená schéma

<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="urn:X-movies" targetNamespace="urn:X-movies" name="movie">
   <wsdl:types>
      <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="urn:X-movies">
         <xsd:element name="movieReservationRequest">
            <xsd:complexType>
               <xsd:sequence>
                  <xsd:element name="title" type="xsd:string" />
                  <xsd:element name="date" type="xsd:date" />
                  <xsd:element name="numberOfTickets" type="xsd:int" />
               </xsd:sequence>
            </xsd:complexType>
         </xsd:element>
         <xsd:element name="movieReservationResponse">
            <xsd:complexType>
               <xsd:sequence>
                  <xsd:element name="id" type="xsd:int" />
                  <xsd:element name="reservedSeats" type="xsd:int" minOccurs="1" maxOccurs="unbounded" />
               </xsd:sequence>
            </xsd:complexType>
         </xsd:element>
      </xsd:schema>
   </wsdl:types>
</wsdl:definitions>

Všimnime si, ako sme medzi elementy <wsdl:types></wsdl:types> bez zmeny vložili celú deklaráciu XML schémy, ktorú sme definovali v predošlej sekcii.

Importnutá schéma

Ak máme XML schému definovanú vo verejne dostupnom súbore prítomnom na adrese http://movie.novotnyr.sk/schema.xsd, môžeme ju priamo importnúť do WSDL súboru.

<wsdl:types>
  <xsd:schema>
    <xsd:import schemaLocation="http://movie.novotnyr.sk/schema.xsd" 
                namespace="urn:X-movies" />
  </xsd:schema>       
</wsdl:types>

Všetky elementy zo schémy sa po importe ocitnú v mennom priestore urn:X-movies, čo je zhodou okolností rovnaký menný priestor ako cieľový menný priestor WSDL schémy. (A v porovnaní s predošlým priamym uvedením schémy: je to rovnaká situácia, ako keď elementy XML schémy uvedieme do príslušného cieľového menného priestoru s použitím targetNamespace na elemente <xml:schema>.

Správy

Ďalej potrebujeme definovať abstraktné správy, ktoré budú putovať medzi klientom a serverom. Je zjavné, že správy budú dve: požiadavka movieReservationRequest a odpoveď movieReservationResponse. Každá bude mať jednu časť part tvorenú príslušným koreňovým elementom.

Požiadavka

Požiadavka bude pozostávať z koreňového elementu movieReservationRequest v mennom priestore s prefixom tns (ten je namapovaný na cieľový menný priestor http://movie.novotnyr.sk/ws/types.

<wsdl:message name="movieReservationRequest">
  <wsdl:part name="movieReservationRequestPart" element="tns:movieReservationRequest"/> 
</wsdl:message>

Odpoveď

Odpoveď na požiadavku pozostáva z koreňového elementu movieReservationResponse, ktorý sa nachádza tiež v mennom priestore s prefixom tns.

<wsdl:message name="movieReservationResponse">
  <wsdl:part name="movieReservationResponsePart" element="tns:movieReservationResponse"/> 
</wsdl:message>

Typy pre port s operácie v ňom

Teraz definujeme typ pre port, ktorý bude logicky združovať niekoľko operácií. Môžeme sa naň dívať ako na analógiu Java interfejsu, kde operáciám zodpovedajú metódy.

Typ pre port predstavuje definíciu, ktorá uvádza, ktoré operácie sú na porte k dispozícii, a ako vyzerajú vstupné a výstupné dokumenty. Zatiaľ nie je nič povedané o konkrétnom protokole (HTTP, Jabber, a podobne), čiže inými slovami definujeme len syntax správ a sadu operácií nad nimi.

V ukážke definujeme typ pre port s názvom movieReservationPort s jedinou operáciou movieReservation. V operácii definujeme odkaz na vstupnú správu (tns:movieReservationRequest) a tiež na výstupnú správu (tns:movieReservationRequest).

<wsdl:portType name="movieReservationPort">
  <wsdl:operation name="movieReservation">
    <wsdl:input message="tns:movieReservationRequest" />
    <wsdl:output message="tns:movieReservationResponse"  />
  </wsdl:operation>
</wsdl:portType>

Binding

Až v tejto chvíli sa posunieme od abstraktnej syntaxe správ ku konkrétnemu tvaru odchádzajúcich a prichádzajúcich správ. Ten definujeme pomocou bindingu, ktorého úloha je:

  • definovať transportný protokol: napr. HTTP, Mail, či Jabber
  • štýl správ: konkrétny tvar volaní operácií.

Každý typ pre port môže definovať svoj vlastný binding, čo znamená, že môžeme definovať rozličné komunikačné protokoly pre rozličné operácie v porte. Konkrétny binding môže byť bližšie špecifikovaný pre každú operáciu zvlášť.

V príklade definujeme binding movieReservationBinding, ktorý asociujeme s typom portu tns:movieReservationPort, ktorý sme definovali pred chvíľou. Ďalej definujeme transportný protokol: bude ním SOAP nad HTTP v štýle document.

<soap:binding style="document" 
              transport="http://schemas.xmlsoap.org/soap/http" />

Následne musíme pre každú operáciu definovanú v type portu určiť konkrétny fyzický formát jednotlivých správ. Pre operáciu movieReservation budeme pre vstup i výstup používať správy v štýle literal.

Jednotlivé štýly správ sú v tomto momente nepodstatné. Štýl, ktorý má najväčšiu podporu medzi klientmi, je document/literal. Podrobnosti možno nájsť v článku Which style of WSDL should I use?)

<wsdl:binding name="movieReservationBinding"  
               type="tns:movieReservationPort">
   <soap:binding style="document" 
                 transport="http://schemas.xmlsoap.org/soap/http" />
   <wsdl:operation name="movieReservation">
     <wsdl:input>
       <soap:body use="literal"/>
     </wsdl:input>
     <wsdl:output>
       <soap:body use="literal"/>
     </wsdl:output>      
   </wsdl:operation>
 </wsdl:binding>

Služba

Ako poslednú náležitosť nastavíme službu service, ktorá definuje konkrétnu adresu, na ktorej sú jednotlivé porty a operácie fyzicky dostupné. Služba logicky zoskupuje viacero portov, pričom každý port predstavuje kombináciu typu pre port s konkrétnym bindingom. V našom prípade definujeme službu movieReservationService obsahujúcu port movieReservationPort, ktorý sa vzťahuje k bindingu tns:movieReservationBinding a zároveň nastavíme URL adresu, na ktorej bude služba zverejnená.

<wsdl:service name="movieReservationService">
  <wsdl:port name="movieReservationPort" 
             binding="tns:movieReservationBinding">
    <soap:address location="http://localhost:8080/movies/ws"/>
  </wsdl:port>
</wsdl:service>

Sumár

Ak si zosumarizujeme veci definované vo WSDL, tak máme:

  • typy – definujú syntax (štruktúru + dátové typy) prenášaných dát vo vnútri správ
  • správy – definujú abstraktnú štruktúru prenášaných zásielok (požiadaviek a odpovedí).
  • typ pre portu – definuje množinu operácií spolu so správami, ktoré sú v nich používané
  • binding – predstavuje konkrétny protokol a konkrétnu reprezentáciu správ
  • typ pre port + binding = port. Inak povedané, port získame dodaním protokolu k abstraktnej definícii typu portu.
  • služba – zhromažďuje viacero portov

Kompletné WSDL

Kompletné WSDL si môžeme zobraziť ilustračne na obrázku:

wsdl

Kompletné WSDL vyzerá nasledovne:

<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="urn:X-movies" targetNamespace="urn:X-movies" name="movie">
    <wsdl:types>
        <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="urn:X-movies">
            <xsd:element name="movieReservationRequest">
                <xsd:complexType>
                    <xsd:sequence>
                        <xsd:element name="title" type="xsd:string" />
                        <xsd:element name="date" type="xsd:date" />
                        <xsd:element name="numberOfTickets" type="xsd:int" />
                    </xsd:sequence>
                </xsd:complexType>
            </xsd:element>
            <xsd:element name="movieReservationResponse">
                <xsd:complexType>
                    <xsd:sequence>
                        <xsd:element name="id" type="xsd:int" />
                        <xsd:element name="reservedSeats" type="xsd:int" minOccurs="1" maxOccurs="unbounded" />
                    </xsd:sequence>
                </xsd:complexType>
            </xsd:element>
        </xsd:schema>
    </wsdl:types>
    <wsdl:message name="movieReservationRequest">
        <wsdl:part name="movieReservationRequestPart" element="tns:movieReservationRequest" />
    </wsdl:message>
    <wsdl:message name="movieReservationResponse">
        <wsdl:part name="movieReservationResponsePart" element="tns:movieReservationResponse" />
    </wsdl:message>
    <wsdl:portType name="movieReservationPort">
        <wsdl:operation name="movieReservation">
            <wsdl:input message="tns:movieReservationRequest" />
            <wsdl:output message="tns:movieReservationResponse" />
        </wsdl:operation>
    </wsdl:portType>
    <wsdl:binding name="movieReservationBinding" type="tns:movieReservationPort">
        <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" />
        <wsdl:operation name="movieReservation">
            <wsdl:input>
                <soap:body use="literal" />
            </wsdl:input>
            <wsdl:output>
                <soap:body use="literal" />
            </wsdl:output>
        </wsdl:operation>
    </wsdl:binding>
    <wsdl:service name="movieReservationService">
        <wsdl:port name="movieReservationPort" binding="tns:movieReservationBinding">
            <soap:address location="http://localhost:8080/movies/ws" />
        </wsdl:port>
    </wsdl:service>
</wsdl:definitions>

Literatúra

Pridaj komentár

Vaša e-mailová adresa nebude zverejnená. Vyžadované polia sú označené *