Here's what event sourcing is about: Every time you make a change to the application state, you record the change as an event.
You can replay the events since the beginning of the recording, up to a certain time. Then you've recreated the state of the application at that time. And by merging the events into a different data structure, you can provide a user specific view of the state (a "query model").
In short, event sourcing is about persisting events instead of just the current state. Event sourcing can be helpful for auditing purposes, and to analyze or rebuild previous system states for business analysis.
Think of a shopping cart: a typical e-commerce application would only store the state of the cart when the user proceeds to checkout. What if you want to know which shopping cart items have been removed by the user, to optimize the purchasing flow? That's when storing each event becomes helpful, e.g. ShoppingCartItemRemoved.
A Hello World example
In this example, a user sends a POST HTTP request with the data of a CreateGreeting command to the backend. This command contains the name of the person to greet. The backend transforms the command into a GreetingCreated event. This event contains the person's name from the command, and a default salutation (Hello,):
The event also contains the id of the entity you see in the middle: the Greeting
entity that consumes the commands, and produces the events. That way, the state of this entity can later be reconstructed.
By producing the event, the Greeting
entity has accepted the command as valid, and the event records this as a fact. The event is now stored in a journal, e.g. an in-memory, relational or NoSQL database.
So far, the state of the Greeting
entity hasn't changed yet.
To change the state, Greeting
takes the event and current state as input, and produces a new instance of the state class:
Objects of GreetingState
are immutable. Greeting
replaces the old state with the new state after applying the event.
What if you want to change the salutation for Jill's greeting later on? This can be done with a ChangeSalutation
command. If you encode the id of Jill's Greeting
entity in the request URL, the command handling looks like this:
Note that the event captures only the information that is relevant for the change about to happen. It doesn't need to capture all information in GreetingState
.
Applying the SalutationChanged
event looks like this:
The interesting thing is this: Greeting
takes the salutation from the event, and combines it with the personName
from its current state, to produce the new state.
The implementation problem
The problem I've seen in this. When building an event-sourced application, there is a steep learning curve. Not only do you need to get adjusted to this new way of thinking about state. You also need to learn the event sourcing library/framework details.
I want to change that. I created the Being library. It aims to cut down the technical complexity as far as possible. You can find it on Github. It's in an early stage of development, so I'm very thankful for Feedback.
Command and event handling code
When you use Being, you need to define the command handlers: which types of commands the entity consumes, and which event(s) it produces as a reaction to each command.
You also need to define the event handlers: for each of the event types, which new entity state to create as a reaction to it.
The behavior of the Greeting
entity shown below has the following code:
public class Greeting implements AggregateBehavior<GreetingCommand, GreetingState> {
@Override
public GreetingState initialState(final String id) {
return GreetingState.identifiedBy(id);
}
@Override
public CommandHandlers<GreetingCommand, GreetingState> commandHandlers() {
return CommandHandlers.handle(
commandsOf(CreateGreeting.class).with((cmd,state) -> new GreetingCreated(state.id, "Hello,", cmd.personName)),
commandsOf(ChangeSalutation.class).with((cmd, state) -> new SalutationChanged(state.id, cmd.salutation))
);
}
@Override
public EventHandlers<GreetingState> eventHandlers() {
return EventHandlers.handle(
eventsOf(GreetingCreated.class).with((event,state) -> new GreetingState(event.id, event.salutation, event.personName)),
eventsOf(SalutationChanged.class).with((event,state) -> new GreetingState(event.id, event.salutation, state.personName))
);
}
}
Apart from the initialState()
method that defines the starting state of Greeting
, this should look pretty familiar.
The first command handler consumes a CreateGreeting
command that contains the name of the person to greet, and produces a GreetingCreated
event.
But a user can also change the salutation via a ChangeSalutation
command. This command contains only the new text for the salutation, not the person's name. The person is identified by the entity's id, state.id
.
Both the command handlers and the event handlers can use the current state of the entity. So when a SalutationChanged
event is applied, the person name is not taken from the event, but from the current state of the entity: (event,state) -> new GreetingState(event.id, event.salutation, state.personName)
.
Code for the Greeting entity's state
Here's the code for the GreetingState
class that represents the state of the entity:
public final class GreetingState {
public final String id;
public final String salutation;
public final String personName;
public static GreetingState identifiedBy(final String id) {
return new GreetingState(id, "", "");
}
public GreetingState(final String id, final String salutation, final String personName) {
this.id = id;
this.salutation = salutation;
this.personName = personName;
}
@Override
public String toString() {
return "GreetingState [id=" + id + ", salutation=" + salutation + ", personName=" + personName + "]";
}
// hashCode() and equals() omitted for brevity
}
As you can see, objects of the state class are immutable.
Code for commands and events
Commands are simple POJOs, as you can see in the following example:
public class CreateGreeting implements GreetingCommand{
public final String personName;
public CreateGreeting(String personName) {
this.personName = personName;
}
@Override
public String toString() {
return "CreateGreeting [personName=" + personName + "]";
}
}
Commands of an entity implement a common interface, like GreetingCommand
in the example, which may be empty:
public interface GreetingCommand {
}
The reason for having a common interface for the commands is type safety. Use this command interface as the first type parameter of the entity class, as shown above.
Each event class must be a subclass of IdentifiedDomainEvent
:
public final class GreetingCreated extends IdentifiedDomainEvent {
public final String id;
public final String salutation;
public final String personName;
public GreetingCreated(final String id, final String salutation, String personName) {
super(SemanticVersion.from("1.0.0").toValue());
this.id = id;
this.salutation = salutation;
this.personName = personName;
}
@Override
public String identity() {
return id;
}
@Override
public String toString() {
return "GreetingCreated [id=" + id + ", salutation=" + salutation + ", personName=" + personName + "]";
}
}
Being is based on the powerful VLINGO XOOM platform that defines the IdentifiedDomainEvent
super class.
Conclusion
Apart from what I've shown above, you also need to define the HTTP request handlers. The Being website explains how to do that.
I want to invite you to have a look at it, if you find this topic interesting. And I'm very grateful for feedback.
Drop a note in the comments, visit the Gitter community or contact me on Twitter.