Skip to content

Building representations of aggregate state

Dominic Fox edited this page Mar 31, 2016 · 2 revisions

An aggregate’s state at a particular time is a function of all of the events in its event history up to that time. In order to obtain a representation of that state, we need to replay the relevant portion of the event history to an event handler that can build it. In Concursus, this is done by dispatching events to handler methods on a concrete class, which will first create an instance of that class using a static factory method, and then call instance methods on the resulting object to mutate its state*.

Given the PersonEvents interface defined earlier, here is a PersonState class that can receive events generated by an emitter using that interface:

@HandlesEventsFor(“person”)
public final class PersonState {
    @HandlesEvent
    public static PersonState created(UUID personId, String name, LocalDate dob) {
        return new PersonState(personId, name, dob);
    }

    private final UUID id;
    private String name;
    private final LocalDate dob;
    private final Set<UUID> groupIds = new HashSet<>();
    private boolean deleted = false;

    private PersonState(UUID id, String name, LocalDate dob) {
        this.id = id;
        this.name = name;
        this.dob = dob;
    }

    @HandlesEvent
    public void changedName(String newName) {
        name = newName;
    }

    @HandlesEvent
    public void addedToGroup(UUID groupId) {
        groupIds.add(groupId);
    }

    @HandlesEvent
    public void removedFromGroup(UUID groupId) {
        groupIds.remove(groupId);
    }

    @HandlesEvent
    public void deleted() {
        deleted = true;
    }

    // getters go here
}

Note that the PersonState class doesn’t actually implement PersonEvents, and that the method signatures don’t include the event timestamps or (with the exception of the factory method) the aggregate’s UUID. The mapping from emitter method to state handler method is by convention, keyed on method name (although this can be overridden in the @HandlesEvent annotation). This is more brittle than having the class implement the same interface as the one used to emit events, but it has the advantage that initial events can be dispatched to static factory methods (thereby creating the state on which subsequent events will operate), and we don’t need to pass in redundant method parameters.

It’s also worth noting that the PersonState class isn’t used to emit events at all. In a break with the “classic” Domain Driven Design pattern, we don’t process commands by obtaining an object representing the current state of the aggregate, then calling methods on it to update its state, then persisting those updates to the event log. Instead, it is quite common for a command processor to emit events by dispatching them straight to the event bus, without consulting the existing state of the aggregate in any way. Do we need to check whether a particular person exists before recording a “changedName” event against that person’s aggregate id? It is quite possible that the “created” event and the “changedName” event will arrive out-of-order; provided they both arrive, and we ensure that initial events are replayed before subsequent state-changing events when building any state representation, there is no problem with simply recording them as they come in.

* This is a departure from the general rule of preferring immutable data structures in Concursus, and there are two pragmatic reasons for it. The first is that representations of aggregate state are transient: the “source of truth” is the event history, from which the state can always be reconstructed. As temporary containers of information, state objects are relatively safe to mutate, provided that mutation is always done in the context of replaying an event history into them. The second is that a purely functional approach, in which each event was applied to a state object to produce a new state object representing the updated state, would run into difficulties when dealing with events that added an item to a collection or updated some value nested deeply in a data structure. Languages designed for pure functional programming have persistent data structures and constructs such as lenses to facilitate this; Java doesn’t (although there is support in libraries like PCollections and Javaslang), and it’s hard to implement this pattern in regular Java in a way that isn’t cumbersome, inefficient or both.