乐趣区

关于程序员:基于ABP实现DDD仓储实践

  因为软件系统中可能有着不同的数据库,不同的 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 多平台公布

退出移动版