Část #4: Modulární systémy ve Spring Framework
Aplikační události jsou jedním ze základních stavebních kamenů Springu a proto by bylo škoda se ochudit o tuto skvělou vlastnost na rozhraní modulů. Je zřejmé, že nebudeme chtít otevřít všechny aplikační události svému okolí, nicméně u řady událostí bychom chtěli umožnit ostatním modulům reagovat. Jako příklad uvedu interakci mezi modulem pro správu uživatelů a notifikačním modulem - notifikační modul se stará o rozesílání emailových notifikací v reakci na konkrétní aplikační události (samozřejmě obecně - konfigurovatelně). To je typická ukázka stavu, kdy chceme, aby uživatelský modul dokázal emitovat třebas událost “založení nového uživatele” tak, aby notifikační modul mohl reagovat odesláním emailu.
Přeposílání eventů z jednoho modulu do jiného modulu
Další lahůdkou je možnost posílání událostí z oddělených aplikačních kontextů. Standardně se vám multicaster postará o vyvolání listenerů na úrovni aplikačního kontextu, ze kterého pochází beana, která událost vyvolala (respektive ApplicationEventPublisher, který použijete pro distribuci události). Dále je událost propagována do nadřazených kontextů. Nedostane se vám tedy do aplikačních kontextů, které nejsou ve stromě kontextů nad tím vaším.
V praxi se ovšem může hodit, mít možnost na události “modulu” reagovat v jiném modulu. Některé události v podstatě mohou tvořit část rozhraní daného modulu (jistě většina událostí bude pouze pro vnitřní účely, některé by se ale hodilo propagovat vně a zařadit je jako součást rozhraní).
Jak tedy zajistit propagaci těchto událostí i do modulů na stejné úrovni? Opět k tomu můžeme využít BeanPostProcessor. Zavedeme nové rozhraní s názvem ExternalEventListener, které budou implementovat ty listenery, které budou chtít naslouchat událostem okolních modulů. Rozhraní ExternalEventListener má metodu getExternalEventClasses, která vrací pole objektů Class těch externích událostí, kterým chtějí naslouchat.
Dále zavedeme marker rozhraní PublicEvent, které budou implementovat aplikační události, které modul považuje za veřejné (tzn. zařazuje je do svého rozhraní).
V kořenovém kontextu zaregistrujeme aplikační listener s názvem ExternalEventPropagateListener. Listenerem v root contextu proběhnou všechny události (tedy i události jednotlivých modulů). Tento listener udržuje mapu aplikačních kontextů (respektive libovolných jiných objektů), které si přejí “přeposlat” externí události. Listener si eviduje vždy seznam Class aplikačních událostí, o které má daný objekt zájem a v případě, že takovou událost zachytí, provede přeposlání události danému registrovanému objektu. Před vlastní registrací je prověřeno, zda modul skutečně požaduje přeposílání pouze veřejných událostí - pokud nikoliv, je vyhozena vyjímka IllegalArgumentException.
Výše zmíněný BeanPostProcesor s názvem ExternalEventPropagatePostProcessor nám při startu modulu prověří každou beanu, zda implementuje rozhraní ExternalEventListener a pokud ano, zaregistruje do kořenového kontextu aktuálně procesovaný aplikační kontext se seznamem Class aplikačních eventů, které právě procesovaný listener požaduje.
Celý výše uvedený proces jsem ještě zakreslil jako sequence diagram, jelikož při takovém množství kódu by mohla být celková představa o fungování celého procesu poněkud zmatená. Pokud tomu tak je, snad se mi podaří nejasnosti rozptýlit následujícím diagramem:
Závěr seriálu
Dnešní díl uzavírá seriál o modulárních systémech ve Springu. Popsané řešení dostatečně naplňuje naše požadavky na modulární serverový systém. Je možné že v budoucnosti třebas sáhneme po OSGI, ale v současné době toto jednoduché řešení postačuje.
Sami vidíte, že popsaná implementace, nejde za hranice Springu a že se jedná jen o nenáročné řešení, která vám nezabere víc jak jeden, dva dny implementace a otestování ve vašich podmínkách.
Hlavní přínos vidím v tom, že je jednoduše možné skládat nezávislé knihovny = moduly s přesným vydefinováním jejich rozhraní (nikoliv na úrovni class API, ale na úrovni živých funkčních objektů daného modulu). Naopak můžeme vydefinovat podmnožinu (typicky servisních bean), které budou pro všechny moduly společné, čímž je možné ve výsledku efektivně zjednodušit správu výsledné kompozice.
S čím si zatím nejsem 100% jistý, zda bude možné bez problémů vytvářet transakce zahrnující operace na beanami z různých modulů (tj. aplikačních kontextů) pomocí AOP. Teoreticky by to mělo fungovat bez větších potíží, ale jelikož nejsem schopný věc zpatra domyslet a nenašel jsem zatím čas si na to napsat test (ještě jsem to nepotřeboval), nechci to tu prezentovat jako fakt.
Doufám, že vám seriál líbil a že jej považujete za přínosný. Budu vám vděčný, když mi na závěr napíšete nějaký komentář k němu, i kdyby ten komentář měl být pouze ve smyslu, že čas strávený jeho čtením pro vás nebyl čas ztracený. Předem díky za reakce … statistiky jsou jedna věc a přímá zpětná vazba je věc jiná
.





10.04.2007 v 20:01
Skvělá práce. Pro mě jsou články tohoto typu ty vůbec nejzajímavější.
10.05.2007 v 7:55
Výborný seriál, doufám že brzo přibudou další.
10.29.2007 v 17:16
Díky moc za tento článek. Sám vím, kolik je za tím práce dát takovýto článek dohromady, takže fakt super.
S těmi transakcemi si také myslím, že nebude problém - jak v rámci jednoho aplikačního kontextu, tak v rámci více kontextů resp. root kontextu. Jen v tvém pojetí se bude muset naimplementovat transakční interseptor programově, protože ty vkládáš veřejné beany dynamicky za běhu.
10.30.2007 v 5:55
Dík za pochvalu
.
Jinak myslím, že by nebylo ani potřeba řešit transakční interceptor programově. Ony ty beany jsou totiž normálně vytvářené Springem a Spring je tudíž může normálně obalit proxy objekty - jen se vše odehraje v kontextu nižší úrovně. Já pak jen v závěru už hotovou “public” beanu vezmu a zaregistruji ji také do nadřízeného kontextu.
V TODO listu mám zaznamenané, abych v tomhle ohledu teorii podpořil praxí, takže jakmile ten test napíšu, hodím sem o tom ještě dodatek. Tohle je podle mého názoru právě věc, která v modulárním systému postaveném na OSGI nepůjde.
08.08.2008 v 8:50
Ahoj Honzo, bavil jsem se dnes s naším technologickým šéfem vývoje o projektu P@W (PeopleAtWork) a nasazení Springu a modulárnosti. Zjistil jsem, že v P@Wu kolega naimplementoval modulární řešení propagovaných bean implementujících rozhraní ModulePropagatedBean. Pokud ho beana implementuje, je postrčena do Root kontextu a je k dispozici ostatním modulům. Hodně mi to připomínalo tvoje řešení, tak jsem mu ukazoval tvoje stránky, a on na to, no vždyť podle tohohle jsem to naprogramoval
:)!
Takže shrnuto a podtrženo, v našem produktu je použit tvůj návrhový vzor pro Spring z tohoto seriálu - řešení modularity jednotlivých komponent a poskládání výsledného Spring kontextu tak, aby zároveň vznikl fungující aplikační kontext + aby byla udržena nezávislost modulů - balení v samostatném Jaru.
Tušil jsem, že ti to udělá radost, tak to píšu ;).
08.10.2008 v 18:18
No tak to jsem rád, že nápad nezůstane zapomenut. Přesně z tohoto důvodu jsem celou sérii psal. Pokud byste měli s řešením nějaké pozitivní / negativní zkušenosti, tak vám budu vděčný, pokud je sem připíšete. My to řešení docela uspokojivě provozujeme rok, tak by mě zajímala zase vaše zkušenost.