Command Query Responsibility Segregation pattern

V parte: Repositories ed Event Stores in Axondi

La serie sul pattern architetturale Command Query Responsibility Segregation (CQRS) continua con l'approfondimento dei macroblocchi che compongono il framework Axon, implementazione Java di tale pattern. In questo articolo è la volta di Repositories and Event Stores.

Introduzione

Continuiamo la scoperta dei macroblocchi che compongono il framework Axon. In questa parte è la volta di Repositories and Event Stores. Qualora non lo aveste già fatto, per una migliore comprensione di quello corrente, suggerisco di leggere preliminarmente anche i due articoli sul pattern CQRS [2] [3] e i precedenti due sul framework Axon [4] [5].

La versione di Axon a cui si fa riferimento è sempre la 1.4.

Repositories and Event Stores

Per repository si intende il meccanismo che consente di accedere agli aggregates. Un repository funge da gateway al meccanismo di immagazzinamento utilizzato per rendere persistenti i dati. In CQRS i repositories hanno bisogno solamente di essere in grado di trovare gli aggregates in base al loro identificatore univoco. Qualsiasi altro tipo di query deve essere eseguita sul Query Database e non sul repository.

In Axon tutti i repositories devono implementare l'interfaccia org.axonframework.repository.Repository. Essa prevede tre metodi: load(identifier, version), load(identifier) e add(aggregate). Il metodo load() consente di caricare aggregates da un repository. Il primo parametro è l'identificatore univoco dell'aggregate, mentre il secondo (opzionale) viene usato per rilevare eventuali modifiche concorrenti. Il metodo add() viene usato per aggiungere nuovi aggregates ad un repository. L'interfaccia Repository non prevede un metodo di cancellazione di aggregates. Tale operazione va eseguita invocando il metodo protected markDeleted() dell'aggregate che si vuole cancellare. Il motivo per cui tale metodo è protected (e quindi non accessibile all'esterno)  è che un aggregate è il solo responsabile del mantenimento del proprio stato. La cancellazione infatti è un cambiamento di stato come gli altri, anche se in molti casi è irreversibile. I repositories devono invocare il metodo isDeleted() di un aggregate per sapere se esso è stato marcato come deleted. Qualora si tenti di ricaricare un aggregate in stato deleted, un repository lancia un eccezione di tipo org.axonframework.repository.AggregateNotFoundException oppure di tipo org.axonframework.eventsourcing.AggregateDeletedException.

Standard Repositories

La soluzione più semplice per implementare un repository è costituita dagli standard repositories. Essi memorizzano lo stato corrente di un aggregate. Al verificarsi di un cambiamento di stato, il nuovo sovrascrive quello vecchio. Questo fa si che sia i query components che i command components di una applicazione usino le stesse informazioni. Axon fornisce delle classi astratte per poter implementare un repository di questo tipo.

L'implementazione più elementare può essere fatta estendendo la classe astratta org.axonframework.repository.AbstractRepository. Questa si occupa di pubblicare l'evento ad ogni salvataggio di un aggregate. L'implementazione di un repository a partire da tale classe non prevede alcun meccanismo di locking e si aspetta che questo sia fornito dal sottostante meccanismo di storage dei dati. Qualora non sia fornito anche da quest'ultimo allora, per prevenire eventuali modifiche concorrenti agli aggregates, bisogna implementare un repository a partire dalla classe astratta org.axonframework.repository.LockingRepository.

In questo modo è possibile definire una strategia di locking (ottimistica o pessimistica). In caso di lock ottimistico, quando viene rilevato un accesso concorrente, il thread che tenta di salvare l'aggregato per secondo riceve una eccezione di tipo org.axonframework.repository.ConcurrencyException. Il lock pessimistico invece previene accessi concorrenti a tutti gli aggregates. Se non viene specificato nulla, quest'ultima è la strategia impostata di default per il LockingRepository di Axon.

Axon fornisce anche una terza classe astratta per l'implementazione di repository standard, org.axonframework.repository.GenericJpaRepository. In questo modo è possibile eseguire lo storage di aggregates compatibili con JPA [6].

Event Sourcing Repositories

Come accennato nell'articolo precedente [5], gli aggregate roots che implementano l'interfaccia org.axonframework.eventsourcing.EventSourcedAggregateRoot possono essere salvati in un event source repository. Tali repositories non salvano gli aggregate, ma la serie di eventi generati da questi ultimi.

La classe astratta org.axonframework.eventsourcing.EventSourcingRepository fornisce le funzionalità base necessarie per poter implementare un event sourcing repository in Axon. L'implementazione dipende, oltre che dall'EventStore (di cui parleremo nel prossimo paragrafo), anche dal tipo di AggregateFactory scelto. Quest'ultima è responsabile della creazione di istanze di aggregates non inizializzate e specifica quali saranno e come bisogna crearle.

Una volta che un aggregate è stato creato, l'EventSourcingRepository può inizializzarlo usando gli eventi caricati dall'EventStore. Axon mette a disposizione due implementazioni della interfaccia org.axonframework.eventsourcing.AggregateFactory:
org.axonframework.eventsourcing.GenericAggregateFactory
e
org.axonframework.eventsourcing.SpringPrototypeAggregateFactory.

GenericAggregateFactory può essere usata per qualsiasi tipo di Event Sourced Aggregate root. Essa crea istanze di aggregates del tipo gestito dal repository. La classe Aggregate deve essere non astratta e deve avere un costruttore senza argomenti che non deve eseguire alcun tipo di inizializzazione. GenericAggregateFactory è adatta per tutti gli scenari dove gli aggregates non necessitano di speciale injection di oggetti non serializzabili.

A seconda delle esigenze architetturali di una applicazione, talvolta potrebbe però risultare utile iniettare delle dipendenze negli aggregates tramite Spring [7]. Per fare ciò bisogna configurare nello Spring context applicativo un prototype bean che definisce anche una SpringPrototypeAggregateFactory. In questo modo essa potrà creare istanze di aggregates usando lo Spring Application Context e non più tramite costruttore.

Nei casi particolari in cui nessuna delle implementazioni fornite da Axon soddisfi le nostre esigenze, nulla vieta di creare la propria implementazione di AggregateFactory.

Infine, una ulteriore possibile implementazione di repository in Axon è org.axonframework.eventsourcing.HybridJpaRepository. Quest'ultima è una combinazione di GenericJpaRepository ed Event Sourcing Repositories. Può trattare solo event sourced aggregates  e può memorizzarli sia in un modello relazionale, sia in un event store. In lettura invece può utilizzare solo un modello relazionale.   

Event Stores

Gli Event Sourcing Repositories necessitano di un event store per memorizzare e caricare gli eventi dagli aggregates. Axon fornisce due implementazioni di event store:

org.axonframework.eventstore.fs.FileSystemEventStore

e

org.axonframework.eventstore.jpa.JpaEventStore

Entrambe sono in grado di memorizzare tutti i tipi di eventi e utilizzano un Serializer per serializzare e deserializzare gli eventi. L'implementazione di Event Serializer di default in Axon è org.axonframework.eventstore.XStreamEventSerializer. Come si evince dal nome di tale classe, gli eventi vengono serializzati in XML.  

Il FileSystemEventStore memorizza gli eventi in un file nel file system. Esso fornisce buone prestazioni e una facile configurazione. Il rovescio della medaglia di questo event store è che non fornisce supporto per le transazioni e non si adatta molto bene in caso di ambiente cluster. L'unica configurazione necessaria è la location dove esso può memorizzare i propri file e il Serializer da utilizzare per serializzare e deserializzare gli eventi.

JpaEventStore memorizza gli eventi in un data source compatibile con JPA. Esso  supporta le transazioni, a differenza della versione su file system. JpaEventStore memorizza gli eventi nelle cosiddette entries. Queste contengono la forma serializzata di un evento oltre ad alcuni campi in cui sono memorizzati meta-dati per la ricerca rapida di tali entries. Per utilizzare JpaEventStore  bisogna includere nel classpath le annotations di javax.persistence. Per default, un event store necessita che la sua configurazione all'interno del persistence  context (definito nel file META-INF/persistence.xml) contenenga la classi org.axonframework.eventstore.jpa.DomainEventEntry e org.axonframework.eventstore.jpa.SnapshotEventEntry.

Vediamo un esempio:


      
              org...eventstore.jpa.DomainEventEntry
              org...eventstore.jpa.SnapshotEventEntry
      

Un'ultima nota per quanto riguarda la serializzazione. Abbiamo detto prima che gli event stores necessitano di serializzare gli eventi prima di salvarli e che la classe che viene utilizzata di default è XStreamEventSerializer. Essa ricorre alla libreria XStream [8] per serializzare domain events in XML e deserializzarli. XStream è piuttosto veloce è più fressibile della serializzazione nativa di Java. In più il risultato della serializzazione di XStream è human readable e quindi questo comporta vantaggi per il logging e il debugging. XStreamEventSerializer è altamente configurabile ed è possibile quindi definire alias per determinati packages, classi o anche semplicemente fields. Inoltre è possibile definire degli alias per le classi che definiscono event changes, in modo da accorciare nomi potenzialmente troppo lunghi.

Conclusioni

In questo articolo abbiamo visto in dettaglio il macroblocco di Axon relativo ai Repositories e agli Event Stores. Nel prossimo vedremo finalmente in dettaglio l'implementazione di una web application CQRS and Axon based e infine l'ultimo dei macro blocchi di tale framework, quello relativo all'event processing.

Riferimenti

[1] Sito ufficiale di Axon framework

http://www.axonframework.org/

 

[2] Guglielmo Iozzia, "Command Query Responsibility Segregation pattern. I parte: Breve panoramica su CQRS", MokaByte 177, Ottobre 2012

http://www2.mokabyte.it/cms/article.run?articleId=7TK-XI4-MRI-VMN_7f000001_13046033_40989f48

 

[3] Guglielmo Iozzia, "Command Query Responsibility Segregation pattern. II parte: Quando utilizzarlo?", MokaByte 178, Novembre 2012

http://www2.mokabyte.it/cms/article.run?articleId=AKD-QEF-VCL-OUP_7f000001_13046033_f3c5af0b

 

[4] Guglielmo Iozzia, "Command Query Responsibility Segregation pattern. III parte: Introduzione ad Axon Framework", MokaByte 179, Dicembre 2012

http://www2.mokabyte.it/cms/article.run?permalink=mb179_CQRS-3

 

[5] Guglielmo Iozzia, "Command Query Responsibility Segregation pattern IV parte: Domain Modeling in Axon framework", MokaByte 181, Febbraio 2013

http://www2.mokabyte.it/cms/article.run?articleId=F79-K2B-PYN-G2N_7f000001_26089272_9f04d42c

 

[6] JPA website @ Oracle

http://www.oracle.com/technetwork/java/javaee/tech/persistence-jsp-140049.html

 

[7] Sito ufficiale di Spring framework

http://www.springsource.org/

 

[8] Sito ufficiale di XStream

http://xstream.codehaus.org/

 

Condividi

Pubblicato nel numero
182 marzo 2013
Guglielmo Iozzia si è Laureato nel 1999 in Ingegneria Elettronica (indirizzo Biomedico) presso l‘Università di Bologna. Ha progettato e realizzato un software diagnostico per la predizione dell‘andamento della pressione intracranica in pazienti in terapia intensiva neurochirurgica. Frequenta il mondo Java dall‘inizio del 2000. Dopo numerose esperienze presso un‘azienda di Bologna…
Articoli nella stessa serie
Ti potrebbe interessare anche