neděle 5. dubna 2009

Tranzitivní závislosti v Mavenu z pohledu návrhu modulu

Tranzitivní závislosti v Mavenu neodpouštějí chyby v návrhu struktury modulů. Pojďme se podívat na to s čím je potřeba počítat. Mějme modul Foo. Tento modul je použit jednak v kontextu serveru, jako součást webové aplikace, a jednak v lokálním kontextu jako součást command line nástroje. Modul Foo má pro svojí řadu několik závislostí. Řekněme jednu společnou pro obě prostředí a potom dvě rozdílné množiny, každou pro jedno prostředí, jak je znázorněno na následujícím obrázku.

Mechanismus tranzitivních závislostí v Mavenu způsobí, že pokud modul Foo přidáme do WARu, dostanou se nám tam všechny závislosti a to včetně těch lokálních (žluté krabičky). A naopak, pokud si necháme vygenerovat například classpath pro lokální použití, dostanou se nám tam závislosti nutné pouze pro prostředí serveru. V případě, že by šlo pouze o malé množství závislostí, asi by to nevadilo, ale v případě, že se jedná o větší množství, musíme se tím začít zaobírat.

První a nejlepší způsob je udělat refaktor modulu Foo a ten rozdělit na tři části. Na modul obsahující logiku pro server Foo server, modul obsahující logiky pro lokální použití Foo local a modul obsahující logiku společnou pro obě části Foo common. Tento poslední modul vznikne pouze za předpokladu, že existuje sdílená logika. Zároveň s rozčleněním do těchto modulů roztrhneme i závislosti původního Foo modulu. Ideální stav zobrazuje následující obrázek.

No nežijeme v ideálním světě a tak se může ukázat, že modul Foo půjde refaktorovat velice složitě, pokud vůbec, neboť se může jednat o dědictví z minulosti. V takovém případě máme dvě možnosti. Tam kde modul Foo chceme použít, uděláme nechtěným závislostem přítrž jejich explicitním vynecháním. Toto řešení má tu nevýhodu, že na každém místě použití modulu Foo musíme tento manuální krok zopakovat. Pokud nám v budoucnu nastane situace, že modul Foo bude potřebovat novou závislost, řekněme kolizní závislost, ať již pro serverové či lokální prostředí, budeme muset opravit manuální odstranění o další závislost.

Druhou možností je využití takzvaných volitelných (optional) závislostí, které mají tu vlastnost, že nejsou součástí tranzitivního uzávěru. Na dalším obrázku jsou volitelné závislosti vyznačeny čárkovaně. Díky tomu, že nejsou součástí tranzitivního uzávěru, tak je sice moduly, které je používají "nenakoupí", ale zároveň to znamená, že je budou muset opět explicitně uvést tam kde budou potřeba. To ilustruje následující obrázek, kdy si WAR explicitně přidává volitelné závislosti.

I toto řešení má stejnou nevýhodu v podobě manuálního opakovaní všech volitelných závislostí na každém místě použití a s tím spojený problém budoucího rozšíření. Pro eliminaci této nevýhody lze použít následujícího triku. Zavedeme si speciální moduly pro obě prostředí, které nám poslouží pouze k seskupení závislostí. Díky tomu budeme mít management závislostí pro dané prostředí centralizovaný pouze do jednoho místa. Tyto ad hoc moduly je pak možné použít namísto původního modulu Foo.

Tento seskupovací workaround lze použít v podstatě na úpravu jakýchkoliv nevyhovujících závislostí a lze jej vhodně kombinovat s explicitním vynecháním závislostí. Je škoda, že Maven nenabízí podobný mechanismus urovnání závislostí nějakým lepším způsobem. Na druhou stranu na to lze koukat i tak,že Maven vynucuje správné rozčlenění modulů a schválně nenabízí prostředky pro jejich obcházení.