Nei mesi scorsi siamo andati alla scoperta dei concetti base di CQRS e del framework Axon \[1\], implementazione Java di tale pattern. In quest’ultimo articolo della serie vedremo un esempio completo di applicazione basata sul framework in esame. Come accennato nelle conclusioni del precedente articolo, la release di riferimento stavolta sarà la 2.0.3 (ultima stabile) e non più la 1.4, con la quale avevamo cominciato. Per la migrazione di progetti che usano la release 1.4 alla release 2.0 si può consultare l’utile guida riportata al link \[2\]. Per una migliore comprensione del codice, si consiglia la lettura preliminare degli articoli precedenti di questa serie: la trattazione di quello corrente presuppone la conoscenza dei concetti base e dei blocchi logici di Axon.
Creazione del progetto
Per prima cosa creiamo un nuovo progetto Java in Eclipse. Quindi importiamo nel build path il core di Axon (la libreria axon-core-2.0.3.jar). Per semplicità, in questo esempio supporremo di avere un unico aggregate, ToDoItem (nel mondo reale il domain model ovviamente sarà più complesso, ma a scopo didattico è meglio semplificare per chiarire immediatamente i concetti). Aggiungiamo ad esso un unico attributo, id, di tipo String. Facciamo estendere a ToDoItem la classe org.axonframework.eventsourcing.AbstractEventSourcedAggregateRoot, l’implementazione di base fornita da Axon per gli aggregate. Implementiamo quindi i metodi getIdentifier() e handle() di tale classe astratta.
Il codice di ToDoItem
Il codice di ToDoItem al momento risulta quindi essere il seguente:
public class ToDoItem extends AbstractEventSourcedAggregateRoot {\
private String id;\
\
public ToDoItem() {\
}\
\
@Override\
public Object getIdentifier() {\
return id;\
}\
\
@Override\
protected Iterable getChildEntities() {\
// TODO Auto-generated method stub\
return null;\
}\
\
@Override\
protected void handle(DomainEventMessage arg0) {\
if (eventMessage.getPayloadType().equals(ToDoItemCreatedEvent.class)) {\
ToDoItemCreatedEvent event =\
(ToDoItemCreatedEvent) eventMessage.getPayload();\
this.id = event.getTodoId();\
}\
}\
}
**getIdentifier()** restituisce semplicemente l’identificatore della classe **ToDoItem**. Nel metodo handle() mettiamo il codice necessario per l’Event specifico. Prima di completare l’implementazione della classe dobbiamo implementare un Command ed un Event per **ToDoItem**. Chiamiamo il Command **CreateToDoItemCommand**: esso è relativo alla creazione di un nuovo **ToDoItem**:
public class CreateToDoItemCommand {\
\
@TargetAggregateIdentifier\
private final String todoId;\
private final String description;\
\
public CreateToDoItemCommand(String todoId, String description) {\
this.todoId = todoId;\
this.description = description;\
}\
\
public String getTodoId() {\
return todoId;\
}\
\
public String getDescription() {\
return description;\
}\
}
L’annotation @TargetAggregateIdentifier serve ad indicare qual’è l’attributo target del Command. L’implementazione dell’Event generato dall’esecuzione di tale Command è:
public class ToDoItemCreatedEvent {\
\
private final String todoId;\
private final String description;\
\
public ToDoItemCreatedEvent(String todoId, String description) {\
this.todoId = todoId;\
this.description = description;\
}\
\
public String getTodoId() {\
return todoId;\
}\
\
public String getDescription() {\
return description;\
}\
\
@Override\
public String toString() {\
return “ToDoItemCreatedEvent(“ + todoId + “, ‘“ + description + “‘)”;\
}\
}
Queste ultime due implementazioni sembrano simili. In realtà lo sono, ma solo perchè stiamo esaminando un contesto molto semplice avente un solo aggregate. Man mano che il domain model si complica, le implementazioni di un Command e dell’Event relativo differiscono sempre di più.
Completiamo ToDoItem
A questo punto possiamo completare la classe ToDoItem, partendo dalla creazione di un CommandHandler. A tale scopo aggiungiamo un nuovo costruttore annotato con @CommandHandler:
@CommandHandler\
public ToDoItem(CreateToDoItemCommand command) {\
apply(new ToDoItemCreatedEvent(command.getTodoId(),\
command.getDescription()));\
}
In questo modo abbiamo un CommandHandler per il command CreateToDoItemCommand. L’invocazione del metodo apply() della classe AbstractEventSourcedAggregateRoot fa capire ad Axon che si vuole applicare l’evento ToDoItemCreateEvent all’aggregate. Infine bisogna annotare con l’annotazione @AggregateIdentifier l’attributo che funge da identificatore per l’aggregate:
@AggregateIdentifier\
private String id;
e implementare (sempre tramite annotation) l’**EventHandler** per l’evento **ToDoItemCreateEvent**
@EventHandler\
public void on(ToDoItemCreatedEvent event) {\
this.id = event.getTodoId();\
}
Esso imposta il valore dell’id dell’aggregate al momento della sua creazione.
Infrastruttura ed esecuzione
A questo punto non ci resta che costruire l’infrastruttura per poter eseguire l’applicazione. In questo paragrafo descriveremo come impostarla e configurarla senza fare rifermento ad alcun tipo di user interface: le considerazioni presentate sono indipendenti da essa: quindi valgono per qualsiasi tipo di applicazione (standalone da riga di comando, standalone con intefaccia grafica, web, etc.).
L’infrastruttura deve provvedere a fornire i seguenti blocchi:
- Command Bus
- Command Gateway
- Event Sourcing Repository
- Event Store
- Event Bus
Il tutto può essere fatto in Java puro:
// Init the Command Bus\ CommandBus commandBus = new SimpleCommandBus();\ \ // Init the CommandGateway: it provides a friendlier API\ CommandGateway commandGateway = new DefaultCommandGateway(commandBus);\ \ // Store Events on the FileSystem, in the “events/” folder\ EventStore eventStore = new FileSystemEventStore(\ new SimpleEventFileResolver(new File(“./events”)));\ \ // Init a Simple Event Bus\ EventBus eventBus = new SimpleEventBus();\ \ // Configure the Event Sourcing Repository\ EventSourcingRepository repository =\ new EventSourcingRepository(ToDoItem.class);\ repository.setEventStore(eventStore);\ repository.setEventBus(eventBus);\ \ // Tell Axon that our ToDoItem Aggregate can handle commands\ AggregateAnnotationCommandHandler.subscribe(ToDoItem.class, repository, commandBus);\ \ // Send some Commands on the CommandBus.\ final String itemId = UUID.randomUUID().toString();\ commandGateway.send(new CreateToDoItemCommand(itemId, “Need to do this”));\ commandGateway.send(new MarkCompletedCommand(itemId));
Oltre alla soluzione Java puro, si può ricorrere anche a Spring \[3\]. In questo caso bisogna aggiungere al progetto anche le dipendenze di tale framework: Axon richiede la release 3.0.0 o successiva. Tutti i blocchi vengono definiti in un context file, chiamato, per esempio, axonContext.xml:
\<beans xmlns=“http://www.springframework.org/schema/beans”\ xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance”\ xmlns:axon=“http://www.axonframework.org/schema/core”\ xsi:schemaLocation=“http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd\ http://www.axonframework.org/schema/core http://www.axonframework.org/schema/axon-core-2.0.xsd”\>\ \ \<axon:command-bus id=“commandBus”/\>\ \<axon:event-bus id=“eventBus”/\>\ \ \<axon:event-sourcing-repository id=“toDoRepository”\ aggregate-type=“org.axonframework.test.sample.ToDoItem”/\>\ \ \<axon:aggregate-command-handler id=“toDoItemHandler”\ aggregate-type=“org.axonframework.test.sample.ToDoItem”\ repository=“toDoRepository”\ command-bus=“commandBus”/\>\ \ \<axon:filesystem-event-store id=“eventStore” base-dir=“events”/\>\ \ \<bean class=“org.axonframework.commandhandling.gateway.CommandGatewayFactoryBean”\>\ \<property name=“commandBus” ref=“commandBus”/\>\ \</bean\>\ \ \</beans\>
Il bootstrap
Infine bisogna implementare il bootstrap file, in questo modo:
private CommandGateway commandGateway;\
\
public ToDoItemRunner(CommandGateway commandGateway) {\
this.commandGateway = commandGateway;\
}\
\
public static void main(String\[\] args) {\
ApplicationContext applicationContext =\
new ClassPathXmlApplicationContext(“axonContext.xml”);\
ToDoItemRunner runner =\
new ToDoItemRunner(applicationContext.getBean(CommandGateway.class));\
runner.run();\
}\
\
private void run() {\
final String itemId = UUID.randomUUID().toString();\
commandGateway.send(new CreateToDoItemCommand(itemId, “Need to do this”));\
commandGateway.send(new MarkCompletedCommand(itemId));\
}
Implementare l’Event Listener
Eseguendo lo startup dell’applicazione, il Command Handling è “up and running” e quindi non resta che implementare solo un Event Listener per poter fare operazioni in base agli eventi da esso prodotti:
public class ToDoEventHandler {\
\
@EventHandler\
public void handle(ToDoItemCreatedEvent event) {\
System.out.println(“We’ve got something to do: “ +\
event.getDescription() + “ (“ + event.getTodoId() + “)”);\
}\
\
}
L’annotation @EventHandler fa capire ad Axon che il metodo è un Event Handler. Il parametro in ingresso al metodo definisce il tipo di evento di cui è in ascolto. Il wire di tale Event Handler all’infrastruttura applicativa va fatto aggiungendo la seguente riga
AnnotationEventListenerAdapter.subscribe(new ToDoEventHandler(), eventBus);
nel codice di configurazione, prima che i comandi vengano inviati al gateway. Oppure, nel caso di applicazione Spring based, aggiungendo le seguenti due righe al context file:
\<axon:annotation-config /\>\ \<bean class=“org.axonframework.test.sample.ToDoEventHandler”/\>
La prima riga va aggiunta una sola volta, anche se si implementano e si registrano ulteriori Event Handlers.
Conclusioni
Il codice di esempio presentato in questo articolo è molto semplice, ma illustra tutti i passi necessari per implementare l’architettura di una applicazione Axon based. Infatti, a prescindere dalla complessità della business logic applicativa, la implementazione di nuovi Command, Event ed Event Listener è un processo puramente meccanico e ripetitivo.
Riferimenti
- \
- [1\] Sito ufficiale di Axon framework
- [<u>http://www.axonframework.org/</u>](http://www.axonframework.org/)
- \
- [2\] Guida alla migrazione da Axon 1.4 ad Axon 2.0
- [<u>http://www.axonframework.org/axon-2-migration-guide/</u>](http://www.axonframework.org/axon-2-migration-guide/)
- \
- [3\] Sito ufficiale di Spring framework
- [<u>http://www.springsource.org/spring-framework</u>](http://www.springsource.org/spring-framework)
