实现领域驱动设计 - 使用ABP框架 - 存储库
存储库
Repository 是一个类似于集合的接口,领域层和应用程序层使用它来访问数据持久性系统(数据库),以读写业务对象(通常是聚合)
常见的存储库原则是:
- 在领域层定义一个存储库接口(因为它被用于领域层和应用层),在基础设施层实现(启动模板中的EntityFrameworkCore项目)
- 不要在存储库中包含业务逻辑。
- 存储库接口应该是独立于数据库提供者/ ORM的。例如,不要从存储库方法返回DbSet。DbSet是 EF Core 提供的一个对象
- 为聚合根创建存储库,而不是为所有实体。因为,子集合实体(聚合的)应该通过聚合根访问
不要在存储库中包含领域逻辑
虽然这个规则在一开始看起来很明显,但是很容易将业务逻辑泄露到存储库中
示例:从存储库中获取不活跃的问题
public interface IIssueRepository : IRepository<Issue, Guid>
{
Task<List<Issue>> GetInActiveIssuesAsync();
}
IIssueRepository 扩展了标准 IRepository<...> 接口,添加GetInActiveIssuesAsync 方法。这个存储库使用这样一个Issue类:
public class Issue : AggregateRoot<Guid>, IHasCreationTime
{
public bool IsClosed { get; private set; }
public Guid? AssignedUserId { get; private set; }
public DateTime CreationTime { get; private set; }
public DateTime? LastCommentTime { get; private set; }
}
(代码只显示了本例所需的属性)
规则规定存储库不应该知道业务规则。这里的问题是 “什么是不活跃的问题? ”它是业务规则定义吗?”
让我们看看实现来理解它:
public class EfCoreIssueRepository :
EfCoreRepository<IssueTrackingDbContext, Issue, Guid>
IIssueRepository
{
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();
}
}
(使用EF Core实现。查看 EF Core集成文档,了解如何使用 EF Core 创建自定义存储库。)
当我们检查 GetInActiveIssuesAsync 的实现时,我们看到了一个业务规则,它给出了不活跃的问题的定义:该问题应该是开放的,没有分配给任何人,30天前创建的,并且在最近30天内没有任何评论
这是隐藏在存储库方法中的业务规则的隐式定义。当我们需要重用该业务逻辑时,就会出现问题
例如,假设我们想要在 Issue 实体上添加一个 bool IsInActive() 方法。这样,当我们有 Issue 实体时,我们就可以检查活跃度。
让我们看看实现:
public class Issue : AggregateRoot<Guid>, IHasCreationTime
{
public bool IsClosed { get; private set; }
public Guid? AssignedUserId { get; private set; }
public DateTime CreationTime { get; private set; }
public DateTime? LastCommentTime { get; private set; }
public bool IsInActive()
{
var daysAgo30 = DateTime.Now.Subtract(TimeSpan.FromDays(30));
return
//开放的
!IsClosed &&
//没有分配给任何人
AssignedUserId == null &&
//30天前创建的
CreationTime < daysAgo30 &&
//最近30天内没有任何评论
(LastCommentTime == null || LastCommentTime < daysAgo30);
}
}
我们必须复制/粘贴/修改代码。如果活动性的定义改变了呢?我们不应该忘记更新这两个地方。这是业务逻辑的重复,这是非常危险的
这个问题的一个很好的解决方案是规范模式!
规范
规范是一个命名的、可重用的、可组合的和可测试的类,用于基于业务规则筛选领域对象
ABP框架提供了必要的基础设施来轻松地创建规范类并在应用程序代码中使用它们。让我们将不活跃的问题过滤器实现为一个规范类:
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);
}
}
Specification<T> 基类通过定义表达式简化了创建规范类的工作。只是将表达式从存储库移到这里
现在我们就可以在 Issue 实体和 EfCoreIssueRepository 类中复用 InActiveIssueSpecification 了
在实体中使用规范
Specification 类提供了一个 IsSatisfiedBy 方法,如果给定的对象(实体)满足规范,该方法返回true。我们可以重写这个 Issue。IsInActive 方法如下所示:
public class Issue : AggregateRoot<Guid>, IHasCreationTime
{
public bool IsInActive()
{
return new InActiveIssueSpecification().IsSatisfiedBy(this);
}
}
在存储库中使用规范
首先,从存储库接口开始:
public interface IIssueRepository : IRepository<Issue, Guid>
{
Task<List<Issue>> GetIssuesAsync(ISpecification<Issue> spec);
}
- 将 GetInActiveIssuesAsync 重命名为简单的 GetIssuesAsync, 并接受一个规范对象
- 由于规范(过滤器)已经从存储库中移出,我们不再需要创建不同的方法来获得不同条件下的问题(比如: GetAssignedIssues(...) , GetLockedIssues(...) 等等)
更新后的存储库实现可以像这样:
public class EfCoreIssueRepository :
EfCoreRepository<IssueTrackingDbContext, Issue, Guid>
IIssueRepository
{
public async Task<List<Issue>> GetIssuesAsync(ISpecification<Issue> spec)
{
var dbSet = await GetDbSetAsync();
return await dbSet
.Where(spec.ToExpression())
.ToListAsync();
}
}
因为ToExpression()方法返回一个表达式,所以它可以直接传递给Where方法来过滤实体
- 最终,我们做到了业务逻辑的代码复用,消除了安全隐患
使用默认的存储库
实际上,您不必创建自定义存储库才能使用规范。标准的IRepository已经扩展了IQueryable,所以你可以在上面使用标准的LINQ扩展方法:
public class IssueAppService : ApplicationService, IIssueAppService
{
public async Task<List<Issue>> GetInActiveIssuesAsync()
{
var queryable = await _issueRepository.GetQueryableAsync();
var issues = await AsyncExecuter.ToListAsync(
queryable.Where(new InActiveIssueSpecification())
);
}
}
AsyncExecuter 是ABP框架提供的一个实用工具,用于使用异步LINQ扩展方法(如这里的ToListAsync),而不依赖于EF Core NuGet包。有关更多信息,请参阅 Repositories文档
组合规范
规范的一个强大的方面是它们是可组合的。假设我们有另一个规范,它只在问题位于里程碑时返回true
public class MilestoneSpecification : Specification<Issue>
{
public Guid MilestoneId { get; }
public override Expression<Func<Issue,bool>> ToExpression()
{
return i => i.MilestoneId == MilestoneId;
}
}
本规范是参数化的,与 InActiveIssueSpecification 有所不同。我们可以结合这两个规范来获得特定里程碑中的非活跃问题列表
public class IssueAppService : ApplicationService, IIssueAppService
{
public async Task<List<Issue>> GetInActiveIssuesWithinMilestoneAsync(Guid milestoneId)
{
var queryable = await _issueRepository.GetQueryableAsync();
var issues = await AsyncExecuter.ToListAsync(
queryable.Where(
new InActiveIssueSpecification()
.And(new MilestoneSpecification(milestoneId))
.ToExpression()
)
);
}
}
上面的示例使用And扩展方法来组合这些规范。还有更多的组合方法可用,比如 Or(…) 和 AndNot(…)
有关ABP框架提供的规范基础架构的更多细节,请参阅 规范文档。
实现领域驱动设计 - 使用ABP框架 - 存储库的更多相关文章
- 实现领域驱动设计 - 使用ABP框架 - 什么是领域驱动设计?
前言: 最近看到ABP官网的一本电子书,感觉写的很好,翻译出来,一起学习下 (Implementing Domain Driven Design) https://abp.io/books DDD简介 ...
- 实现领域驱动设计 - 使用ABP框架 - 通用准则
在进入细节之前,让我们看看一些总体的 DDD 原则 数据库提供者 / ORM 无关性 领域和应用程序层应该与 ORM / 数据库提供程序 无关.它们应该只依赖于 Repository 接口,而 Rep ...
- 实现领域驱动设计 - 使用ABP框架 - 解决方案概览
.NET解决方案的分层 下图显示了使用ABP的 应用启动模板 创建的Visual Studio解决方案: 解决方案名称为问题跟踪,它由多个项目组成.通过考虑DDD原则以及开发和部署实践,该解决方案是分 ...
- 实现领域驱动设计 - 使用ABP框架 - 创建实体
用例演示 - 创建实体 本节将演示一些示例用例并讨论可选场景. 创建实体 从实体/聚合根类创建对象是实体生命周期的第一步.聚合/聚合根规则和最佳实践部分建议为Entity类创建一个主构造函数,以保证创 ...
- .net core +codefirst(.net core 基础入门,适合这方面的小白阅读) 【我们一起写框架】领域驱动设计的CodeFirst框架(一)—序篇
.net core +codefirst(.net core 基础入门,适合这方面的小白阅读) 前言 .net core mvc和 .net mvc开发很相似,比如 视图-模型-控制器结构.所以. ...
- 【我们一起写框架】领域驱动设计的CodeFirst框架(一)—序篇
前言 领域驱动设计,其实已经是一个很古老的概念了,但它的复杂度依旧让学习的人头疼不已. 互联网关于领域驱动的文章有很多,每一篇写的都很好,理解领域驱动设计的人都看的懂. 不过,这些文章对于那些初学者而 ...
- 【DDD】使用领域驱动设计思想实现业务系统
最近新接了一个业务系统——社区服务系统,为了快速熟悉和梳理老系统的业务逻辑和代码,同时对老系统代码做一些优化,于是打算花上一个月时间不间断地对老系统服务进行重构.同时,考虑到社区业务的复杂性,想起了之 ...
- .NET领域驱动设计—初尝(一:疑问、模式、原则、工具、过程、框架、实践)
.NET领域驱动设计—初尝(一:疑问.模式.原则.工具.过程.框架.实践) 2013-04-07 17:35:27 标签:.NET DDD 驱动设计 原创作品,允许转载,转载时请务必以超链接形式标明 ...
- 如何使用ABP进行软件开发(2) 领域驱动设计和三层架构的对比
简述 上一篇简述了ABP框架中的一些基础理论,包括ABP前后端项目的分层结构,以及后端项目中涉及到的知识点,例如DTO,应用服务层,整洁架构,领域对象(如实体,聚合,值对象)等. 笔者也曾经提到,AB ...
随机推荐
- 01 | 堆、栈、RAII:C++里该如何管理资源?(极客时间笔记)
基本概念 堆,英文是 heap,在内存管理的语境下,指的是动态分配内存的区域.这个堆跟数据结构里的堆不是一回事.这里的内存,被分配之后需要手工释放,否则,就会造成内存泄漏. C++ 标准里一个相关概念 ...
- oracle各种用户登录的方式
Oracle有3种用户: system.sys.scott 1.system和sys的差别在与是否能创建数据库2.sys用户登录创建数据库,3.scott是给刚開始学习的人学习的用户.学习者能够用Sc ...
- layui下拉框渲染问题,以及回显问题
最近实习公司给的新人练手项目用的layui,layui之前自己也接触过但是也就是用了用table组件,没有用过layer弹层这些东西,所以就了解了一下. 首先遇到的一个问题就是下拉框没有样式,然后加样 ...
- Water 2.5.9 发布,一站式服务治理平台
Water(水孕育万物...) Water 为项目开发.服务治理,提供一站式解决方案(可以理解为微服务架构支持套件).基于 Solon 框架开发,并支持完整的 Solon Cloud 规范:已在生产环 ...
- 百兆以太网(100BASE-TX)的波形和眼图
沾了公司的光用了那台采样率吓死人的示波器看了下百兆以太网的三电平波形和眼图. 之前我也强调过百兆的三电平是不能从1状态越过0状态跳到-1状态的,从眼图上能明显看出来. 可以看出这个信号还是不错的.甚至 ...
- AWS - Basic 1
之前由于公司 Training 考取了 AWS-SAP 的证书,更多理解的是概念和理论上的知识,并未实操.但对于学习一门技术来说,实践是加深理解和掌握该技术的必经之路,强调知行合一.所以最近打算重新熟 ...
- gin框架使用【2.RESTFUL API使用】
package mainimport ( "github.com/gin-gonic/gin")func main() { router := gin.Default() rout ...
- docker基础_Dockerfile
Dockerfile []: https://docs.docker.com/language/python/build-images/ "docker官方文档" 以python为 ...
- python基础练习题(题目 模仿静态变量的用法)
day27 --------------------------------------------------------------- 实例041:类的方法与变量 题目 模仿静态变量的用法. 程序 ...
- nodejs使用jquery风格环境安装
BEGIN; 1.npm install jQuery 注意:是jQuery,不是jquery! 2.npm install jsdom 注意:直接执行会安装错误,必须先指定安装版本! 解决:修改pa ...