How to have both: clean architecture and synchronization between domain models and persistence?

105 Views Asked by At

Imagine a *.sln with 4 projects:

  1. Project.Storage (business logic models and interfaces to implement by infrastructure layer)
  2. Project (main logic)
  3. Project.Storage.EntityFramework (persistence layer which implements Project.Storage)

In Project.Storage we have defined model as below:

public class Grant
{
    public string GrantId { get; set; }
    public string Subject { get; set; }
    public string ClientId { get; set; }
    public ICollection<AuthorizationDetail> AuthorizationDetails { get; set; } = new List<AuthorizationDetail>();
    public ICollection<string> Claims { get; set; } = new HashSet<string>();
    public DateTime GrantedAt { get; set; }
}

And simple interface for it:

public interface IGrantStore
{
    Task UpdateAsync(string grantId, Action<Grant> update, CancellationToken cancellationToken = default);
}

The interface is implemented as below in Project.Storage.EntityFramework:

public class GrantStore : IGrantStore
{
    private readonly GrantsDbContext _dbContext;

    public GrantStore(GrantsDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public async Task UpdateAsync(string grantId, Action<Grant> update, CancellationToken cancellationToken = default)
    {
        var entity = await _dbContext.Grants.FindAsync(new string[] { grantId }, cancellationToken);
        if (entity != null)
        {
            update(entity);
            await _dbContext.SaveChangesAsync(cancellationToken);
        }
    }
}

And the problem is with synchronization between logic and persistence. If we want to update the as domain model and in my database, then I have to modify properties of instance of domain model, and then call UpdateAsync from store to update it in database. I do the same thing two times.

public async Task SomeMethod(Grant grant, CancellationToken cancellactionToken = default)
{
    var newAuthorizationDetails = //something
    var newClaims = //something

    grant.AuthorizationDetails = newAuthorizationDetails;
    grant.Claims = newClaims;

    await _grantStore.UpdateAsync(grant.GrantId, update =>
    {
        grant.AuthorizationDetails = newAuthorizationDetails;
        grant.Claims = newClaims;
    }, cancellationToken);
}

How to avoid it while I use Clean Architecture? I don't think depending on Change Tracking provided by EF is good, because what if someone uses eg. Dapper without Change Tracking (or how EF could know when to use tracking or not)?

In simple words, how to update the domain model and make some magic that allow persistence layer to detect change and do appropriate query to database?

0

There are 0 best solutions below