ayeo
ayeo

Reputation: 482

Mixing together DDD with Event Sourcing

I can't get my head around concept of mixing together DDD with ES. I consider events as being part of domain side. Given that there is no problem with publishing them from repository to outside world and keeping models pure and simple. But aside from that there must be possibility of replaying them back on particular aggregate. This is where my problem occurs. I would like to keep my domain models pure and simple objects that remain lib/framework agnostic.

To apply past events on aggregate the aggregate must be aware of being part of ES structure (wherefore it would not remain pure domain object). As main job of aggregate is to enfroce some bussines invariants that may evolve over time it is impossible to apply old events using aggregate API. For instance, there is aggregate Post with child entities Comments. Today Post allows 10 Comments to be added, and method addCommnet() guards that rule. But it is not used to be that way all time. One year ago user was allowed to add up to 20 Comments. So appying past events may not meet current rules.

Broadway (popular PHP CQRS library) works around the problem by applying events without any prevalidation. Method addCommnet() just checks it against our invariants and then processes appling events. Applyinig event itself does not do any further checking. That is greaat but I perceive that as high level of integration in my domain models. Does really my domain model need to know anything about infastructure (which is ES style of saving data)?

EDIT: To state the problem with the simplest words possible: is there any opportunity to get rid of all those applyXXX() methods from aggregate?

EDIT2: I have written (bit hacky) PoC of this idea with PHP - github

Upvotes: 4

Views: 1112

Answers (4)

Paweł Babilas
Paweł Babilas

Reputation: 35

I think that your problem is that you want to validate events when they are applied, but apply and validation are two different stages of aggregate action. When you are adding comment by method addComment(event), there is your logic to validate and this method is throwing event, when you reply event this logic is not checking again. Past event can not be changed, and if your aggregate throws exception with reply event something is wrong with your aggregate. That's how I understand your problem.

Upvotes: 0

Alexey Zimarev
Alexey Zimarev

Reputation: 19640

My answer is very short. Indeed, it is event-sourcing that you struggle with, not CQRS.

If handling of some event changes over time, you have two scenarios really:

  1. You are fixing a bug and your handler should really behave differently. In this case you just proceed with the change.
  2. You got some new intent. You actually have a new handling. This means that in fact this is a different event. In this case you have a new event and new handler.

These scenarios have no relation to programming languages and frameworks. Event-sourcing in general is much more about the business that about any tech.

I would second to Greg's book recommendation.

Upvotes: 0

VoiceOfUnreason
VoiceOfUnreason

Reputation: 57377

I can't get my head around concept of mixing together DDD with CQRS.

From the sound of things, you can't quite get your head around the mix of DDD and event sourcing. CQRS and Event Sourcing are separate ideas (that happen to go together well).

Today Post allows 10 Comments to be added, and method addCommnet() guards that rule. But it is not used to be that way all time. One year ago user was allowed to add up to 20 Comments. So appying past events may not meet current rules.

That's absolutely true. Notice, however, that it is also true that if you had a non event sourced post with 15 comments, and you try to make a "rule" now that only 10 comments are allowed, you still have a problem.

My answer to this puzzle (in both styles) is that you need a slightly different understanding of the responsibilities involved.

The responsibility of the domain model is behavior; it describes which states are reachable from the current state. The domain model shouldn't restrict you from being in a bad state, it should prevent good states from becoming bad states.

In version one, we might say that the state of a Post includes a TwentyList of Comments, where a TwentyList is (surprise) a container that can hold up to 20 comment identifiers.

In version two, where we want to maintain a limit of 10 comments, we don't change the TwentyList to a TenList, because that gives us backward compatibility headaches. Instead, we change the domain rule to say "no comments may be added to a post with 10 or more comments". The data schema is unchanged, and the undesirable states are still representable, but the allowed state transitions are greatly restricted.

Ironically enough, a good book to read to get more insights is Greg Young's Versioning in an Event Sourced System. The lesson, at a high level, is that event versioning is just message versioning, and state is just a message that a previous model left behind for the current model.

Value types aren't about rule constraints, they are about semantic constraints.

Keep in mind that the timelines are very different; behaviors are about the now and next, but states are about the past. States are supposed to endure much longer than behaviors (with the corresponding investment in design capital that implies).

Does really my domain model need to know anything about infrastructure (which is ES style of saving data)?

No, the domain model should not need to know about infrastructure.

But events aren't infrastructure -- they are state. A journal of AddComment and RemoveComment events is state just like a list of Comment entries is state.

The most general form of "behavior" is a function that takes current state as its input and emits events as its output

List<Event> act(State currentState);

as we can always at an outer layer, take the events (which are a non destructive representation of the state, and build the state from them.

State act(State currentState) {
    List<Event> changes = act(currentState)
    State nextState = currentState.apply(changes)
    return nextState
}

List<Event> act(List<Event> history) {
    State initialState = new State();
    State currentState = initialState.apply(changes)
    return act(currentState)
}

State act(List<Event> history) {
    // Writing this out long hand to drive home the point
    // we could of course call act: List<Event> -> State
    // to avoid duplication.

    List<Event> changes = act(history)
    State initialState = new State()
    State currentState = initialState.apply(history)

    State nextState = currentState.apply(changes)

    return nextState;
}

The point being that you can implement the behavior in the most general case, add a few adapters, and then let the plumbing choose which implementation is most appropriate.

Again, separation of responsibilities is your guiding star: state that manages what is, behavior that manages what changes are allowed, and plumbing/infrastructure are all distinct concerns.

In the simplest terms: I'm looking for opportunity to get rid of many applyXXX() (or similar in languages with overloading methods) methods from my aggregate

applyXXX is just a function, that accepts a State and an Event as arguments and returns a new State. You can use any spelling and scoping you want for it.

Upvotes: 2

Constantin Galbenu
Constantin Galbenu

Reputation: 17703

Disclaimer: I'm a CQRS framework guy.

Broadway (popular PHP CQRS library) works around the problem by applying events without any prevalidation. 

That's the way every CQRS Aggregate works, events are not checked because they express facts that already happened in the past. This means that applying an event doesn't throw exceptions, ever.

To apply past events on aggregate the aggregate must be aware of being part of ES structure (wherefore it would not remain pure domain object)

No, it doesn't. It must be aware of its past events. That is good.

Today Post allows 10 Comments to be added, and method addCommnet() guards that rule. But it is not used to be that way all time. One year ago user was allowed to add up to 20 Comments. So appying past events may not meet current rules.

What keeps you aggregate from ignoring that event or to interpret differently than 1 year ago?! This particular case should make you think about the power of CQRS: writes have a different logic than reads. You apply the events on the aggregate in order to validate the future commands that arrive at it (the write/command side). Displaying those 20 events is handled by other logic (the read/query side).

This is where my problem occurs. I would like to keep my domain models pure and simple objects that remain lib/framework agnostic.

CQRS make possible to keep your aggregates pure (no side effects), no dependency to any library and simple. I do this using the style presented by cqrs.nu, by yielding events. This means that aggregate command handlers methods are in fact generators.

The read models can also very very simple, plain PHP immutable objects. Only the read model updater has dependency to persistence, but that can be inversed using an interface.

Upvotes: 2

Related Questions