neděle 12. června 2011

Dependency Injection je cesta, nikoliv cíl

Když jsem asi před šesti lety objevil dependency injection (dále v textu DI) jako způsob pro odstranění těsných vazeb mezi objekty, připadalo mi to jako Svatý grál programování. Díky masivnímu rozšíření Spring frameworku a zanesení DI do všech dalších frameworků a standardů, které přišly po něm se DI stalo běžnou součástí dnešního programování. Bohužel DI používáme aniž bychom o tom přemýšleli a na místech kde to není třeba.

V předchozím odstavci jsem DI popsal jako způsob, ano DI je způsob a nebo realizace chcete-li něčeho čemu se říká Inversion of Control, dalším realizací je Service Loator. Klíčové je to, že nechceme naše objekty zatěžovat tím, aby musely vědět o dalších objektech víc než je potřeba, protože tím si na sobě vytvářejí těsnou vazbu. Pokud mají mezi sebou objekty naopak vazbu volnou, umožňuje nám to jejich jednoduší testovatelnost a další výhody.

Posíleni touto mantrou jsme začali vytvářet komplexní grafy popisující vztahy mezi našimi objekty v XML. Asi na sto řádků kódu jsme získali dvacet řádku XML deskriptoru, a teď pozor, aniž by nám to přineslo něco kromě bolehlavu při refaktoru názvů tříd a packagu a dohledávání toho proč to sakra nefunguje s výsledkem, že nějaký šikula změnil název beany.

Vkrádá se mi na jazyk pochopitelná otázka, kdy tedy DI použít a kdy mít prostě těsnou vazbu mezi objekty. Já mám dvě pravidla. Pravidlo číslo jedna, pokud se jedná o infrastrukturu (messaging, transakce, blablabla) a nebo věci spojené s deploymentem vždycky to řešte přes DI. Není nic horšího než si zašpinit business logiku infrastrukturním kódem. Druhé pravidlo zní, DI umožněte, ale zároveň nechte objekt poradit si těsnou vazbou.

public class SweetService {
    private final LittleDao littleDao;
    
    public SweetService() {
        this(new LittleDao());
    }
    
    public SweetService(LittleDao littleDao) {
        this.littleDao = littleDao;
    }
}

Výše uvedený kód přesně tenhle přístup ilustruje. Úmyslně používám zprofanované termíny Service a DAO, o jejichž významu předpokládám, že je čtenáři zřejmý a nemusím tak kód nijak vice komentovat. Třída SweetService umí vytvořit instanci LittleDao bez pomoci zvenčí, a zároveň jí umí přijmout jako argument konstruktoru. Pokud bude někdy potřeba nějaká specializace třídy LittleDao mám pořád možnost použít konstruktor, kde je argumentem. Mimochodem používám to vždy když třídu testuji pomocí Mockita a vkládám si tam mocky, na kterých verifikuji její chování.

V 99% případu si vaše obyčejné objekty vystačí s implicitním konstruktorem, který bude obsahovat těsné vazby. Pokud nastane situace, že se konstrukce zesložiťuje, mělo by přijít zamyšlení jestli náhodou nevytváříte třídu s úlohou alfa samce. V takovém případě je dobré se podívat na kód s trochu větším odstupem a zkusit ho rozbít na menší objekty s jasnou odpovědností.

Nyní je na čase ptát se co tím získáme. Nezávislost kódu na okolí. Objekt dokáže pracovat i bez toho aniž by ho bylo nutné někde registrovat. Menší množství integračních testů (přímá úměra s tím jak rychlé ty testy jsou a jak rychle zjistíte, že se něco někde rozbilo), kterými bychom museli pokrýt externí konfiguraci. Rychlost startu aplikace související s velikostí, které je potřeba zpracovat uvnitř IoC kontejneru. Přehlednost aplikace a kódu, všechno co je mimo kód se špatně dohledává a spravuje.

Když mluvím o konfiguraci je tu samozřejmě možnost používat anotace a tím odstranit, některou z nevýhod externí konfigurace a XML. Anotace jsou dobrou volbou, ale mají svoje nevýhody - těsné navázání na konkrétní typ, nemožnost určit defaultní implementaci (platí pro Spring framework) a dříve nebo později nutnost mixování s XML, protože ne všechno lze pomocí nich vyjádřit.

Pokud DI není cíl, ale cesta, pak cílem by měl být správný objektový návrh. DI k němu dokáže velmi dobře pomoci jenom se musí nad jeho konkrétní použitím přemýšlet v širším kontextu.