I've noticed a potential area for enhancement in our event application logic that could improve the accuracy and reliability of our domain model's state evolution. Specifically, I propose we adjust the order of operations in the Apply
method of our event handling to apply modification and inception recorders after the event has been applied to the aggregate. This change aims to ensure that recorders capture the most up-to-date state of the aggregate.
Current Implementation:
In our current implementation, the Apply
method in our event structure performs several operations in the following order:
- Increment the aggregate version.
- Append the event to the list of changes.
- Set the aggregate ID from the event state if applicable.
- Record inception and modification dates if the event state implements
InceptionRecorder
or ModificationRecorder
.
- Merge event metadata into aggregate metadata.
- Apply the state changes from the event to the aggregate.
- Clone the aggregate and set it on the event.
Here's the relevant code snippet for context:
func (e *Event[S]) Apply(aggregate *Aggregate[S]) {
// Initial setup and state capture...
if v, ok := e.State().(InceptionRecorder[S]); ok {
v.RecordInception(&e.occurredAt, aggregate)
}
if v, ok := e.State().(ModificationRecorder[S]); ok {
v.RecordModification(&e.occurredAt, aggregate)
}
// Applying state changes...
e.State().Apply(aggregate)
// Post-application steps...
}
Proposed Change:
I suggest we modify the sequence to apply the event to the aggregate before executing the inception and modification recorders. This adjustment ensures that any transformations to the aggregate's state, including additions of new value objects or other modifications, are fully reflected before we capture their inception or modification timestamps.
func (e *Event[S]) Apply(aggregate *Aggregate[S]) {
// Initial setup...
// First, apply state changes to ensure the latest state is captured
e.State().Apply(aggregate)
// Then, record inception and modifications with the updated state
if v, ok := e.State().(InceptionRecorder[S]); ok {
v.RecordInception(&e.occurredAt, aggregate)
}
if v, ok := e.State().(ModificationRecorder[S]); ok {
v.RecordModification(&e.occurredAt, aggregate)
}
// Final steps...
}
Benefits:
- Flexibility: It allows for a more nuanced handling of state changes, particularly for cases where the exact moment of state modification is critical (e.g., when an
occurredAt
date must be applied to a newly added value object from the aggregate).
I believe this adjustment will provide a more robust foundation for our event sourcing system, capturing state changes more accurately and flexibly. I welcome feedback on this proposal, including any concerns or additional benefits I may not have considered.