Architettura Pub/Sub
Attualmente in Maestrale, utilizziamo molto spesso webcomponent per modellare la visualizzazione di dati eterogenei provenienti da diverse fonti: abbiamo webcomponents che si occupano della visualizzazione dei dati provenienti da microservizi che interrogano PLC, oppure microservizi che ritornano statistiche di gestione di apparecchi industriali.
L’interfaccia di questi componenti, staccati l’uno dall’altro, deve essere ‘responsiva’, cambiare rapidamente e in contemporanea in base a determinati input dell’utente, oppure a determinati stati dell’applicativo.
Per gestire la comunicazione tra i diversi componenti, essenzialmente indipendenti, e gestire il flusso di azioni abbiamo pensato di utilizzare un’architettura publisher/subscriber: mittenti e destinatari dialogano attraverso un dispatcher (o bus o canale di comunicazione) mandandosi dei messaggi.
Nella fattispecie entrano in gioco dei publisher (mittenti del messaggio) e dei subscriber (destinatari del messaggio). Lo schema di azione è questo
- Uno o più publisher (nella nostra realtà webcomponent) pubblicano un messaggio(o payload) sul dispatcher (un EventBus)
- Uno o più subscriber (controller specifici del nostro framework MVC o webcomponent) si ‘abbonano’ alla ricezione di uno o più tipologie di messaggio
- Il dispatcher inoltra ogni messaggio ai subscriber interessati allo specifico messaggio.
Event Bus
Per poter creare un dispatcher di messaggi da/per Webcomponents che venissero gestiti lato framework, abbiamo implementato un basilare EventBus: per il nostro caso, una piattaforma di gestione eventi, globalmente accessibile, richiamabile, per pubblicare o sottoscrivere messaggi, da qualsiasi oggetto JavaScript.
L’implementazione si basa sull’utilizzo di un oggetto che supporti gli eventi. Abbiamo utilizzato un semplice Element del DOM che esegue il dispatch degli eventi e a cui vengono registrati dei listner di eventi… E’ più semplice vederlo :)
const EventBus = () => {
let _bus = document.createElement('div');
const _register = (event, callback) => {
_bus.addEventListener(event, callback);
};
const _remove = (event, callback) => {
_bus.removeEventListener(event, callback);
};
const _fire = (event, detail = {}) => {
_bus.dispatchEvent(new CustomEvent(event, { detail }));
};
return {
register: _register,
remove: _remove,
fire: _fire
}
};
window.EventBus = EventBus();
Utilizzo
Supponiamo di avere un webcomponent dummy composto da una pulsantiera: i pulsanti della pulsantiera triggerano dei metodi che cambiano lo stato dei pulsanti stessi e della pulsantiera. Vogliamo inoltre che il click su un pulsante specifico faccia scatenare l’aggiornamento di un altro webcomponents che espone dei dati in una griglia dopo una chiamata REST ad un servizio.
Il webcomponent della pulsantiera dovrà utilizzare l’EventBus per eseguire il trigger del custom event ‘BUTTONBAR_CLICK_BTN’ al click dei pulsanti sulla pulsantiera, qualora il pulsante sia quello scelto (naturalmente questo dipende dall’architettura e dall’analisi di dominio della applicazione da sviluppare)
// webcomponents-buttonbar.js
const $btn = document.querySelector(".buttonbar button");
$btn.addEventListener("click", (e) => {
// make stuff on UI of webcomponents
// fire event
const triggerFire = <some condition to perform triggering>;
triggerFire && window.EventBus.fire("BUTTONBAR_CLICK_BTN", { target: e.target });
});
Il webcomponent della griglia nel frattempo si preoccuperà di registrare un listener per l’evento specifico ‘BUTTONBAR_CLICK_BTN’. Quando l’evento viene scatenato dal publisher (webcomponent-buttonbar), il listner del subscriber (webcomponent-grid) verrà invocato.
// webcomponents-grid.js
connectedCallback() {
window.EventBus.register("BUTTONBAR_CLICK_BTN", this.updateGrid({store: this.STORE}));
}
updateGrid() {
//perform some REST call for retrieving data
//UI update (better in other method)
}
Questo è solo un semplice esempio del possibile uso di un EventBus per far dialogare ‘ogetti’ diversi e che non risiedano in nessuna gerarchia di dipendenza reciprocamente, cosi come webcomponent pensati per essere ‘moduli’ importabili/esportabili facilmente e velocemente nelle nostre web application senza preoccuparci delle dipendenze.
Vantaggi
Più che vantaggi ad utilizzare un EventBus, possiamo parlare dei vantaggi dell’utilizzo dell’architettura Publisher/Subscriber. Tra gli altri ho individuato quelli che, secondo me, possono risultare davvero convenienti in ambito web.
Separazione delle responsabilità: ciascun componente (non inteso solamente come webcomponent) della web application può occuparsi delle proprie specificità, lo scambio dei messaggi e l’instradamento sono demandati al dispatcher.
Una web application può comunicare con uno o più servizi o componenti sviluppati in modo indipendente, i quali potrebbero usare piattaforme, linguaggi di programmazione e protocolli di comunicazione diversi: devono solo registarsi al dispatcher per ‘ascoltare’ uno o più messaggi, e devono solo pubblicare sul dispatcher per mettersi in comunicazione con altri componenti
Si può pianificare o posticipare l’uso dei messaggi: i subscriber possono prelevare i payload ed eseguire il listner specifico in base a determinate soglie orarie, oppure il publisher può instradare i payload in base a una pianificazione specifica
Aumenta e migliora la testabilita: i bus possono essere monitorati e i payload inviati controllati o registrati come parte di una seria strategia di test di integrazione
Questa è solo una breve panoramica sull’uso di tale pattern. Ho evidenziato solo vantaggi, ma naturalmente esistono anche degli svantaggi da soppesare negli specifici casi d’uso.