CQRS ed Event Sourcing: Il nostro primo progetto andato in produzione – Parte 4 Implementiamo un api per l’approvazione di un prestito

cqrs

Per sviluppare Soisy abbiamo utilizzato symfony2broadway: prendendo come esempio uno dei casi d’uso dell’applicazione (opportunamente semplificato), in questo post vi mostreremo come l’abbiamo implementato.

Questo lo scenario: il cliente che ha richiesto un prestito dovrà fornire diverse informazioni (codice fiscale, nome, cognome ecc) . Alla fine di questo flusso verrà generata la richiesta di approvazione del prestito. L’approvazione avviene in tempo reale senza tempi di attesa.

Vediamo cosa succede lato backend quando viene invocata l’api per richiedere l’approvazione:

Command bus e repository per il read model Loan sono stati registrati come servizi nel container. In questo ci viene in aiuto il BroadwayBundle che registra dei servizi e mette a disposizione comandi da console per creare o cancellare l’event store.

Il comando viaggia sul command bus e viene gestito dal relativo Command Handler. Broadway utilizza la convezione handleNOME_COMANDO

Dopo che l’aggregato è stato ricostruito, viene chiamato il metodo approve.

Alla fine di ogni metodo di un aggregato viene chiamato il metodo apply che permette di collezionare una serie di eventi, che successivamente verranno salvati nell’event store grazie al repository utilizzato nel Command Handler.

In un approccio CQRS puro, avremo salvato l’aggragato vero e proprio. In event sourcing salviamo gli eventi, che rappresentano lo stato del nostro aggregato.

Come detto negli articoli precenti, una volta salvati gli eventi vengono pubblicati dall’event bus e mandati a tutti i projector che sono in ascolto. Broadway utilizza la convenzione handleNOME_EVENTO.

Questo metodo del projector aggiorna il read model e lo salva.

Il controllo torna al controller symfony che esegue una query ottenendo il read model aggiornato e lo restituisce al chiamante.

E’ importante notare che tutto il flusso avviene all’interno di una singola richiesta PHP, quindi in maniera sincrona. Nel caso in cui ci sia l’esigenza di renderlo asincrono c’è un interessante approccio che prevede l’utilizzo di code per la proiezione dei dati e quindi la costruzione dei read model.

Nel prossimo (e ultimo post) trarremo le conclusioni su questa esperienza che tuttora stiamo portando avanti anche su un altro progetto.

CQRS ed Event Sourcing: Il nostro primo progetto andato in produzione – Parte 3 Testing

cqrs

In ideato crediamo molto nella qualità del codice e di conseguenza nei test automatici. In Soisy non abbiamo fatto eccezione e come vedremo a breve, grazie alle componenti di Broadway, fare test unitari sulla nostra logica di business è veramente facile.

Anche in questo caso andremo ad implementare due tipologie di test unitari rispettivamente per la scrittura e per la lettura.

Come testare un comando (e quindi controllare che il giusto evento verrà salvato)

Negli articoli precedenti abbiamo visto come possiamo approvare un prestito ovvero:  “dato che un prestito è stato richiesto, quando viene inviato il comando ApproveLoan allora dovrà essere salvato un evento LoanApproved”.

Con broadway possiamo implementare il test proprio come descritto sopra:

Broadway implementa la classe Scenario che espone un’interfaccia fluente con in metodi given, when, then e internamente utilizza un InMemory repository come mock dell’event store. Cosi facendo i test sono isolati dal database e molto veloci.

Given prende come parametro un array di eventi che verranno applicati all’aggregato Loan il quale verrà quindi ricostruito e portato in un determinato stato, che per noi è quello che assume dopo che l’utente ha richiesto un prestito.

When prende un comando che verrà inviato all’aggregato Loan. In sostanza verrà chiamato il metodo Loan::approve il quale applicherà l’evento LoanApproved con lo stato desiderato ovvero con $status == ‘OK’ dato che stiamo testando un approvazione.

Then è il metodo che effettua l’asserzione di uguaglianza tra il vero evento applicato (actual) e l’evento LoanApproved che gli stiamo passando come parametro (expected).

Stiamo quindi testando che dato un comando verrà salvato l’evento che ci aspettiamo, con lo stato voluto.

Come testare la proiezione (e quindi che il giusto read model sia salvato)

Applichiamo gli stessi concetti per il testing del read model ovvero: “dato che è stato richiesto un prestito, quando il prestito verrà approvato allora il read model verrà correttamente aggiornato e salvato”.

Come potete vedere il gioco è sempre lo stesso solo che questa volta, la when prende un evento dato che il read model verrà modificato solo dopo che l’evento è accaduto. Il then si assicura che il read model generato dal codice di produzione sia quello che ci aspettiamo ovvero $expecetdLoan.

Broadway mette a disposizione nel repository git degli esempi di utilizzo.
Nel prossimo post vedremo come implementare un’api in cqrs + event sourcing.

CQRS ed Event Sourcing: Il nostro primo progetto andato in produzione – Parte 2 Read Side

cqrs

Read Model

Avere un unico modello per lettura e scrittura, all’aumentare della complessità ci porta spesso in situazioni difficili da mantenere come ad esempio l’utilizzo di query complesse per reperire il modello stesso. Il read model ci permette di avere una rappresentazione “denormalizzata” del dato, che semplifica tantissimo la logica della view e delle query di lettetura. In soisy ad esempio una delle prime cose che dovevamo modellare erano i prestiti e quindi abbiamo creato un Aggregate\Loan.php  per modellare la scrittura e un ReadModel\Loan.php per modellare la lettura.

Questo oggetto rappresenta il dato da mostrare ed espone solo comportamenti utili alla presentazione.

I metodi serialize e deserialize come per l’evento servono rispettivamente preparare il dato ad essere salvato e per idratare l’oggetto per essere utilizzato.

Projector

I projector sono oggetti che stanno in ascolto su determinati eventi con i quali riescono a costruire i read model.

Ogni volta che un evento viene salvato nell’event store, tale evento viene “pubblicato” e inviato ai relativi subscriber (ovvero i projector), che utilizzeranno i dati trasportati dall’evento per costruire il read model relativo.

Vediamo un esempio:

Precedentemente abbiamo salvato l’evento LoanApproved. A questo punto broadway ci mette a disposizione un event bus che pubblica l’evento. L’event bus ha la responsabilità di consegnare gli eventi pubblicati a tutti le parti sottoscritte implementando il classico pattern publish/subscriber. Broadway la libreria che stiamo utilizzando per implementare CQRS/ES mette a disposizione questa interfaccia e due implementazioni funzionanti del bus.

In tutti i projector che stanno in ascolto su questo evento, verrà eseguito il metodo applyLoanApproved che prenderà il read model precedentemente salvato alla LoanWasRequested (1), lo aggiornerà con i nuovi dati arrivati (2) e lo salverà nel read model store (3).

Nel prossimo post parleremo di testing dei nostri comandi, eventi, projector e read model.

CQRS ed Event Sourcing: Il nostro primo progetto andato in produzione – Parte 1 Write Side

cqrs

Da circa un anno in ideato stiamo sviluppando Soisy, una piattaforma di social lending che ha l’obiettivo di offrire servizi bancari come prestiti, investimenti e pagamenti da privato a privato, senza l’intermediazione di una banca. Abbiamo modellato il dominio del software utilizzando i pattern CQRS e Event Sourcing.

Racconteremo la nostra esperienza in una serie di post a partire da questo.

Cos’è CQRS

CQRS (Command/Query Responsability Segregation) è un pattern utilizzato per la modellazione del dominio di un software.  

Tipicamente ogni azione che viene eseguita in un software può essere definita come comando o come query: il comando è un’operazione che modifica lo stato del sistema, la query è un’operazione di lettura che serve a reperire delle informazioni lasciando il sistema inalterato. Paragonandolo ai verbi definiti in HTTP, un comando potrebbe rappresentare una POST mentre la query una GET.

Se ci pensate, queste due azioni sono concetti ben separati che spesso tendiamo a racchiudere in un unico modello di dominio. CQRS si basa proprio su questa idea: comandi e query dovrebbero essere tenuti separati. Nel nostro dominio ogni modello dovrebbe essere pensato per la lettura o per la scrittura, in scenari complessi infatti, un modello che si occupa sia di scrittura che di lettura potrebbe facilmente crescere a dismisura diventando presto difficile da gestire e quindi costoso. Da qui la scelta di spezzare il modello in due parti distinte e separate: “write side” e “read side”.

Cos’è Event Sourcing

Event Sourcing è un pattern che utilizza gli eventi come strumento per modellare la logica di business. Lo stato dell’applicazione e i suoi cambiamenti vengono rappresentati come una sequenza di eventi salvati nella base di dati, chiamata event store. Non esistono operazioni di modifica o cancellazione di eventi, siamo in append mode e tutto quello che è successo nel nostro dominio è rappresentato da questa sequenza. Pensiamo, ad esempio, a cosa succede nella realtà quando commettiamo un errore: non possiamo cancellare l’errore commesso ma possiamo compiere un’azione per porvi rimedio. L’azione compiuta risolve l’errore ma non lo cancella dal tempo: questo è quello che succede all’interno di un dominio modellato ad eventi.

Differentemente da un comando che rappresenta un azione che si sta compiendo nel presente, come ad esempio “approve a loan”, l’evento rappresenta un’azione passata ed è descritta come tale, come ad esempio “loan was approved”.

Andiamo un po’ più in profondità per cercare di capire meglio quali sono le componenti coinvolte e come implementare la modellazione del nostro dominio tramite l’unione di questi due pattern.

Quella che vedrete è un implementazione di cqrs + event sourcing realizzata tramire l’utilizzo di broadway, ma non l’unica possibile. Broadway è una libreria PHP che mette a disposizioni delle componenti utili ad implementare questi pattern.

Comando

Il comando rappresenta un’azione che viene eseguita sul sistema: in OOP può essere rappresentato come un oggetto il cui nome descrive l’azione e il suo stato rappresenta tutta l’informazione necessaria a compiere quell’azione.

Il comando è dunque il messaggio inviato all’aggregato radice ( Aggregate Root – AR) e contiene i dati necessari ad eseguire la logica di business rappresentata dall’azione.

Nella nostra applicazione, abbiamo implementato i comandi come oggetti immutabili per renderne più sicuro l’utilizzo.

Evento

L’evento rappresenta una cosa successa nel nostro sistema: in OOP può essere rappresentato come un oggetto immutabile il cui nome descrive l’azione successa e il suo stato rappresenta il nuovo stato persistito.

I metodi serialize e deserialize servono rispettivamente a preparare il dato ad essere salvato e per idratare l’oggetto per essere utilizzato.

Come vedremo tra poco, ad ogni comando inviato all’aggregato radice, viene emesso e salvato un evento che rappresenta il cambiamento di stato dovuto all’azione richiesta.

Aggregato

Un aggregato rappresenta il cuore del nostro dominio o di una parte di esso. Può essere visto come un grafo di oggetti (o anche un unico oggetto) che definisce logiche di business. Generalmente si individua un singolo oggetto del grafo, detto aggregato root, che ne espone tutto il comportamento. L’aggregato root è l’unico punto d’accesso all’intero aggregato.

Questo oggetto ha la responsabilità di ricevere il comando, eseguire logica di business  rispettando le invarianti e infine generare eventi che verrano poi salvati sull’event store. Le invarianti rappresentano dei vincoli di dominio che impedisco all’aggregato di assumere degli stati inconsistenti ovvero stati che non esistono nel dominio e che quindi non dovrebbero esistere nell’aggregato. 

Lo stato di un aggregato viene sempre ricostruito dallo stream dei suoi eventi. Ogni evento ha sempre un ID che rappresenta l’identità del nostro aggregato (loanId). La sequenza di eventi con lo stesso ID rappresentano la storia del nostro aggregato e quindi tutti i suoi stati da quando è stato creato ad oggi.

Nell’esempio sopra, l’aggregato root Loan espone un metodo approve che riceve le informazioni dal comando ApproveLoan (poi vedremo come), controlla che il Loan non sia già stato approvato, esegue della logica per calcolare lo status ed infine “applica” l’evento LoanApproved che verrà successivamente salvato.

L’apply esegue il metodo applyLoanApproved, che riceve l’evento appena creato e lo utilizza per aggiornare lo stato dell’aggregato. Come vedremo nel prossimo paragrafo, questo metodo viene utilizzato anche per ricostruire lo stato dell’aggregato.

Nell’aggregato troverete sempre due tipologie di metodi: applyNOME_EVENTO che ha la sola responsabilità di aggiornare lo stato dell’aggregato, e metodi pubblici come “approve” che si occupano esclusivamente di eseguire logica di business. Evitate di inserire della logica all’interno dei vari applyNOME_EVENTO, dato che renderebbe più difficile fare test, fare debug e complicherebbe la ricostruzione dell’aggregato.

A questo punto potreste avere un dubbio: “per ogni evento che viene applicato all’interno dell’aggregato, devo scrivere il rispettivo metodo applyNOME_EVENTO ?”

La risposta è no. Come abbiamo detto, questi metodi servono per aggiornare lo stato e quindi vanno aggiornate solo le proprietà utili alla logica di business. Nell’esempio sopra, viene aggiornata solo la proprietà approvedAt che serve per capire se un prestito è già stato approvato.

Introduciamo ora un altro componente che abbiamo utilizzato per permettere di ricostruire l’aggregato e inviare il comando ApproveLoan: il Command Handler.

Command Handler

Il Command Handler non è un oggetto di dominio ed esegue queste tre azioni:

(1) ricostruisce lo stato dell’aggregato portandolo a quello corrente,

(2) invia il comando all’aggregato tramite l’aggregato root,

(3) persiste gli eventi generati dall’aggregato.

Il metodo load ricostruisce l’aggregato applicando tutto lo stream dei suoi eventi. Se ad esempio nello stream c’è l’evento LoanWasRequested, verrà chiamato il metodo protetto dell’aggregato root applyLoanWasRequested che andrà appunto ad assegnare i dati relativi della richiesta di prestito.

Dopo avere riportato l’aggregato al suo stato corrente, viene chiamato il metodo approve che genera l’evento LoanApproved il quale viene salvato nell’event store tramite il metodo save.
A questo punto abbiamo completato la nostra “write side”.

 

Nel prossimo post vedremo come implementare la “read side”.