If you're interested in functional programming, you might also want to checkout my second blog which i'm actively working on!!

Friday, January 10, 2014

AxonFramework - initial exploration

For the record, this demo is kind of copied from the quickstart but it is in some ways more elaborate and using the latest and greatest version 2.0.7
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.pelssers</groupId>
<artifactId>axondemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<spring.version>3.1.2.RELEASE</spring.version>
<axon.version>2.0.7</axon.version>
<junit.version>4.11</junit.version>
</properties>
<dependencies>
<dependency>
<groupId>org.axonframework</groupId>
<artifactId>axon-core</artifactId>
<version>${axon.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.axonframework</groupId>
<artifactId>axon-test</artifactId>
<version>${axon.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
view raw gistfile1.xml hosted with ❤ by GitHub
Now we start thinking about the behaviour of our little demo application. A user can create a new todo item and he can mark an existing item as completed.
package nl.pelssers.axondemo.api;
import org.axonframework.commandhandling.annotation.TargetAggregateIdentifier;
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;
}
}
view raw gistfile1.java hosted with ❤ by GitHub
package nl.pelssers.axondemo.api;
import org.axonframework.commandhandling.annotation.TargetAggregateIdentifier;
public class MarkCompletedCommand {
@TargetAggregateIdentifier
private String todoId;
public MarkCompletedCommand(String todoId) {
this.todoId = todoId;
}
public String getTodoId() {
return todoId;
}
}
view raw gistfile1.java hosted with ❤ by GitHub
When commands are issued, something is bound to happen, so we also need to think about what events occur.
package nl.pelssers.axondemo.api;
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;
}
}
view raw gistfile1.java hosted with ❤ by GitHub
package nl.pelssers.axondemo.api;
public class ToDoItemCompletedEvent {
private final String todoId;
public ToDoItemCompletedEvent(String todoId) {
this.todoId = todoId;
}
public String getTodoId() {
return todoId;
}
}
view raw gistfile1.java hosted with ❤ by GitHub
We also need to think about our aggregate, in this case our ToDoItem
package nl.pelssers.axondemo.api;
import org.axonframework.commandhandling.annotation.CommandHandler;
import org.axonframework.eventhandling.annotation.EventHandler;
import org.axonframework.eventsourcing.annotation.AbstractAnnotatedAggregateRoot;
import org.axonframework.eventsourcing.annotation.AggregateIdentifier;
public class ToDoItem extends AbstractAnnotatedAggregateRoot<ToDoItem>{
private static final long serialVersionUID = 5438310735257852739L;
@AggregateIdentifier
private String id;
public ToDoItem() {
}
/**
* This is a constructor annotated with a CommandHandler. It makes sense that a ToDoItem
* instance gets created whenever a CreateToDoItemCommand is issued. The command handler
* creates a ToDoItemCreatedEvent which gets registered with the EventContainer by calling apply
* @param command
*/
@CommandHandler
public ToDoItem(CreateToDoItemCommand command) {
apply(new ToDoItemCreatedEvent(command.getTodoId(), command.getDescription()));
}
@EventHandler
public void on(ToDoItemCreatedEvent event) {
this.id = event.getTodoId();
}
@CommandHandler
public void markCompleted(MarkCompletedCommand command) {
apply(new ToDoItemCompletedEvent(id));
}
}
view raw gistfile1.java hosted with ❤ by GitHub
Of course we need to test if events are properly created when a command is issued.
package nl.pelssers.axondemo;
import nl.pelssers.axondemo.api.CreateToDoItemCommand;
import nl.pelssers.axondemo.api.MarkCompletedCommand;
import nl.pelssers.axondemo.api.ToDoItem;
import nl.pelssers.axondemo.api.ToDoItemCompletedEvent;
import nl.pelssers.axondemo.api.ToDoItemCreatedEvent;
import org.axonframework.test.FixtureConfiguration;
import org.axonframework.test.Fixtures;
import org.junit.Before;
import org.junit.Test;
public class ToDoItemTest {
private FixtureConfiguration<ToDoItem> fixture;
private final static String ITEM1_ID = "TODO-1";
private final static String ITEM1_DESCRIPTION = "Clean house";
@Before
public void setUp() throws Exception {
fixture = Fixtures.newGivenWhenThenFixture(ToDoItem.class);
}
@Test
public void testCreateToDoItem() throws Exception {
fixture
.given()
.when(new CreateToDoItemCommand(ITEM1_ID, ITEM1_DESCRIPTION))
.expectEvents(new ToDoItemCreatedEvent(ITEM1_ID, ITEM1_DESCRIPTION));
}
@Test
public void testMarkToDoItemAsCompleted() throws Exception {
fixture
.given(new ToDoItemCreatedEvent(ITEM1_ID, ITEM1_DESCRIPTION))
.when(new MarkCompletedCommand(ITEM1_ID))
.expectEvents(new ToDoItemCompletedEvent(ITEM1_ID));
}
}
view raw gistfile1.java hosted with ❤ by GitHub
We also need to configure the application, as usual we will use Spring to wire our app.
package nl.pelssers.axondemo.configuration;
import java.io.File;
import nl.pelssers.axondemo.ToDoEventHandler;
import nl.pelssers.axondemo.api.ToDoItem;
import org.axonframework.commandhandling.CommandBus;
import org.axonframework.commandhandling.SimpleCommandBus;
import org.axonframework.commandhandling.annotation.AggregateAnnotationCommandHandler;
import org.axonframework.commandhandling.gateway.CommandGateway;
import org.axonframework.commandhandling.gateway.DefaultCommandGateway;
import org.axonframework.eventhandling.EventBus;
import org.axonframework.eventhandling.SimpleEventBus;
import org.axonframework.eventhandling.annotation.AnnotationEventListenerAdapter;
import org.axonframework.eventsourcing.EventSourcingRepository;
import org.axonframework.eventstore.EventStore;
import org.axonframework.eventstore.fs.FileSystemEventStore;
import org.axonframework.eventstore.fs.SimpleEventFileResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AxonDemoConfiguration {
@Bean
public CommandBus commandBus() {
return new SimpleCommandBus();
}
@Bean
public CommandGateway commandGateway() {
return new DefaultCommandGateway(commandBus());
}
@Bean
public EventStore eventStore() {
return new FileSystemEventStore(new SimpleEventFileResolver(new File("c:/tmp/axondemo")));
}
@Bean
public EventBus eventBus() {
return new SimpleEventBus();
}
@Bean
public EventSourcingRepository<ToDoItem> todoRepository() {
EventSourcingRepository<ToDoItem> repository = new EventSourcingRepository<ToDoItem>(ToDoItem.class, eventStore());
repository.setEventBus(eventBus());
return repository;
}
@SuppressWarnings("unchecked")
@Bean
public AggregateAnnotationCommandHandler<ToDoItem> toDoItemHandler() {
return AggregateAnnotationCommandHandler.subscribe(ToDoItem.class, todoRepository(), commandBus());
}
@Bean
public AnnotationEventListenerAdapter todoEventHandler() {
return AnnotationEventListenerAdapter.subscribe(new ToDoEventHandler(), eventBus());
}
}
view raw gistfile1.java hosted with ❤ by GitHub
The demo app just issues 2 commands, mimicking what a user might do. And to make it interesting we also create an event handler for our ToDoItem events.
package nl.pelssers.axondemo;
import nl.pelssers.axondemo.api.CreateToDoItemCommand;
import nl.pelssers.axondemo.api.MarkCompletedCommand;
import nl.pelssers.axondemo.configuration.AxonDemoConfiguration;
import org.axonframework.commandhandling.gateway.CommandGateway;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.stereotype.Component;
@Component
public class ToDoApplication {
private CommandGateway commandGateway;
public ToDoApplication(CommandGateway commandGateway) {
this.commandGateway = commandGateway;
}
/**
* @param args
*/
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AxonDemoConfiguration.class);
ToDoApplication todoApplication = new ToDoApplication(context.getBean(CommandGateway.class));
todoApplication.run();
}
private void run() {
final String itemId = "TODO-1";
commandGateway.send(new CreateToDoItemCommand(itemId, "Clean house"));
commandGateway.send(new MarkCompletedCommand(itemId));
}
}
view raw gistfile1.java hosted with ❤ by GitHub
package nl.pelssers.axondemo;
import org.axonframework.eventhandling.annotation.EventHandler;
import nl.pelssers.axondemo.api.ToDoItemCompletedEvent;
import nl.pelssers.axondemo.api.ToDoItemCreatedEvent;
public class ToDoEventHandler {
@EventHandler
public void handle(ToDoItemCreatedEvent event) {
System.out.println("We've got something to do: " + event.getDescription() + " (" + event.getTodoId() + ")");
}
@EventHandler
public void handle(ToDoItemCompletedEvent event) {
System.out.println("We've completed a task: " + event.getTodoId());
}
}
view raw gistfile1.java hosted with ❤ by GitHub
When we run the ToDoApplication we notice a file gets created in c:/tmp/axondemo/ for our aggregate called TODO-1.events. Also we nicely see some output appearing in the console:
We've got something to do: Clean house (TODO-1)
We've completed a task: TODO-1

No comments:

Post a Comment