5. listopadu 2012

Verzování XSD v SOAP webových službách

Když jsem psal minule o verzování SOAP webových služeb, zaměřil jsem se pouze na verzování v rámci WSDL. Letmo jsem také popsal WSDL strukturu, s tím, že jsem nijak neřešil definici datových typů. Tady se může skrývat další úskalí (nebo příležitost pro) verzování.

Datové typy jsou uvedeny v elementu types. Pokud není definice typů triviální, nebo pokud není ve službě definovaná pouze jedna operace, tak se zpravidla drží definice externě v XSD souboru a do WSDL se pouze naincludují (pokud chceme mít typy ve stejném namespace, jako má WSDL), nebo naimportují (pokud chceme, aby XSD mělo samostatný namespace).

Myslím, že je lepší používat element import. Kromě toho, že je to podmínka, aby kontrakt splňoval WS-I Basic Profile, tak to umožňuje dodržovat následující konvenci:
  1. Součástí namespace WSDL je název služby.
  2. Každá operace má svoje vlastní XSD (v němž je definovaný request a response).
  3. Součástí namespace XSD je název služby a název operace.
  4. Plus samozřejmě verzujeme.
Takže pokud bysme měli službu s názvem MyService a operaci s názvem myOperation, tak by namespacy vypadaly takto:

WSDL:
http://sw-samuraj.cz/ws/MyService-v1

XSD:
http://sw-samuraj.cz/ws/MyService-v1/myOperation

Podobně jako u WSDL, je v namespace uvedena pouze major verze a jelikož je už ji obsahuje název služby (MyService-v1), není potřeba ji duplikovat ještě v názvu operace.

Kromě namespaců verzujeme také samotný XSD soubor, např. myOperation-v1.2.xsd. Což nás staví před následující dilema. Představme si, že máme službu, která obsahuje dvě operace. Něco jako:
<?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>
      <xsd:import
        namespace=
          "<trimmed>/MyService-v1/myOperation"
        schemaLocation="xsd/myOperation-v1.2.xsd"/>
      <xsd:import
        namespace=
          "<trimmed>/MyService-v1/yourOperation"
        schemaLocation="xsd/yourOperation-v1.2.xsd"/>
    </schema>
  </types>

  <!-- messages omitted -->

  <portType name="MyServicePort-v1.2">
    <operation name="myOperation">
      <!-- input, output and fault omitted -->
    </operation>
    <operation name="yourOperation">
      <!-- input, output and fault omitted -->
    </operation>
  </portType>

  <!-- binding omitted -->

  <!-- service omitted -->

</definitions>
Dilema zní: pokud dojde ke změně kontraktu pouze v rámci jedné operace, změním verzi pouze u XSD, které této operaci odpovídá, nebo změním verzi u všech XSD, které jsou součástí kontraktu? (V obou případech samozřejmě verzujeme WSDL tak, jak bylo uvedeno v předešlém článku.)

Pokud by se nám změnila operace myOperation, tak v prvním případě by import vypadal takto:
<types>
  <xsd:schema>
    <xsd:import
      namespace=
        "<shortened>/MyService-v1/myOperation"
      schemaLocation="xsd/myOperation-v1.3.xsd"/>
    <xsd:import
      namespace=
        "<shortened>/MyService-v1/anotherOperation"
      schemaLocation="xsd/anotherOperation-v1.2.xsd"/>
  </schema>
</types>

<portType name="MyServicePort-v1.3">
<!-- rest omitted -->
Výhodou této volby je, že verzujeme pouze XSD, u nichž opravdu došlo ke změně, zbytek necháváme na pokoji. Nevýhodou je, že verze XSD se nám rozjedou - jednak vůči sobě a jednak vůči WSDL. Reálně pak verzujeme XSD jako samostatný artefakt a ne jako součást kontraktu. To může být korektní situace, někdy vznikají XSD bez vazby na WSDL - v rozsáhlých projektech jsou často XSD vytvářena týmy analytiků a jejich import do WSDL zajišťují vývojáři.

V případě druhé možnosti by import vypadal následovně:
<types>
  <xsd:schema>
    <xsd:import
      namespace=
        "<shortened>/MyService-v1/myOperation"
      schemaLocation="xsd/myOperation-v1.3.xsd"/>
    <xsd:import
      namespace=
        "<shortened>/MyService-v1/anotherOperation"
      schemaLocation="xsd/anotherOperation-v1.3.xsd"/>
  </schema>
</types>

<portType name="MyServicePort-v1.3">
<!-- rest omitted -->
Nevýhodou této možnosti je, že musíme převerzovat všechy XSD v kontraktu, i ty, které se nezměnily (což se může zdát nepřirozené). Výhodou je, že kontrakt má jednotné verzování, takže mmj. nemusím kontrolovat jestli mám všechny XSD ve správné verzi. Zároveň je explicitně řečeno, že XSD je součástí kontraktu (ve smyslu objektové kompozice) a kontrakt (WSDL + XSD) by měl být vytvářen jedním týmem.

U nás na projektu o tom byla poměrně bouřlivá diskuze, kterou variantu zvolit. Argumenty byly poměrně vyrovnané a více méně odpovídaly těm uvedeným výše. Nakonec jsme se rozhodli pro jednotný kontrakt, takže se změnou verze WSDL převerzováváme i všechny XSD.

Žádné komentáře:

Okomentovat