Mohd Ilyas
Mohd Ilyas

Reputation: 57

Clean architecture Database First Approach - Issues

I am implementing clean architecture using the existing database, with scaffolding command I have generated the POCO entities in the Infrastructure layer and as well as manually created the entities in the domain layer to map them later.

in the Application layer, I have the generic interface repository with a few standard operations.

public interface IRepository<T> where T : class
 {
     Task<IReadOnlyList<T>> GetAllAsync();
     Task<T> GetByIdAsync(int id);
     Task<T> AddAsync(T entity);
     Task UpdateAsync(T entity);
     Task DeleteAsync(T entity);
 }

As per the principles of Clean-Architecture, I am implementing it in the Infrastructure layer.

public class Repository<T> : IRepository<T> where T : class
    {

        protected readonly MyDBContext _MyDBContext;
        public Repository( MyDBContext mydbContext)
        {
            _MyDBContext= mydbContext;
        }
        public async Task<T> AddAsync(T entity)
        {
            await _MyDBContext.Set<T>().AddAsync(entity);
            await _MyDBContext.SaveChangesAsync();
            return entity;
        }

-----
----

I am using a Mediator pattern with CQRS, when I try to save the user from the API layer I will end up with the below exception.

System.InvalidOperationException: Cannot create a DbSet for 'ABC.Domain.Entities.User' because this type is not included in the model for the context. However, the model contains an entity type with the same name in a different namespace: 'ABC.Infrastructure.Models.User'.

It will be resolved if I can able to map the domain entity to the infrastructure entity in the above Repository implementation.

In the above implementation, the T is the ABC.Domain.Entities.User, not the ABC.Infrastructure.Models.User.

I can't pass the ABC.Infrastructure.Models.User from the Application layer ( because I can't add a reference to the Infrastructure layer in Application layer) due to the rule Clean Architecture all dependencies flow inwards and Core has no dependency on any other layer.

Please help me to map the incoming domain entity with the infrastructure entity in the above repository implementation so that I can use these general methods for other entity operations as well.

Check my skeleton repo.

https://gitlab.com/mail2mdilyas/workcontinent/-/blob/master/Work.Continent.Infrastructure/Repositories/Base/Repository.cs

In the above class, the "AddAsync" operation is in the generic repository (Repository.cs) and can be used for different insert operations with different domain entities in the future. And here I won't be knowing what is T :

public class Repository : IRepository where T : class

please advise me of the generic way to find and map the incoming domain entity with the data entity.

Upvotes: 1

Views: 2298

Answers (1)

msmolcic
msmolcic

Reputation: 6557

I'd say you have two different ways to handle this. You could either just map the domain model to the table structures and let your configuration files on the infrastructure layer do the mapping for you. That's the way I prefer to do it nowadays and it's an easy adjustment in your codebase. So, for your entity configuration of modelBuilder.Entity<Professional> - Professional class should be the one coming from your domain instead of infrastructure. Ardalis is also using this approach if you take a look at his sample again.

As an alternative, you could make the implementation of a repository on the infrastructure layer aware of two different models and do the mapping within the implementation of your repository itself. You'll have to pass two different generic types to the Repository<TDomainEntity, TDbModel> and declare it correctly during DI registration. However, the repository interface itself must accept domain entities only because you shouldn't leak database models outside of your infrastructure layer.

public class Repository<TDomainEntity, TDbModel>
    : IRepository<TDomainEntity>
    where TDomainEntity : class
    where TDbModel : class
{
    protected readonly MyDBContext _dbContext;
    protected readonly IMapper _mapper;
    
    public Repository(MyDBContext dbContext, IMapper mapper)
    {
        _dbContext= dbContext;
        _mapper = mapper;
    }
        
    public async Task<TDomainEntity> AddAsync(TDomainEntity entity)
    {
        TDbModel model = _mapper.Map<TDbModel>(entity);
        await _dbContext.Set<TDbModel>().AddAsync(model);
        await _dbContext.SaveChangesAsync();
        return entity;
    }
}

I'd say there are multiple cons to doing it like this. You have an unnecessary layer of mapping since you can manipulate the mapping of your domain entity to the database table through your model configuration. This also increases the number of changes you have to make if any new requirement comes up in your model. You'll also have to work on some reflection magic to match your domain entity and database model based on naming convention or something or do a bunch of registrations manually.

Upvotes: 1

Related Questions