【发布时间】: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
}
然后使用实体框架实现为聚合提供基本的Find、Add 和Update 功能:
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,这将不是问题。
这个设计的好处是我可以开始进一步细化存储库模型(IUpdateableRepository、IFindableRepository、IDeleteableRepository、IInsertableRepository)并且聚合存储库现在可以根据我们的仅公开相关功能规范(也许您应该被允许插入UserRepository,但不能插入ClientRepository)。除此之外,它还指定了完成某些存储库操作的标准化方式(即,IsDeleted 和 DeletedDate 列的更新将是通用的,并且不在开发人员手中)。
问题
当我想为一些没有删除功能的聚合创建存储库时,会出现上述设计的问题,例如:
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