Minulý týden jsem zase trochu programoval, psal jsem jednu webovou službu. Ta měla WSDL definované třetí stranou, takže to byla poměrně jednoduchá a rutinní záležitost. Vzal jsem XML schema, z něj vygeneroval XmlBeans a začal jsem implementovat. Narazil jsem ale na jeden problém, který se mi nedařilo vyřešit.
Měl jsem vyrobit čtyři webové služby, jejichž odpovědi se lišily jen v pár detailech. V zásadě vypadaly takto:
<XXXResponse> <Version>1</m:Version> <RequestId>XYZ123</RequestId> <Result>SUCESS</Result> <XXXTransactionId>ABC456</XXXTransactionId> </XXXResponse>
Místo XXX si představte různé fáze transakce. Můj problém spočíval v tom, že jsem nechtěl mít v programu čtyřikrát ten samý kus kódu pro generování odpovědi, chtěl jsem ho tam mít jen jednou. Jenže se to nedařilo, zkoušel jsem všelijaké finty s dědičností, vymýšlel jsem sofistikované wrappery, ale pořád jsem měl čtyři docela ošklivé bloky, které si byly ohromě podobné. Jediný rozdíl byl v jménu odpovědi a jménu transactionId. Ale tyto drobné rozdíly mi bránily v tom, udělat znovupoužitelný kód pro všechny webové služby.
Až mě napadla jednoduchá věc. Vždy jsem tvrdil, že generování kódu je zlo, kterému je potřeba se za každou cenu vyhnout. Ale u webových služeb jsem to tak nějak bral za dané. Znáte to, contract first přeci spočívá v tom, že začnu s návrhem XSD a podle něj píši kód. Je jen přirozené si ten kód pak nechat vygenerovat. A to je právě ta chyba. XML a Java jsou různé světy. Ano jsou si docela podobné, ale každý má jiný účel, jiné zvyklosti a jiné potřeby. Problém je, že, generovaný kód vždy odráží svůj původ.
Když generuji kód na základě XML, dostanu anemický model se kterým se mi pak špatně pracuje. Často pak skončím u toho, že mapuji vygenerované třídy na nějaké svoje vymazlené třídy a pak zase zpátky.
Když na druhou stranu generuji XSD na základě Javy, riskuji, že se s výsledným XML bude mým klientům špatně pracovat, že bude špatně rozšířitelné a podobně.
Je to podobný problém jako u objektově relačního mapování. Objekty a tabulky jsou si hrozně podobné, ale přesto je mezi nimi obrovská propast. Pamatuji si doby, kdy jsem generoval Javovské třídy na základě tabulek. Výsledek byl vždy žalostný. V poslední době jednoduše namapuji ručně psanou Javu na ručně optimalizovanou databázi a obě strany jsou spokojené.
Proč něco podobného nejde u XML? Pokud jste byli na mém lightning talku, možná si vzpomenete jak jsem si stěžoval, že v roce 2010 musím ručně mapovat XML třídy na normální třídy a zpátky. Mýlil jsem se, nemusím. Mohu udělat to samé jako s databází. Díky JAXB 2 mohu jednoduše namapovat svoje třídy na XML. Samozřejmě, v první iteraci si mohu pro jednoduchost ty třídy vygenerovat, ale pak už je ručně upravím a nikdy je negeneruji znovu.
A přesně tak jsem vyřešil svůj problém s duplicitou kódu. Ručně jsem si upravil JAXB třídy. Nejdřív jsem si vyrobil abstraktního předka všech odpovědí. To by ještě šlo zařídit úpravou XML schematu. Ale pak jsem do toho abstraktního předka přidal abstraktní metodu setTransactionId a dokonce jsem ho nechal implementovat nějaké rozhraní. To jsou koncepty, které v XML vůbec nemají smysl! Tím pádem ani nemá smysl pomýšlet na jejich generování.
Tím, že jsem udělal JAXB třídy nezávislé na XSD, jsem si zajistil, že je nemusím kopírovat do jiných interních tříd. Mohu svůj business kód udělat závislý jen na rozhraních, které XML třídy implementují. Konkrétně u mě to ušetřilo desítky řádků ošklivého kódu.
Kdybych měl dost odvahy, mohly bych dokonce i k těmto třídám přidat JPA anotace a ukládat je bez obav do databáze. Ale tak silný žaludek zatím ještě nemám.
Samozřejmě i tento přístup má své nevýhody. Asi největší je, že se mi může Java odchýlit od XML schematu. Může se mi stát, že udělám chybu, a začnu generovat nevalidní XML. Od toho ale máme testování, můžete použít třeba moji úžasnou knihovnu, která vám to pohlídá.
Zkuste se nad tím zamyslet. Generovaný kód nefungoval nikdy, vždy s ním byly jen problémy. Ale u webových služeb jsem ho alespoň já bral jako hotovou věc. Když se ho ale zbavíme, ušetříme si spoustu starostí. Náš kód může vypadat o něco víc objektově a my si užijeme mnohem víc legrace s programováním užitečných věcí.