因为软件系统中可能有着不同的数据库,不同的ORM,仓储思维的实质是解耦它们。在ABP中具体的实现仓储接口定义在畛域层,实现在基础设施层。仓储接口被畛域层(比方畛域服务)和应用层用来拜访数据库,操作聚合根,聚合根就是业务单元。这篇文章次要剖析怎么通过规约将业务逻辑从仓储实现中剥离进去,从而让仓储专一于数据处理。
一.业务需要
还是以Issue聚合根为例,如果有个业务规定是:判断是否是未激活的Issue,条件是关上状态、未调配给任何人、创立超过30天、最近30天没有评论。Issue聚合根如下:
二.在仓储中实现业务逻辑
该业务规定在基础设施层中实现如下:
namespace IssueTracking.Issues
{
public class EfCoreIssueRepository : EfCoreRepository<IssueTrackingDbContext, IssueTracking, Guid>, IIssueRepository
{
// 构造函数
public EfCoreIssueRepository(IDbContextProvider<IssueTrackingDbContext> dbContextProvider) : base(dbContextProvider)
{
}
// 判断是否是未激活的Issue
public async Task<List<Issue>> GetInActiveIssuesAsync()
{
var daysAgo30 = DateTime.Now.Subtract(TimeSpan.FromDays(30));
var dbSet = await GetDbSetAsync();
return await dbSet.Where(i =>
// 关上状态
!i.IsClosed &&
// 无调配人
i.AssignedUserId == null &&
// 创立工夫在30天前
i.CreationTime < daysAgo30 &&
// 没有评论或最初一次评论在30天前
(i.LastCommentTime == null || i.LastCommentTime < daysAgo30)
).toListAsync();
}
}
}
依据DDD中仓储的实际准则,必定是不能将业务逻辑放在仓储实现中的,接下来应用规约的形式来解决这个问题。
三.应用规约实现业务逻辑
规约就是一种约定,标准来讲:规约是一个命名的、可重用的、可组合的和可测试的类,用于依据业务规定来过滤畛域对象。通过ABP中的Specification<T>规约基类创立规约类,将判断Issue是否激活这个业务规定实现为一个规约类如下:
namespace IssueTracking.Issues
{
public class InActiveIssueSpecification : Specification<Issue>
{
public override Expression<Func<Issue, bool>> ToExpression()
{
var daysAgo30 = DateTime.Now.Subtract(TimeSpan.FromDays(30));
return i =>
// 关上状态
!i.IsClosed &&
// 无调配人
i.AssignedUserId == null &&
// 创立工夫在30天前
i.CreationTime < daysAgo30 &&
// 没有评论或最初一次评论在30天前
(i.LastCommentTime == null || i.LastCommentTime < daysAgo30)
}
}
}
接下来解说在Issue实体和EfCoreIssueRepository类中如何应用InActiveIssueSpecification规约。
四.在实体中应用规约
规约是依据业务规定来过滤畛域对象,Issue聚合根中的IsInActive()办法实现如下:
public class Issue : AggregateRoot<Guid>, IHasCreationTime
{
public bool IsClosed { get; private set; }
public Guid? AssignedUserId { get; private set; }
public DateTime CreateTime { get; private set; }
public DateTime? LastCommentTime { get; private set; }
// 判断Issue是否未激活
public bool IsInActive()
{
return new InActiveIssueSpecification().IsSatisfiedBy(this);
}
}
创立一个InActiveIssueSpecification实例,应用它的IsSatisfiedBy()来进行规约验证。
五.在仓储中应用规约
畛域层中的(自定义)仓储接口如下,GetIssuesAsync()接管一个规约对象参数:
public interface IIssueRepository : IRepository<Issue, Guid>
{
Task<List<Issue> GetIssuesAsync(ISpecification<Issue> spec);
}
基础设施层中的(自定义)仓储实现如下:
public class EfCoreIssueRepository: EfCoreRepository<IssueTrackingDbContext, EfCoreIssueRepository, Guid>, IIssueRepository
{
// 构造函数
public EfCoreIssueRepository(IDbContextProvider<IssueTrackingDbContext> dbContextProvider) : base(dbContextProvider)
{
}
public async Task<List<Issue>> GetIssuesAsync(ISpecification<Issue> spec)
{
var dbSet = await GetDbSetAsync();
// 通过表达式实现Issue实体过滤
return await dbSet.Where(spec.ToExpression()).ToListAsync();
}
}
}
应用层应用规约如下,实质上就是新建一个规约实例,而后作为GetIssuesAsync()的参数:
public class IssueAppService : ApplicationService, IIssueAppService
{
private readonly IIssueRepository _issueRepository;
// 构造函数
public IssueAppService(IIssueRepository issueRepository)
{
_issueRepository = issueRepository;
}
public async Task DoItAsync()
{
// 在应用层通过仓储应用规约来过滤实体
var issues = await _issueRepository.GetIssuesAsync(new InActiveIssueSpecification());
}
}
六.在应用层中通过默认仓储来应用规约
下面是在应用层中通过自定义仓储来应用规约的,接下来解说在应用层中通过默认仓储来应用规约:
public class IssueAppService : ApplicationService, IIssueAppService
{
private readonly IRepository<Issue, Guid> _issueRepository;
// 构造函数
public IssueAppService(IRepository<Issue, Guid> issueRepository)
{
_issueRepository = issueRepository;
}
public async Task DoItAsync()
{
var queryable = await _issueRepository.GetQueryableAsync();
// 简略了解,queryable就是查问进去的实体,而后依据规约进行过滤
var issues = AsyncExecuter.ToListAsync(queryable.Where(new InActiveIssueSpecification()));
}
}
阐明:AsyncExecuter是ABP提供的一个工具类,用于应用异步LINQ拓展办法,而不依赖于EF Core NuGet包。
七.组合规约的应用
规约是可组合应用的,这样就变的很弱小。比方,再定义一个规约,当Issue是指定里程碑是返回True。定义新的规约如下:
public class MilestoneSpecification : Specification<Issue>
{
public Guid MilestoneId { get; }
// 构造函数
public MilestoneSpecification(Guid milestoneId)
{
MilestoneId = milestoneId;
}
public override Expression<Func<Issue, bool>> ToExpression()
{
return x => x.MilestoneId == MilestoneId;
}
}
如果和下面定义的InActiveIssueSpecification规约组合,就能够实现业务逻辑:获取指定里程碑中未激活的Issue:
public class IssueAppService : ApplicationService, IIssueAppService
{
private readonly IRepository<Issue, Guid> _issueRepository;
// 构造函数
public IssueAppService(IRepository<Issue, Guid> issueRepository)
{
_issueRepository = issueRepository;
}
public async Task DoItAsync(Guid milestoneId)
{
var queryable = await _issueRepository.GetQueryableAsync();
// 组合规约的应用办法,除了Add扩大办法,还有Or()、And()、Not()等办法
var issues = AsyncExecuter.ToListAsync(
queryable.Where(new InActiveIssueSpecification()
.Add(new MilestoneSpecification(milestoneId))
.ToExpression()
)
);
}
}
参考文献:
[1]基于ABP Framework实现畛域驱动设计:https://url39.ctfile.com/f/25… (拜访明码: 2096)
本文由mdnice多平台公布
发表回复