Help wrestling with Axon configuration sans Spring

Greetings,

I’m an Axon newbie using the framework to prototype an event sourcing solution.

I started by defining aggregates, commands, and event handlers for my domain objects which all went fairly smoothly and worked more or less as expected. I noted that when my prototype starts, events replay to rehydrate existing objects which is expected and desired based on how I understand the design pattern.

Now I’m moving on to define a saga which is interacting with some external components. My saga handles certain event types and then executes some actions which have side effects (i.e. not idempotent behavior). I think I understand that sagas are not expected to replay events when the application restarts for this reason. Initially, I found that the sagas did replay events on restart and determined that I needed to include a token store in my configuration to persist the position in the event stream that was last processed. Having done so, I now see that my saga doesn’t replay events on restart (good…mission accomplished), but neither does my event handler (hmmm…problem).

Getting the Axon configuration set up properly to accomplish the desired semantics is not for the faint of heart. Not sure where I’m going wrong, so I’d like to buy a vowel. Here’s what my configuration looks like now:

Configurer configurer = DefaultConfigurer.defaultConfiguration();
//
//	Set up the token store
//
MysqlDataSource datasource = new MysqlDataSource();
datasource.setPassword(***********);
datasource.setUser(**********);
datasource.setServerName(**********);
datasource.setDatabaseName("tokenstore");
JdbcTokenStore tokenStore = JdbcTokenStore.builder()
	.connectionProvider(new DataSourceConnectionProvider(datasource))
	.serializer(XStreamSerializer.defaultSerializer())
	.build();

tokenStore.createSchema(new GenericTokenTableFactory());
configurer.registerComponent(TokenStore.class, conf -> tokenStore);
//
//	Register Aggregates
//
configurer.configureAggregate(AggregateConfigurer.defaultConfiguration(AxonObjective.class)
		.configureRepository(c -> EventSourcingRepository.builder(AxonObjective.class)
		.eventStore(c.eventStore())
		.build())
	);
//
//	Register Event Handlers
//	NOTE: Order is significant. Event handlers are invoked in the order they are added
//
configurer.registerEventHandler(conf -> new ObjectiveEventHandler());
//
//	Register Sagas
//
configurer.eventProcessing().registerSaga(ObjectiveTaskScheduler.class);
//
//	Create configuration and start the framework
//
configuration = configurer.buildConfiguration();
configuration.start();
//
//	Grab important configuration components for easy access later
//
commandGateway = configuration.commandGateway();
eventStore = configuration.eventStore();

Hi John,

under normal circumstances, I would say that handlers should not be invoked at startup. So the behavior that you’re now seeing, with the TokenStore configured, is what I would expect.

There are two things we need to consider:

Firstly event sourcing is about how you persist and rebuild the state of your (command model) aggregates. When executing a command against and aggregate, you will need that Aggregate’s state to decide on the outcome of that command. With event sourcing, you (selectively) replay events from that Aggregate onto a fresh/empty instance. The advantage of this process is that your decisions are guaranteed to be in line with the events stored. The only way to change “state”, is by appending an event.

Secondly, event sourcing is extremely inefficient when it comes to asking questions about the application’s state. If you want to know “what’s the total balance of all my bank accounts”, traversing the event store is the last thing you’d want to do. That’s why we have Query Models, which are designed to answer these questions efficiently.

Now that last word, “efficiently” is very important. In the vast majority of cases, efficient means fast & easy. For that situation, you’ll probably want to “project” the event stream onto a persisted query model. Typically in a relational database.
However, in some environments, “efficient” means something else. Perhaps: “not using any disk space”. In those (more rare) scenarios, you can project the event stream onto an in-memory model. Either ad-hoc, or pro-actively.
In either case, you will want your “Token Store” to use the same persistence mechanism as the projection. When persisting it in a database, you will want to store it in the same schema, so you have a transactional update of both the projection and the token, which gives you exactly-once processing semantics.

In the Configuration API, you can use the EventProcessingConfigurer (obtained via configurer.eventProcessing() in your code sample) to tune the configuration of your Event Processors. To have a specific processor use a different TokenStore, you can use the EventProcessingConfigurer#registerTokenStore(processorName, tokenStoreBuilder) method.

Note that, by default, an Event Handler is assigned to a Tracking Processor with the name of the package of the Event Handler.

I hope this clarifies things.

Thanks for the response, and yes I understand your points.

The focus of my prototyping at the moment is on the scenario you describe where the projection is an in-memory representation reconstructed from the event stream (i.e. no persistence other than the event stream) . In our case, the projection is a graph. For the purposes of this prototype, I think its OK that the graph is populated from the event stream at startup. As you point out, this won’t scale efficiently, and I’ll be looking for more efficient ways to reconstruct the projection, but my plan is to approach efficiency incrementally. I understand that snapshots is one approach, but I haven’t plumbed the depths of that yet.

So I’m looking for some more specific guidance on how to configure the event handler for my projection so that it replays events in order to reconstitute state.

…or maybe I’m totally off the rails?

I understand what you’re trying to achieve now.
In your configuration, you set the default TokenStore to be a JDBC based TokenStore. Unless you specify an explicit TokenStore for a certain TrackingEventProcessor, it will use that default one.

In your case, you will want to use an In-Memory TokenStore, since your projection is also in-memory. You can configure that using the EventProcessingConfigurer#registerTokenStore(processorName, tokenStoreBuilder) method.

Hope this helps.