CQRS e event sourcing

CQRS e Event Sourcing: il nostro primo progetto andato in produzione – Parte 2 Read Side

Pubblicato il da

Riccardo

Riccardo

Partner

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.

class Loan implements ReadModelInterface, SerializableInterface
{
   private $loanId;
   private $status;
   private $approvedAt;
   ...

   public function __construct(LoanId $loanId)
   {
       $this->loanId = $loanId;
   }

   /**
    * @param mixed $approvedAt
    */
   public function setApprovedAt($approvedAt)
   {
       $this->approvedAt = $approvedAt;
   }

   /**
    * @param mixed $status
    */
   public function setStatus($status)
   {
       $this->status = $status;
   }

   public static function deserialize(array $data)
   {...}

   public function serialize()
   {...}
}

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:

namespace Soisy\Domain\Loan\Projector;

class LoanProjector extends Projector
{
   /**
    * @var RepositoryInterface
    */
   private $repository;

   /**
    * @param RepositoryInterface $repository
    */
   public function __construct(RepositoryInterface $repository)
   {
       $this->repository = $repository;
   }

   protected function applyLoanWasRequested(LoanWasRequested $event)
   {
      $readModel = new Loan($event->getLoanId());

      $readModel->setInstalmentsNumber($event->getInstalmentsNumber());
      $readModel->setAmount($event->getAmount());
      ...
      $readModel->setRequestedAt($event->getRequestedAt());

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

   protected function applyLoanApproved(LoanApproved $event)
   {
      /** @var Loan $readModel */
      $readModel = $this->repository->find($event->getLoanId()); (1)
      $readModel->setApprovedAt($event->getApprovedAt()); (2)
      $readModel->setStatus($event->getStatus()); (2)
      $this->repository->save($readModel); (3)
   }
}

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. Il post precedente su ‘CQRS e Event Sourcing: il nostro primo progetto andato in produzione’ lo trovi qui: Write side.