CQRS e event sourcing

CQRS e Event Sourcing: il nostro primo progetto andato in produzione – Parte 4 Implementiamo un api

Pubblicato il da

Riccardo

Riccardo

Partner

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:

//Controller symfony
public function updateAction(Request $request, $loanId)
{
   $commandBus = $this->get('broadway.command_handling.command_bus');

   //il metodo dispatch si occupa di recapitare il comando al giusto command handler
   $commandBus->dispatch(new ApproveLoan(new LoandId($loandId), new \Datetime()));

   //otteniamo il read model tramite una semplice find
   $loanReadModel = $this->get('soisy.read_model.repository.loan')->find($loanId);

   return new JsonResponse($loanReadModel->serialize());
}

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

//Command handler
protected function handleApproveLoan(ApproveLoan $command)
{
   $loan = $this->repository->load($command->getLoanId());       
   $loan->approve($command->getApprovedAt());
   $this->repository->save($loan);
}

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

//Aggregato
public function approve(\DateTime $approvedAt)
{
   if ($this->approvedAt) { //esempio di invariante
       throw new LoanAlreadyApproved();
   }

   $status  = … calcola status (OK/KO)… ; //logica di business

   $this->apply( //generazione evento
       new LoanApproved(
           $this->loanId,
           $status,
           $approvedAt    
       )
   )
}

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.

//Projector
protected function applyLoanApproved(LoanApproved $event)
{
   /** @var Loan $readModel */
   $readModel = $this->repository->find($event->getLoanId());

   $readModel->setApprovedAt(new \DateTime($event->getApprovedAt()));
   $readModel->setStatus($event->getStatus());

   $this->repository->save($readModel);

}

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.

È 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. I post precedenti su ‘CQRS e Event Sourcing: il nostro primo progetto andato in produzione’ li trovi qui: Write side, Read side, Testing.