Komunikace mezi moduly

Komunikace mezi moduly

Pro komunikaci mezi moduly využíváme neinvazivního způsobu pomocí událostí po vzoru Observer. Tento vzor adoptoval SpringFramework pomocí ApplicationListener, ApplicationEvent a ApplicationEventPublisher rozhranní (pro účely této kapitoly je nutná znalost této Spring funkcionality).

Tento způsob komunikace je pro komunikaci modulů velmi vhodný - autor modulu, tak může bez znalosti budoucího použití deklarovat seznam událostí, které modul dává svému okolí na vědomí.

V případě, že v konkrétním použití nebude žádný modul tyto události konzumovat, nechá je systém jednoduše proběhnout bez dopadu. Pokud se naopak někdo v určité chvíli rozhodne konkrétní událost využívat (opět bez vědomí daného modulu), může tak i jen konfiguračně učinit (viz. například deklarace makra reagujícího na událost v modulu).

Zpracování událostí se děje synchronně ve stejném vlákně, které událost vyvolalo. Tj. okamžitě jak v kódu vyvoláte:

java
1 @Autowired2ApplicationEventPublisher eventPublisher;3
4void myMethod(ApplicationEvent event) {5  eventPublisher.publish(event)6}

provede se zpracování události dalšími moduly. Je-li metoda obklopena transakční anotací, bude i zpracování události v ostatních modulech transakční (pokud si transakční chování samy neupraví).

Tímto způsobem je také možné jednoduše zajistit i řízené selhání operace z vnějšku. Pokud například metodu, která vyvolává událost obklopíme transakčním kontextem, může listener reagující na tuto událost vyvolat vyjímku, která vyvolá selhání původní volané metody a rollback transakce. S touto možností je rozhodně nutné při návrhu počítat.

CPS rozšíření pro moduly implementujeme vlastní Multicaster objekt, který umožňuje přidávat / odebírat aplikační listenery za běhu modulu. Pro dynamickou kontrolu listenerů slouží metody:

  • com.fg.webapp.cps.v1.modules.spring.context.DynamicApplicationEventMulticaster#addApplicationListener
  • com.fg.webapp.cps.v1.modules.spring.context.DynamicApplicationEventMulticaster#removeApplicationListener
  • com.fg.webapp.cps.v1.modules.spring.context.DynamicApplicationEventMulticaster#getApplicationListeners

Při použití listeneru ve spring modulech pak stačí deklarace ExternalEventListener v kontextu modulu.

Zkopírovat odkaz na sekciVeřejné a privátní události modulu

Standardní chování Spring Frameworku při zpracování událostí je ten, že pokud událost vypálíte do kontextu, Spring provede zavolání všech registrovaných listenerů tohoto kontextu a následně pošle událost dál do nadřízeného kontextu.

Toto chování pro moduly není vhodné, protože takto by si moduly, které existují všechny jako podřízené kontexty root kontextu, události předávat nikdy nemohly. Proto existuje dvojice rozhranní com.fg.webapp.cps.v1.modules.spring.PublicApplicationEvent a com.fg.webapp.cps.v1.modules.spring.ExternalEventListener.

PublicApplicationEvent je marker rozhranní, které označuje události, které jsou součástí veřejného rozhranní modulu a které mohou být konzumovány ostatními moduly.

ExternalEventListener je rozhranní, které musí být implementováno ApplicationListenery, které mají zájem konzumovat události okolních modulů. Toto rozhranní deklaruje metodu, pomocí které listener vrátí typy událostí, o které má zájem.

Framework dále zajistí distribuci požadovaných PublicApplicationEventů do aplikačních kontextů modulů, které prostřednictvím ExternalEventListenerů deklarovaly, že mají o tyto události zájem.

Moduly mohou používat bez obav i soukromé ApplicationEventy pro vlastní potřeby, aniž by se musely obávat toho, že jiný modul bude těchto interních pochodů využívat. Pokud ApplicationEvent neimplementuje rozhranní com.fg.webapp.cps.v1.modules.spring.PublicApplicationEvent, jiný modul se k této události nedostane.

Zkopírovat odkaz na sekciPriorita zpracování

Pokud na stejnou událost reaguje více listenerů, lze řešit vzájemnou prioritu pomocí Spring anotace Order

Příklad listeneru s definovanou prioritou:

java
1 @Order(value = 100)2public class ContentBlockTraitProcessingListener implements ApplicationListener<ContentBlockEvent>, ExternalEventListener {3
4    @Override5    @Transactional6    public void onApplicationEvent(@Nonnull ContentBlockEvent contentBlockEvent) { }7
8    @Override9    public Class[] getExternalEventsTypes() {10        return new Class[]{ContentBlockEvent.class};11    }12}