CQRS and Event Sourcing, when implemented together, fundamentally shift your application’s perspective from "what is the current state?" to "how did we get to the current state?"
Let’s see this in action. Imagine we’re building an e-commerce platform.
// Command Handler for adding an item to a cart
@CommandHandler
public void handle(AddItemToCartCommand command) {
// Aggregate lifecycle methods are invoked by Axon
// If the aggregate doesn't exist, Axon creates it.
// If it exists, Axon loads its state by replaying events.
CartAggregate cart = repository.load(command.getCartId());
cart.addItem(command.getProductId(), command.getQuantity());
// The aggregate's internal methods publish domain events.
// Axon then persists these events and dispatches them.
repository.save(cart);
}
// Event Handler for updating the read model (e.g., a denormalized database view)
@EventHandler
public void on(ItemAddedToCartEvent event) {
// This event handler updates a separate read model, optimized for querying.
// It doesn't affect the command side or the aggregate's state directly.
cartViewRepository.save(new CartView(
event.getCartId(),
event.getProductId(),
event.getQuantity(),
// ... other denormalized data
));
}
The core problem CQRS addresses is the impedance mismatch between the complexity of business logic (commands) and the efficiency of data retrieval (queries). Traditional CRUD systems often use the same data model for both, leading to compromises. Event Sourcing takes this further by making the immutable sequence of events the single source of truth, rather than the current state.
In Axon, the Aggregate is the cornerstone of the command side. It’s a Java object that represents a business entity (like a Cart or an Order) and is responsible for handling commands and producing domain events. When a command arrives, Axon finds the relevant aggregate, loads its state by replaying past events (from the EventStore), executes the command’s logic (which might involve validating business rules and generating new events), and then persists the new events.
The EventStore is the heart of Event Sourcing. It’s an append-only log of every significant change that has ever happened in the system. Each event is immutable and represents a fact. For example, ItemAddedToCartEvent, OrderPlacedEvent, PaymentReceivedEvent. The current state of an aggregate is derived by replaying all events associated with it. This provides a complete audit trail and allows for time-travel debugging and sophisticated analytics.
The query side, often called the "read model," is completely decoupled. It’s populated by EventHandlers that subscribe to domain events published by aggregates. These handlers can transform events into data structures optimized for specific query needs. For instance, an ItemAddedToCartEvent might be used to update a denormalized CartView in a relational database, making it fast to retrieve the contents of a user’s cart without complex joins or computations. This separation means you can scale the command and query sides independently and use different data storage technologies for each.
A common misconception is that Event Sourcing means you can never update or delete data. While the events themselves are immutable, the representation of the current state derived from those events can be managed. For example, you can introduce a "cancel order" event. When replaying events to reconstruct the order state, this event would simply be processed like any other, potentially marking the order as cancelled. You don’t go back and delete or modify past events. If you need to "remove" something that was added, you typically publish a corresponding "item removed" event.
The real power emerges when you combine this with Axon’s capabilities for distributed systems. Axon Server, for example, acts as a highly available event store and message bus, managing event persistence, routing commands to the correct aggregate instances, and distributing events to interested handlers across multiple nodes. This allows you to build scalable, resilient applications where the event log is the central nervous system.
Understanding the distinction between command and event handlers, and how aggregates bridge the two by loading state from the event store and publishing new events, is crucial. The read models, updated asynchronously by event handlers, are what your user interfaces and reporting tools will typically query.
The next logical step is understanding how to handle complex sagas, which are long-running business processes that orchestrate multiple commands across different aggregates in response to events.