1
votes

This question is largely based on the article NHibernate – Automatic change tracking for aggregate roots in DDD scenarios

Although the logic in the article seems sound I have yet to find an implementation solution that will cover all use cases. The problem seems to be related to the following paragraph from the article

There is a slight problem here that we may generate several updates per transaction here, but I am not worried about that overly much, it is fairly simple to resolve (by keeping track of the entity and not updating if we already updated in the current transaction), so I’ll leave it up to you.

Following the article we simply force a version update whenever we update a related entity within the aggregate root. However in cases where both aggregate root and related entity are 'dirty' this will cause a double update on the aggregate root. This causes nhibernate to fall over as the second version update triggered by default from the dirty aggregate root expects the version to be the same as what was loaded from the db.

I've attempted to put a check into the 'PreInsertEventListener' and 'PreUpdateEventListener' checking if the aggregate root is dirty when updating a related entity. If this is the case then ignore the forced update of version.

public bool OnPreUpdate(PreUpdateEvent updateEvent)
{
    var rootFinder = updateEvent.Entity as ICanFindMyAggregateRoot;
    if (rootFinder == null)
        return false;

    if (!updateEvent.Session.IsAggregateRootDirty(rootFinder.MyRoot))
    {
        updateEvent.Session.Lock(rootFinder.MyRoot, LockMode.Force);
    }

    return false;
}


public static class SessionExtensions
{
        public static bool IsAggregateRootDirty(this ISession session, IAggregateRoot entity)
        {   
            ISessionImplementor sessionImplementation = session.GetSessionImplementation();
            IPersistenceContext persistenceContext = sessionImplementation.PersistenceContext;
            IEntityPersister entityPersister = sessionImplementation.GetEntityPersister(null, entity);

            EntityEntry entityEntry = persistenceContext.GetEntry(entity);

            if ((entityEntry == null) && (entity is INHibernateProxy))
            {
                INHibernateProxy proxy = entity as INHibernateProxy;
                object obj = sessionImplementation.PersistenceContext.Unproxy(proxy);
                entityEntry = sessionImplementation.PersistenceContext.GetEntry(obj);
            }

            object[] oldState = entityEntry.LoadedState;
            object[] currentState = entityPersister.GetPropertyValues(entity, sessionImplementation.EntityMode);

            int[] findDirty = entityEntry.Persister.FindDirty(currentState, oldState, entity, sessionImplementation);
            var hasDirtyCollection = currentState.OfType<IPersistentCollection>().Any(x => x.IsDirty);

            return (findDirty != null) || hasDirtyCollection;
        }
}

This solution does seem to work albeit I still need to test it with few more use cases. However I feel as if this solution is a bit heavy handed and was hoping for a solution more along what was outlined in the article. Is there a way to detect weather the version has already been updated in the same transaction or will be, or a simple way to keep track of entities within the transaction set have its version updated.

Thanks.

1
Note that you do not always want to bump the root's version when children aggregates are modified. Imagine you have a business rule stating that there should not be more than 10 comments on a post. Adding/removing comments should bump the root's version, but modifying one shouldn't. - plalx
The point is that I do want the aggregate root to roll its version when a child entity is updated. That is the point of the article that the root entity maintains the version of the entire aggregate root. Assuming there can be no more than 10 comments on a post would be restricted in the domain itself as comments in this scenario would only ever be added through the root. - Drauka
What I am saying is that you will harm concurrency if you bump to root version all the time, even when not required. In the above example, two users should be able to modify comments at the same time without facing optimistic concurrency exceptions. Therefore, the root version should not be incremented when a comment is modified. However, it should when one is added. The mechanism that you implement will have to be configurable so that you can specify what actions should increment the root version or your model will never be scalable. - plalx
If there was a requirement that users should be able to modify comments independently then in this case comments would have to form their own aggregate root. But that is not the requirement. Also do note that the post - comment domain is 'made up' for the occasion. - Drauka
how would you then enforce the maximum comments rule on posts. You would have to introduce a collection of value objects on Post just to maintain that rule and you would have to use eventual consistency to keep the Comment aggregate in sync. The fact that your solution must bumps the root's version in all scenarios forces you to model your domain differently. It might be worth the cost however, but it's a valid concern in my mind. - plalx

1 Answers

-1
votes

You're looking for Optimistic Concurrency, I think. See http://ayende.com/blog/3946/nhibernate-mapping-concurrency.