DDD 并在应用层从基础设施层 class 实施合同

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

所以我与一位同事讨论了从 Base class 到接口实现契约的问题。

我们有以下 DDD 结构,Api -> 应用程序 -> 域 -> 基础设施。 在基础架构中,我们使用 EF Core。

以下代码示例

申请

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

基础设施

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);
    }
}

所以我主张,而不是在每个存储库中实现 AddNAMEAsync 方法来使 AddAsync public 虚拟 base class,并在相应的接口中使用 base class 实现。通过这种方式,我们仍然可以在需要时订购 AddAsync,并最大限度地减少存储库中不必要的“重复代码”。

另一方面,我的同事认为这会使名称过于通用,并且在调用存储库时,您不会仅通过阅读代码就知道要将哪个实体添加到上下文中。并且还争论说我们不应该在接口中公开基本 class 方法,而应该只公开 Parent -> Child 公开。

我们正在使用 SOLID 原则,每个处理程序只处理一个 entity/domain 聚合,我们在 variables/objects 上也有非常明确的命名,因此您可以轻松地看到要添加到上下文中的内容存储库名称以及域模型

您可以在 BaseRepository 中使用 AddAsync(T entity) 方法,这将为您的所有实体提供可重用代码,但这已由 Entity Framework Core 实现。你真的需要再次实现这个结构吗?

EF Core 已经提供 DbSet 作为 基本存储库 DbSet<Order> 作为您的 订单存储库 。还提供 DbContext 作为 工作单元 实施。

如果您不使用任何其他 ORM 工具或 EF Core 持久化方法,则无需创建通用基础存储库、存储库和工作单元 类。 EF Core 已经提供了这些封装的模式。

这样封装EF Core提供的类和方法会解决什么问题? EF Core 的 DbSet 和你的 BaseRepository 有什么区别?

查看此示例project

  • 我认为您在基础 class 中实现 AddAsync() 方法并从中继承其他 class 方法是正确的。因为,当你想使用AddAsync()时,你会先注入相关的class,然后再使用它。 (它将为您提供可重用的代码库)
public class MyClass 
{
   private readonly OrderService _orderService;
   
   //..
   await _orderService.AddAsync(...);
}

  • 这种用法在命名上是没有问题的,因为相关的服务和它要做什么都很清楚。

Btw, CreateAsync or InsertAsync could be a better naming in my opinion to demonstrate database operation.

在命名方面,从我的角度来看,您可以安全地使用“AddAsync()”。更重要的是,使用存储库的客户端使用 IOrderRepository 类型。在这种情况下,当查看 AddOrderAsync () Order 部分在命名方面可以看作是多余的,因为首先,你在一个已经表明一切都与订单有关的类型上调用它,其次,你正在传递Order 类型的对象,它再次指示您要添加的内容。

您仍然可以做的是明确表示泛型方法也是 IOrderRepository 可以扩展的泛型接口(例如 IRepository)的一部分。如果您想从基础 class 派生基础结构 OrderRepository,或者如果您想将其传递到某个其他对象中以支持组合而不是继承(例如 DbContext),则实施细节。

你可以看到我指的是什么的插图here in the microservices reference application of Microsoft (eshopOnContainers)。