DEV Community

Cover image for Developer-friendly event sourcing
Bertil Muth
Bertil Muth

Posted on • Updated on

Developer-friendly event sourcing

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,):

As a summary, a CreateGreeting command with person name Jill is consumed by the Greeting entity that handles the command and produces the GreetingCreated event with salutation hello and person name Jill

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:

As a summary, a GreetingCreated event with salutation hello and person name Jill is consumed by the Greeting entity that handles the event and produces a new GreetingState instance with salutation hello and person name Jill

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 ChangeSalutationcommand. If you encode the id of Jill's Greeting entity in the request URL, the command handling looks like this:

A ChangeSalutation command with salutation Howdy is consumed by the Greeting entity that handles the command, and produces the SalutationChanged event with salutation Howdy

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 SalutationChanged event with salutation Howdy is consumed by the Greeting entity that handles the event and produces a new GreetingState instance with salutation Howdy and person name Jill as output

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))
        );
    }
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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 + "]";
    }
}
Enter fullscreen mode Exit fullscreen mode

Commands of an entity implement a common interface, like GreetingCommand in the example, which may be empty:

public interface GreetingCommand {
}
Enter fullscreen mode Exit fullscreen mode

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 + "]";
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

Discussion (2)

Collapse
gklijs profile image
Gerard Klijs

Did you look at other existing Java libraries that do the same? I'm a bit familiar with the Axon Framework, and being seems very similar to it.

Collapse
bertilmuth profile image
Bertil Muth Author • Edited on

Good point: why use Being, which is new, instead of an industry-proven framework?

Being is a library, not a framework. It's purpose and scope is not messaging infrastructure, but developer experience. So it doesn't reinvent the weel, but builds on preexisting, proven infrastructure (Vlingo).

Axon is a good example, since it's quite developer-friendly as well. But it also ties you to a lot of framework details. And it comes with a learning curve if you want to get started.
(I'm not talking about the essential complexity of event sourcing, but the accidental, framework specific complexity.)

Here are a few quotes from the official documentation:

The @AggregateIdentifier is the external reference point to into the GiftCard Aggregate. This field is a hard requirement. [...]

Note that the Aggregate Identifier must be set in the @EventSourcingHandler of the very first Event published by the aggregate. [...]

The property on the payload that will be used to find the entity that the message should be routed to, defaults to the name of the @EntityId annotated field. For example, when annotating the field transactionId, the command must define a property with that same name, which means either a transactionId or a getTransactionId() method must be present.

And so on. I know I have taken these texts out of context, so they'll naturally be harder to understand. My point is: you have to learn and comply to all of these framework specific, non-obvious rules. This will make it harder to get started, and they'll likely be different from framework to framework.

If you look further in the documentation, you see that the authors even promote using event handlers for aggregate internal classes - which not only tightly couples them to the Axon framework, but also breaks with the DDD idea that the Aggregate Root can enforce consistency boundaries.

In essence, event sourcing is just two functions:
(command, state) -> event(s)
(event, state) -> new state

Using Being, you can define these as Java functions. You can test the state change as plain unit tests, without going through any infrastructure.

So - Being is like a developer-friendly facade for messaging infrastructure. Like SL4J is for Log4j. Today, it only supports Vlingo. Tomorrow, it may well be that it will support Axon as well, as an alternative.

The reason I chose Vlingo is that it is internally based on the Actor Model, which avoids some nasty concurrency problems. (I would have to look at Axon in more detail to see how this is dealt with there.)