Search code examples
c#architectureentity-framework-coredomain-driven-design

DDD and implementing contract in Application Layer from base class in Infrastructure Layer


So I had a discussion with a coworker on implementing contract from a Base class to an interface.

We have the following structure with DDD, Api -> Application -> Domain -> Infrastructure. In the infrastructure we use EF Core.

The following code example

Application

public interface IOrderRepository
{
    IUnitOfWork UnitOfWork { get; }
    Task AddOrderAsync(Order order);
}

Infrasctucture

public class OrderRepository : BaseRepository<Order, DbContext>, IOrderRepository
{
    public OrderRepository(DbContext ctx) : base(ctx) { }

    public async Task AddOrderAsync(Order order)
    {
        try
        {
            await AddAsync(order);
        }
        catch (Exception ex)
        {
            Log.Error($"Exception: {ex}");
            throw ex;
        }
    }

    /*
     * 
     * Some other db methods
     * 
     */
}

public abstract class BaseRepository<T, U> where T : class where U : BaseDbContext, IUnitOfWork
{
    protected readonly U _context;

    public IUnitOfWork UnitOfWork 
    { 
        get 
        { 
            return _context; 
        } 
    }

    public BaseRepository(U context)
    {
        _context = context;
    }

    protected virtual async Task AddAsync(T entity)
    {
        await _context.Set<T>().AddAsync(entity);
    }
}

So I am arguing for, instead of implementing AddNAMEAsync methods in every repository to make AddAsync public virtual in the base class, and in corresponding interfaces and making use of the base class implementation. This way we also still have the possibility to orderride AddAsync if needed and also minimize unneccesary "duplicate code" in repositories.

My coworker on the other hand thinks that this will make the name too generic and when calling the repository you will not know which entity you are adding to the context by just reading the code. And aslo arguing on that we should not expose base class methods in interfaces, but that it instead should only be Parent -> Child exposure.

We are using SOLID principles and each handler only handles one entity/domain aggregate, we also have very clear naming on our variables/objects so you can easily see what you are adding to the context in the repository name as well as in the domain model


Solution

  • In terms of naming you can, from my point-of-view, safely use "AddAsync()". The more important thing is that the client working with the repository uses a type of IOrderRepository. In this case, when looking at AddOrderAsync() the Order part could be seen as redundant in terms of naming because, first, you are calling it on a type that already indicates that everything is about orders, and second, you are passing an object of type Order which again indicates what you are going to add.

    What you could still do is to make explicit that the generic methods are also part of an generic interface (e.g. IRepository) which IOrderRepository could extend. If you want to derive your infrastructure OrderRepository from a base class or if you want to pass it in some other object in terms of favouring composition over inheritance (e.g. a DbContext) is an implementation detail.

    You can see an illustration of what I'm referring to here in the microservices reference application of Microsoft (eshopOnContainers).