【问题标题】:Repository OO Design - Multiple Specifications存储库 OO 设计 - 多种规范
【发布时间】:2015-02-15 15:23:20
【问题描述】:

我有一个非常标准的存储库界面:

public interface IRepository<TDomainEntity>
    where TDomainEntity : DomainEntity, IAggregateRoot
{
    TDomainEntity Find(Guid id);
    void Add(TDomainEntity entity);
    void Update(TDomainEntity entity);
}

我们可以使用各种基础设施实现来提供默认功能(例如实体框架、DocumentDb、表存储等)。这就是实体框架实现的样子(为了简单起见,没有任何实际的 EF 代码):

public abstract class EntityFrameworkRepository<TDomainEntity, TDataEntity> : IRepository<TDomainEntity>
    where TDomainEntity : DomainEntity, IAggregateRoot
    where TDataEntity : class, IDataEntity
{
    protected IEntityMapper<TDomainEntity, TDataEntity> EntityMapper { get; private set; }

    public TDomainEntity Find(Guid id)
    {
        // Find, map and return entity using Entity Framework
    }

    public void Add(TDomainEntity item)
    {
        var entity = EntityMapper.CreateFrom(item);
        // Insert entity using Entity Framework
    }

    public void Update(TDomainEntity item)
    {
        var entity = EntityMapper.CreateFrom(item);
        // Update entity using Entity Framework
    }
}

TDomainEntity 域实体(聚合)和TDataEntity Entity Framework 数据实体(数据库表)之间存在映射。我不会详细说明为什么会有单独的域和数据实体。这是领域驱动设计的哲学(阅读聚合)。这里要理解的重要一点是存储库只会公开域实体。

要为“用户”创建一个新的存储库,我可以这样定义接口:

public interface IUserRepository : IRepository<User>
{
    // I can add more methods over and above those in IRepository
}

然后使用实体框架实现为聚合提供基本的FindAddUpdate 功能:

public class UserRepository : EntityFrameworkRepository<Stop, StopEntity>, IUserRepository
{
    // I can implement more methods over and above those in IUserRepository
}

上述解决方案效果很好。但是现在我们要实现删除功能。我提出了以下接口(这是一个IRepository):

public interface IDeleteableRepository<TDomainEntity>
    : IRepository<TDomainEntity>
{
    void Delete(TDomainEntity item);
}

Entity Framework 实现类现在看起来像这样:

public abstract class EntityFrameworkRepository<TDomainEntity, TDataEntity> : IDeleteableRepository<TDomainEntity>
    where TDomainEntity : DomainEntity, IAggregateRoot
    where TDataEntity : class, IDataEntity, IDeleteableDataEntity
{
    protected IEntityMapper<TDomainEntity, TDataEntity> EntityMapper { get; private set; }

    // Find(), Add() and Update() ...

    public void Delete(TDomainEntity item)
    {
        var entity = EntityMapper.CreateFrom(item);

        entity.IsDeleted = true;
        entity.DeletedDate = DateTime.UtcNow;

        // Update entity using Entity Framework
        // ...
    }
}

根据上面类中的定义,TDataEntity 泛型现在还需要是 IDeleteableDataEntity 类型,这需要以下属性:

public interface IDeleteableDataEntity
{
    bool IsDeleted { get; set; }
    DateTime DeletedDate { get; set; }
}

这些属性在Delete() 实现中进行了相应设置。

这意味着,如果需要,我可以定义 IUserRepository 具有“删除”功能,这将由相关实现固有地处理:

public interface IUserRepository : IDeleteableRepository<User>
{
}

如果相关的 Entity Framework 数据实体是 IDeleteableDataEntity,这将不是问题。

这个设计的好处是我可以开始进一步细化存储库模型(IUpdateableRepositoryIFindableRepositoryIDeleteableRepositoryIInsertableRepository)并且聚合存储库现在可以根据我们的仅公开相关功能规范(也许您应该被允许插入UserRepository,但不能插入ClientRepository)。除此之外,它还指定了完成某些存储库操作的标准化方式(即,IsDeletedDeletedDate 列的更新将是通用的,并且不在开发人员手中)。

问题

当我想为一些没有删除功能的聚合创建存储库时,会出现上述设计的问题,例如:

public interface IClientRepository : IRepository<Client>
{
}

EntityFrameworkRepository 实现仍要求 TDataEntity 的类型为 IDeleteableDataEntity

我可以确保客户端数据实体模型确实实现了IDeleteableDataEntity,但这是误导和不正确的。将会有其他字段永远不会更新。

我能想到的唯一解决方案是从TDataEntity 中删除IDeleteableDataEntity 泛型条件,然后在Delete() 方法中转换为相关类型:

public abstract class EntityFrameworkRepository<TDomainEntity, TDataEntity> : IDeleteableRepository<TDomainEntity>
    where TDomainEntity : DomainEntity, IAggregateRoot
    where TDataEntity : class, IDataEntity
{
    protected IEntityMapper<TDomainEntity, TDataEntity> EntityMapper { get; private set; }

    // Find() and Update() ...

    public void Delete(TDomainEntity item)
    {
        var entity = EntityMapper.CreateFrom(item);

        var deleteableEntity = entity as IDeleteableEntity;

        if(deleteableEntity != null)
        {
            deleteableEntity.IsDeleted = true;
            deleteableEntity.DeletedDate = DateTime.UtcNow;
            entity = deleteableEntity;
        }

        // Update entity using Entity Framework
        // ...
    }
}

因为ClientRepository没有实现IDeleteableRepository,所以不会暴露Delete()方法,这很好。

问题

谁能建议一个更好的架构,它利用 C# 类型系统并且不涉及 hacky cast?

有趣的是,如果 C# 支持多重继承(使用单独的具体实现来查找、添加、删除、更新),我可以这样做。

【问题讨论】:

    标签: oop domain-driven-design repository-pattern ddd-repositories onion-architecture


    【解决方案1】:

    我确实认为您试图获得最通用的解决方案有点过于复杂,但是我认为您当前的问题有一个非常简单的解决方案。

    TDataEntity 是一个持久化数据结构,它没有域值并且在持久化层之外不为人所知。所以它可以有它永远不会使用的字段,存储库是唯一知道这一点的,它是一个持久性 detail 。你可以在这里“马虎”,在这个级别上事情并不那么重要。

    即使是“hacky”演员也是一个很好的解决方案,因为它在一个地方并且是一个私人细节

    到处都有干净和可维护的代码很好,但是我们不能浪费时间在每一层都提出“完美”的解决方案。就个人而言,对于视图和持久性模型,我更喜欢最快和最简单的解决方案,即使它们有点臭。

    P.S:作为一个经验法则,通用存储库接口很好,通用抽象存储库不是那么多(您需要小心),除非您正在序列化事物或使用 doc db。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-01-14
      • 2016-05-20
      • 1970-01-01
      • 2011-11-20
      • 1970-01-01
      • 2013-01-18
      • 2016-09-20
      • 2010-12-02
      相关资源
      最近更新 更多