Kristian Barrett
Kristian Barrett

Reputation: 3752

Repository pattern and aggregate root pattern. How to make SaveChanges in repository?

We recently started using repository pattern and aggregate root pattern. This is all working super well when I am tracking the changes using EntityFramework, and I am able to call SaveChanges, when I am done with the work on my aggregate.

Now my problem is that I also want to use this pattern with my mongodb, but by nature, mongodb does not support change tracking. Rolling my own change tracking seems a bit of an overkill.

My problem is that I cannot do work on my entity in the associated aggregate, and then use the repository to save it back to mongodb. I can only do this if I use ReplaceOneAsync, which does not seem to be the best fit, since it is a chat I am creating and therefor might have several write operations from different clients.

I would like to have some kind of change tracking that will allow me to call SaveChanges on the repository with the specified aggregate.

Her is some pseudo-code that tries to explain what I want to do:

public abstract class BaseMongoRepository<TAggregate, TCollection> : IRepository<TAggregate> where TAggregate : BaseAggregateRoot, IMongoAggregate<TCollection>
{
    private readonly IMongoCollection<TCollection> _mongoCollection;

    protected BaseMongoRepository(IMongoCollection<TCollection> mongoCollection)
    {
        _mongoCollection = mongoCollection;
    }

    public async Task<bool> SaveAsync(TAggregate aggregate)
    {
        var state = aggregate.GetState();
        foreach (var stateChange in state.Changes)
        {
            var change = stateChange.ToUpdateDefinition();
            await _mongoCollection.UpdateOneAsync<TCollection>(aggregate.GetSelector(), change);
        }

        return true;
    }

    public TAggregate GetMongoAggregate(Expression<Func<TCollection, bool>> selector)
    {
        var state = _mongoCollection.AsQueryable().SingleOrDefault(selector);
        return new AggregateWithSelector(state, selector);
    }
}

The GetMongoAggregate would be implemented in the specific versions of the repository, but is here for pseudo purpose.

Hopefully somebody can send me in the right direction or give me some advice on how to model this.

Upvotes: 1

Views: 1351

Answers (1)

R.Titov
R.Titov

Reputation: 3293

Here is a simple example of my implementation (if you need real example you can write me in private)

I have a MongoDB collection:

public class TestCollection
{
    public string Id { get; set; }
    public string Name { get; set; }
}

And I made following aggregate root for the collection:

public class TestCollectionAggregateRoot : IAgregateRoot, 
    IHasObserver<IMongoObserver<TestCollection>>,
    IHasSelector<IMongoSelector<TestCollection>>
{
    private readonly IMongoSelector<TestCollection> _selector;
    private readonly IMongoObserver<TestCollection> _observer;
    private string _name;

    public TestCollectionAggregateRoot(TestCollection root, IMongoSelector<TestCollection> selector, IMongoObserver<TestCollection> observer)
    {
        _selector = selector;
        _observer = observer;
        _name = root.Name;
    }

    public string Name
    {
        get { return _name; }
        set
        {
            if (_name != value)
            {
                _name = value;
                _observer.OnPropertyChanged(x=>x.Name, _name);
            }
        }
    }

    public IMongoObserver<TestCollection> Observer => _observer;
    public IMongoSelector<TestCollection> Selector => _selector;
}

where

public interface IMongoObserver<TCollection>
{
    void OnPropertyChanged<TField>(Expression<Func<TCollection, TField>> func, TField value);

    UpdateDefinition<TCollection> Definition { get; }
}

public interface IMongoSelector<TCollection>
{
    FilterDefinition<TCollection> Definition { get; }
}

Simple implementation for IMongoObserver:

public class MongoObserver<TCollection> : IMongoObserver<TCollection>
{
    private volatile UpdateDefinition<TCollection> _definition;

    public void OnPropertyChanged<TField>(Expression<Func<TCollection, TField>> func, TField value)
    {
        if (Definition != null)
        {
           _definition = Definition.Set(func, value);
        }
        else
        {
            _definition = Builders<TCollection>.Update.Set(func, value);
        }
    }
    public UpdateDefinition<TCollection> Definition => _definition;
}

Simple example Repository.SaveAsync (without InsertOneAsync)

public class Repository : IRepository<TestCollectionAggregateRoot>
{
    private readonly IMongoCollection<TestCollection> _mongoCollection;

    public Repository(IMongoCollection<TestCollection> mongoCollection)
    {
        _mongoCollection = mongoCollection;
    }

    public async Task<bool> SaveAsync(TestCollectionAggregateRoot aggregate)
    {
        var changes = aggregate.Observer.Definition;
        var selector = aggregate.Selector.Definition;

        await _mongoCollection.UpdateOneAsync(selector, changes);
        return true;
    }
}

Upvotes: 1

Related Questions