1

I have been studying DDD and have been experimenting with Event Sourcing as a storage mechanism.

Prior to this (mostly CRUD-style applications with anemic data models stored in relational DBs), a "best-practice" I have tried to keep in mind is to decouple my data model from any persistence concerns.

However, I have been struggling to wrap my head around how to do this with Event Sourcing. It seems that my domain model objects need to know about events and generate them themselves, and know how to reconstruct their state from a stream of events.

The only way around this I can think of is for my repositories to reconstruct domain objects from events, and then do some elaborate change detection to generate resulting events. This seems like it would be unnecessarily complicated and error-prone, as well as resulting in domain logic living in or being duplicated in the repository classes.

Or is my thinking about this all wrong and I should consider events as part of the domain model? This way, domain objects generating events and reconstructing state from events makes a lot more sense, and then storage & retrieval of events would be a persistence concern and be implemented outside of the domain layer.

0

However, I have been struggling to wrap my head around how to do this with Event Sourcing. It seems that my domain model objects need to know about events and generate them themselves

Yes, that's right.

So your use cases look something like functions that accept as arguments a command message and a current state, and return a list of events.

In addition, you need a function that can take a current state and a list of events and create from it a new state.

You can then take these functions, and arrange them in different ways depending on how you want to store your state when not in use.

For example, if you are expecting to store "current state", then you compose something like

currentState = repo.get()
events = useCase(currentState, commandMessage)
nextState = update(currentState, events)
repo.replace(currentState, nextState)

On the other hand, if you are using an event store, you might instead see

currentHistory = repo.get()
currentState = update(EmptyState, currentHistory)
events = useCase(currentState, commandMessage)
nextHistory = currentHistory.append(events)
repo.replace(currentHistory, nextHistory)

Separating "compute the next change" from "apply the next change" isn't familiar, because we tend to blur the lines between those two responsibilities. But if you refactor your "normal" domain objects carefully, you can distinguish the two concerns.

The only way around this I can think of is for my repositories to reconstruct domain objects from events, and then do some elaborate change detection to generate resulting events.

You can do that, sort of -- it's analogous to computing the difference between two documents and generating a patch. But figuring out the semantics of the patch is a difficult problem.

I should consider events as part of the domain model?

Absolutely. They are messages with domain semantics.

0

I agree with everything VoiceOfUnreason has to say, but wanted to comment on this line:

The only way around this I can think of is for my repositories to reconstruct domain objects from events, and then do some elaborate change detection to generate resulting events.

This elaborate change detection should not be necessary because your aggregate already knows what’s happening, and so knows what events need to be created. For example when the last name of a User aggregate changes your command would call the UpdatePersonalInfo method on the User aggregate and pass in the first name, last name, and birthdate. The method would validate the incoming data, then make the change to the aggregate. Since the User aggregate already knows what’s going on, it should already know what events need to be generated so there’s no “elaborate change detection” necessary.

You may be wondering what happens if the user only changes the last name and not the first name or birth date. That doesn’t matter. You still publish the same UserPersonalInfoUpdated event with all the personal info, even if only part of it changed. If a listener is concerned about which of the 3 fields actually changed, it handles that comparison itself so the “elaborate change detection” is only done if necessary.

I suspect you are imagining events that contain a list of aggregate fields that changed along with their new values. The trouble with this approach is that it can reduce the fidelity of the events generated. An event called UserUpdated with a list of changed fields is less intuitive than a UserPersonalInfoUpdated event that contains all the personal info whether it changed or not (especially if the User aggregate contains many fields). It forces listeners to subscribe to the generic UserUpdated event and parse the changed fields looking for interesting changes, rather than subscribing to specific events of interest.

Obviously this will increase the number of events your service must listen to, but with intelligent event naming scheme and wildcard event name filters, you can get general and fine grained notifications for all your aggregates.

  • Thanks for the feedback! "...is for my repositories to reconstruct domain objects from events, and then do some elaborate change detection to generate resulting events." - This comment was specifically in regards to the attempt to remove events from the domain model, keeping them as part of the persistence layer only. Then the repo would need to do some change detection to determine what events to generate. If the event store events are to be a part of the domain model itself, then this concern would be rendered moot as the domain model would generate them as needed. – Entith Jul 10 '18 at 2:07
  • My overall question is more so to do with whether or not the domain model should be aware of and contain "event store" events, as I can see those events being a persistence concern rather than a domain concern. Alternatively, considering "event store" events as part of the domain model also makes some sense to me, with the storage and retrieval of those events being offloaded to some persistence layer. – Entith Jul 10 '18 at 2:11
  • I don't consider the events as part of the domain model. They affect it but are not part of it. I'm assuming you're keeping events in something like Kafka or Kinesis so there is no need to "persist" events since event creation and persistence are the same thing. Possibly you're tracking your events in a table and reading from that table. In that case, I would consider the events themselves as part of the domain model for the Event Store System, not for the application mentioned. – Brad Irby Jul 10 '18 at 2:20
  • Sorry, i misspoke above and SO won't let me change it. In the second sentence said "they affect it but are not part of it" - that should be "they reflect changes made to it, but are not part of it" – Brad Irby Jul 10 '18 at 2:25
  • If events aren't to be considered a part of the domain model, is it appropriate for them to be a part of the domain layer? Wouldn't they then belong in the infrastructure layer, in which case domain objects shouldn't know about them (implementation details leaking out of the infrastructure layer)? I feel like I am missing something key here. – Entith Jul 10 '18 at 2:57
0

Domain Events have been identified as first class citizens in the domain layer since early in DDD history, and events as described by the Event Sourcing approach, while they might differ slightly from other definitions, are no exception.

Exactly where you iterate over the stream of events to rebuild the aggregate varies from implementation to implementation. Some put that logic in an Aggregate base class and others in Application Service/Command Handler code. Sometimes Repositories only know about streams of events, but sometimes they deal with Aggregate Root types, so you could probably put that loop (or fold) in there too.

The aggregate mutation code based on each specific Event, however, always resides in the Aggregate itself because it is domain logic. You'll find it under different method names such as apply or evolve. You can't put that in a Repository because domain logic would be leaking into the infrastructure layer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.