Contents

Reactive Event Sourcing in Java, Part 6: Empty State

So far, our implementation of a Show aggregate started in the first part is very minimalistic. This is fine for learning purposes, but it's time to focus more on modeling.

Domain

For some aggregates, an initial state (also known as an empty state) is very natural and straightforward, e.g. empty order book (for a trading application). In reality, this is rarely the case. Usually, you need to create or initialize the state manually with the first command, e.g. CreateUser, StartPayment, CreateAccount, etc.

Let's refactor our existing code to create the Show aggregate with a given title and a maximum number of seats. In a real use case, this would be a very complex operation, but you will be able to grab the overall concept and adjust it to your case.

We start with the domain, which should be extended with the CreateShow command and the ShowCreated event.

record CreateShow(ShowId showId, String title, int maxSeats) implements ShowCommand {
}
///////
record ShowCreated(ShowId showId, Instant createdAt, InitialShow initialShow) implements ShowEvent {
}

The command is rather obvious, but the event is more interesting. Instead of using only information from the command, we are enriching the event with an InitialShow record that contains all the seats. The logic behind the seat generation might change in the future, e.g. for some seats, prices might be higher (right now it's a constant value). It's safer to add a collection of all the seats to the first event. This way, we will be able to process the old events without worrying about implementation changes. We could feel a temptation to add the Show aggregate to the event instead of InitialShow (at this point, they look the same). I don't recommend this solution since any further changes to the Show aggregate would need to be backward compatible. We need to be able to read the old events. With an explicit separation (via InitialShow), we can refactor our aggregate however we want. It is only a projection from events. This is a huge advantage in comparison to the classic approach to state persistence, where we are constrained with the model changes by the persistence mechanism. That's why with Event Sourcing, we should put more attention to the events modeling because this is the key for long-term elasticity and extensibility.

This time, we cannot use the show.process(...) method to handle the CreateShow command because there is no state yet. We can create a separate utility for that:

public class ShowCreator {

    public static Either<ShowCommandError, ShowCreated> create(CreateShow createShow, Clock clock) {
        //more domain validation here
        if (createShow.maxSeats() > 100) {
            return left(TOO_MANY_SEATS);
        } else {
            var initialShow = new InitialShow(createShow.showId(), createShow.title(), createSeats(INITIAL_PRICE, createShow.maxSeats()));
            var showCreated = new ShowCreated(createShow.showId(), clock.now(), initialShow);
            return right(showCreated);
        }
    }
}

The create method signature is very similar to the process method signature and we will use that fact later.

Since the first ShowCreated event should be the start of aggregate life, we can use it as a factory method parameter:

public static Show create(ShowCreated showCreated) {
    InitialShow initialShow = showCreated.initialShow();
    return new Show(initialShow.id(), initialShow.title(), initialShow.seats());
}

As you noticed, both state.process(...) and state.apply(...) methods, described as entry points to our domain in the first first part, are not very handy during the creation of the state, that's why we need to extend our entry points set and provide a separate path than for normal commands/events processing.

Another approach would be to switch from an object-oriented to a more functional domain design. Instead of state.process(command), we can refactor this to process(state, command) and apply(state, event). This way, we will end up with only 2 entry points (as before) but the state must be an optional parameter. Which strategy is better? Both are fine, for some aggregates, object-oriented code will look nicer, and for others, functional might be better.

Event sourced entity

Some changes are required also in our ShowEntity implementation. Remember that EventSourcedBehavior must be parameterized with an explicit state/aggregate type. First, let's think about how we could model the "empty" Show. There are several options:

  • type hierarchy,
  • using null as an empty Show representation,
  • wrapping the Show with an Optional/Option monadic container.

Type hierarchy

We could refactor our domain to something like this:

public interface Show {}

record ExistingShow(/*some fields*/) implements Show {}

record NotExistingShow(/*no fields*/) implements Show {}

It looks interesting, but from my experience, it will be quite painful to manage such a hierarchy since we need NotExisitngShow only once and after that we will never go back to this state again. Our code base will be full of casting and instanceof statements. I would consider the hierarchical approach when this is driven by domain modeling, e.g. many possible states, with different responsibilities and a different set of data. An empty state is more of a technical problem.

null state

Good, old-fashioned Java null. We all hate nulls because of the NullPointerException but sometimes they are just good enough. Let's verify what this could look like. The empty state is set to null explicitly.

@Override
public Show emptyState() {
    return null;
}

Our command handler builder is split into two sections:
when the state is null then process only CreateShowCommand or return a none/empty response.
otherwise, process all commands as before.

public CommandHandlerWithReply<ShowEntityCommand, ShowEvent, Show> commandHandler() {
    var builder = newCommandHandlerWithReplyBuilder();

    builder.forNullState()
        .onCommand(ShowEntityCommand.GetShow.class, this::returnEmptyState)
        .onCommand(ShowEntityCommand.ShowCommandEnvelope.class, this::handleShowCreation);

    builder.forStateType(Show.class)
        .onCommand(ShowEntityCommand.GetShow.class, this::returnState)
        .onCommand(ShowEntityCommand.ShowCommandEnvelope.class, this::handleShowCommand);

    return builder.build();
}

A similar situation happens in the eventHandler method. For a null state, create it - Show::create, and then continue with the Show.apply(...) method.

public EventHandler<Show, ShowEvent> eventHandler() {
    EventHandlerBuilder<Show, ShowEvent> builder = newEventHandlerBuilder();

    builder.forNullState()
        .onEvent(ShowCreated.class, Show::create);

    builder.forStateType(Show.class)
        .onAnyEvent(Show::apply);

    return builder.build();
}

The rest of the changes are not very significant. The ShowService exposes a new method for creating the show. Finding a show by id might return Option.none(), which should be handled in the HTTP controller. A full diff is available here [like].

Optional state

Wrapping the Show with Optional (Java) or Option (Vavr) monadic container is also a pretty elegant solution. This time, it will be your homework to refactor

EventSourcedBehaviorWithEnforcedReplies<ShowEntityCommand, ShowEvent, Show>

into

EventSourcedBehaviorWithEnforcedReplies<ShowEntityCommand, ShowEvent, Option<Show>>

Just follow the types and compiler errors and you should be able to implement ShowEntity without nulls. The eventHandler method could be refactored to something like this:

public EventHandler<Option<Show>, ShowEvent> eventHandler2() {
    EventHandlerBuilder<Option<Show>, ShowEvent> builder = newEventHandlerBuilder();

    builder.forState(Option::isEmpty)
        .onEvent(ShowCreated.class, event -> Option.of(Show.create(event)));

    builder.forState(Option::isDefined)
        .onAnyEvent((show, event) -> Option.of(show.get().apply(event)));

    return builder.build();
}

If you feel that this show.get() invocation is shameless, well... I agree with you :) Unfortunately, using monadic types in Java is not so pleasant as in e.g. Scala. In the Scala codebase, I would definitely go with an Option wrapper but in Java, it's a matter of readability and convenience. I prefer to be more pragmatic than dogmatic.

Summary

Handling an empty state might be confusing at the beginning of the Event Sourcing journey. I hope that this post will clarify a lot for you. You can experiment with the above solutions and choose the best one for your use case. I'm planning to introduce another aggregate in the future. I will probably use another strategy to model the empty state just to give you more examples.

However, the next part will be about something different. We will leave the write side of Event Sourcing and focus on the read side. We will talk about CQRS and Akka Persistent Query library for events streaming. Stay tuned, join our SoftwareMill Academy mailing list and analyze the part_6 tag.

Blog Comments powered by Disqus.