AggregateRoot version never updated
xdeclercq opened this issue · 9 comments
It loos like the AggregateRoot
Version
field is never updated. It should probably be updated at the end of the LoadsFromHistory
method (which should probably be renamed LoadFromHistory
), with the version of the last event replayed.
This should be incremented when applying any change to the AggregateRoot
, so from my point of view the method LoadFromHistory()
is not the right point for incrementing the version field. You should do this here instead:
private void ApplyChange(Event @event, bool isNew)
{
this.AsDynamic().Apply(@event);
if(isNew) _changes.Add(@event);
this.Version++
}
(Greg may hit me if I'm wrong)
That's correct, @beachwalker! You are not wrong :)
Indeed.
I'd say that the version should be updated at two different points:
- Aggregate is rehydrated, Version should reflect the current version number. This could be done in
LoadFromHistory
method. - When the changes are committed (
MarkChangesAsCommitted
method).
I wouldn't increase the version number directly after an event is applied and the aggregate is still in an uncommitted state, because then we'll lose the original version number and won't be able to do optimistic concurrency control. Consider the following scenario:
- Aggregate is rehydrated (
version = 5
) - Event is applied (
version = 5
,# uncommitted events = 1
) - Another event is applied (
version = 5
,# uncommitted events = 2
) - Aggregate is saved in repository (
expected version = 5
,new version = (5 + 2 =) 7
, we can do optimistic concurrency control here. If the expected version differs from the version currently in the repository, throw a ConcurrencyException) MarkChangesAsCommitted
is called on aggregate (version = 7
,# uncommitted events = 0
)- Rinse & repeat...
We do not lose the original version number at all.
- Aggregate is rehydrated (
version = 5
) - Event is applied (
version = 6
,#uncommittedEvents = 1
) - Another event is applied (
version = 7
,#uncommittedEvents = 2
) - Aggregate is saved in repository (
expectedVersion = 5
,newVersion = 7
. Concurrency control is simple:expectedVersion
(5
) sould be equal tonewVersion - #uncomittedEvents
`(7 - 2) = 5
True! That's a nice way to do the check.
I think I still prefer the Version to reflect the last 'committed' version instead of the 'new/dirty' version which may or may not become the next committed version, but you're right that it doesn't matter for concurrency checks.
Indeed. There are a lot of ways to do it right. For instance, in my own implementation of event store I do the concurrency check in another way