基于ABP落地领域驱动设计-03.仓储和规约最佳实践和原则

dotNET兄弟会 

专注.Net开源技术及跨平台开发!致力于构建完善的.Net开放技术文库!为.Net爱好者提供学习交流家园!

公众号  

围绕DDDABP Framework两个核心技术,后面还会陆续发布核心构件实现综合案例实现系列文章,敬请关注! ABP Framework 研习社(QQ群:726299208) ABP Framework 学习及实施DDD经验分享;示例源码、电子书共享,欢迎加入!

系列文章

基于ABP落地领域驱动设计-01.全景图基于ABP落地领域驱动设计-02.聚合和聚合根的最佳实践和原则

仓储

仓储(接口)是一组集合的接口,被领域层和应用层用来访问数据持久化系统(数据库),以读写业务对象,业务对象通常是聚合。

仓储的通用原则

•在领域层中定义仓储接口,在基础层中实现仓储接口(比如:EntityFrameworkCore项目或MongoDB项目)•仓储不包含业务逻辑,专注数据处理。•仓储接口应该保持 数据提供程序/ORM 独立性。举个例子,仓储接口定义的方法不能返回 DbSet 对象,因为该对象由 EF Core 提供,如果使用 MongoDB 数据库则无法实现该接口。•为聚合根创建对应仓储,而不是所有实体。因为子集合实体(聚合)应该通过聚合根访问。

仓储中不包含领域逻辑

虽然这个规则一开始看起来很好理解,但在实际开发过程中,很容易在不经意间将业务逻辑放到仓储中。

示例:从仓储中获取 inactive 状态的 Issue

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Domain.Repositories;namespace IssueTracking.Issues
{public interface IIssueRepository:IRepository<Issue,Guid>{Task<List<Issue>> GetInActiveIssuesAsync();}
}

IIssueRepository 继承 IRepository<Issue,Guid> 接口,添加了 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;}
}

规则要求我们:仓储不应该知道业务规则,那么问题来了:什么是 inactive Issue(未激活的问题)?这是业务规则

为了更好地理解,我们继续看看接口方法的实现:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using IssueTracking.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;namespace IssumeTracking.Issues
{public class EfCoreIssueRepository:EfCoreRepository<IssueTrackingDbContext,Issue,Guid>,IIssueRepository{public EfCoreIssueRepository(IDbContextProvider<IssueTrackingDbContext> dbContextProvider):base(dbContextProvider){}public async Task<List<Issue>> GetInActiveIssueAsynce(){var daysAgo30=DateTime.Now.Subtract(TimeSpan.FromDays(30));var dbSet =await GetDbSetAsync();return await dbSet.Where(i=>//打开状态!i.IsClosed &&//无分配人i.AssingedUserId ==null &&//创建时间在30天前i.CreationTime < daysAgo30 &&//没有评论或最后一次评论在30天前(i.LastCommentTime == null || i.LastCommentTime < daysAgo30)).ToListAsync();}}
}

在 GetInActiveIssueAsynce 实现方法中,对于未激活的Issue 这条业务规则,需要满足条件:打开状态、未分配给任何人、创建超过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 CreationTiem{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 );}
}

我们不得不复制、粘贴、修改代码。如果对未激活的Issue 规则改变了怎么办?我们应该记得同时更新这两个地方。这是业务逻辑重复,代码的坏味道,是相当危险的。

这个问题的一个很好的解决方案就是规约

规约

规约是一个命名的、可重用的可组合的和可测试的类,用于根据业务规则过滤领域对象

ABP框架提供了必要的基础设施,以轻松创建规约并在你的应用程序代码中使用。让我们把 inactive Issue 非活动问题业务规则实现为一个规约类

using System;
using System.Linq.Expressions;
using Volo.Abp.Specifications;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.AssingedUserId ==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 IsClosed{get;private set;}public Guid? AssignedUserId{get;private set;}public DateTime CreationTiem{get;private set;}public DateTime? LastCommentTime{get;private set;}//...public bool IsInActive(){return new InActiveIssueSpecification().IsSatisfiedBy(this);}
}

创建一个 InActiveIssueSpecification 新实例,使用其 IsSatisfiedBy 方法,进行规约验证。

在仓储中使用规约

首先,修改仓储接口:

public interface IIssueRepository:IRepository<Issue,Guid>
{Task<List<Issue>> GetIssuesAsync(ISpecification<Issue> spec);
}

将方法名 GetInActiveIssuesAsync 改为 GetIssuesAsync (命名更加简洁),接收一个规约对象参数。将规约判断的代码逻辑从仓储中移出之后,我们不再需要定义不同的方法来获取不同条件下的Issue,比如:GetAssignedIssues(...) 获取已有分配人的问题列表,GetLockedIssues(...) 获取已锁定问题列表 等。

修改仓储的实现:

public class EfCoreIssueRepository:EfCoreRepository<IssueTrackingDbContext,Issue,Guid>,IIssueRepository
{public EfCoreIssueRepository(IDbContextProvider<IssueTrackingDbContext> dbContextProvider):base(dbContextProvider){}public async Task<List<Issue>> GetIssuesAsync(ISpecification<Issue> spec){var dbSet = await GetDbSetAsync();return await dbSet.Where(spec.ToExpresion()).ToListAsync();}
}

ToExpression()方法返回一个表达式,可以直接作为 Where 方法的参数传递,实现实体过滤。

最后,我们将规约实例,传递给 GetIssuesAsync 方法:

public class IssueAppServie : ApplciationService,IIssueAppService
{private readonly IIssueRepository _issueRepository;public IssueAppService (IIssueRepository issueRepository){_issueRepository = issueRepository;}public async Task DoItAsync(){var issues = await _issueRepository.GetIssuesAsync(new InActiveIssueSpecification(););}
}

默认仓储

实际上,你不需要创建自定义仓储就能使用规约。标准的IRepository 接口已经扩展 IQueryable 接口,所以你可以直接使用标准的LINQ扩展方法。(非常帅气!!!)

public class IssueAppServie : ApplciationService,IIssueAppService
{private readonly IRepository<Issue,Guid> _issueRepository;public IssueAppService (IRepository<Issue,Guid> issueRepository){_issueRepository = issueRepository;}public async Task DoItAsync(){var queryable = await _issueRepository.GetQueryableAsync();var issues = AsyncExecuter.ToListAsync(queryable.Where(new InActiveIssueSpecification()));}
}

AsyncExecuter是ABP框架提供的一个工具类,用于使用异步LINQ扩展方法(比如这里的ToListAsync),而不依赖于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 i => i.MilestoneId == MilestoneId;}
}

我们新定义了一个新的参数化规约,和前面定义 InActiveIssueSpecification 不同。那么如何组合两个规约,获取指定里程碑中未激活的 Issue(问题)呢?

public class IssueAppServie : ApplciationService,IIssueAppService
{private readonly IRepository<Issue,Guid> _issueRepository;public IssueAppService (IRepository<Issue,Guid> issueRepository){_issueRepository = issueRepository;}public async Task DoItAsync(Guid milesoneId){var queryable = await _issueRepository.GetQueryableAsync();var issues = AsyncExecuter.ToListAsync(queryable.Where(new InActiveIssueSpecification().Add(new MilestoneSpecification(milestoneId)).ToExpression()));}
}

示例中使用 Add 扩展方法组合规约,还有更多的扩展方法,比如:Or(...) AndNot(...)

学习帮助

围绕DDDABP Framework两个核心技术,后面还会陆续发布核心构件实现综合案例实现系列文章,敬请关注!

ABP Framework 研习社(QQ群:726299208) 专注 ABP Framework 学习及DDD实施经验分享;示例源码、电子书共享,欢迎加入!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/301232.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

我国最新十大黑科技发布,颠覆你的想象!

全世界只有3.14 % 的人关注了数据与算法之美人工智能的飞速发展&#xff0c;让“黑科技”一词深入人心。目不暇接的各种前沿技术&#xff0c;一再刷新人们的认知世界。所谓“黑科技”&#xff0c;不仅仅要够炫酷&#xff0c;更要有足够的“生活温度”&#xff0c;实现真正的人性…

基于ABP落地领域驱动设计-04.领域服务和应用服务的最佳实践和原则

围绕DDD和ABP Framework两个核心技术&#xff0c;后面还会陆续发布核心构件实现、综合案例实现系列文章&#xff0c;敬请关注&#xff01; ABP Framework 研习社&#xff08;QQ群&#xff1a;726299208&#xff09; ABP Framework 学习及实施DDD经验分享&#xff1b;示例源码、…

嗓子痛引发大抢救!33岁程序员的垂死经历,为所有人敲响警钟!

全世界只有3.14 % 的人关注了数据与算法之美最近广州天气多变&#xff0c;再加上不规律的饮食、作息&#xff0c;数据汪的扁桃体已经肿痛两三天了。在一大杯“凉茶”下肚后&#xff0c;最终我还是选择去医院做一次检查。万幸的是&#xff0c;检查结果出来后&#xff0c;医生说只…

.NET也内卷了,BAT大厂近日上演抢人大战!

这两年到处都流行“内卷”这个词&#xff0c;而IT互联网行业则是内卷重灾区。还不太明白什么是内卷&#xff1f;看看这个段子&#xff1a;关于内卷&#xff0c;网上有个比喻&#xff0c;说如果葵花宝典被公开了&#xff0c;就会变成一个灾难。因为如果只有一个人拥有的话&#…

EFDC水模型 初学者入门 及软件下载学习指导

EFDC模型有三个不同的版本&#xff0c;它们分别代表了计算机不同的操作菜单和功能。其中的两个版本功能齐全&#xff0c;包括了EFDC 所有的产品版本。另外一个是简化版&#xff0c;涵盖了水动力学&#xff08;包括盐度和温度的影响&#xff09;&#xff0c;毒物&#xff0c;沉积…

每日一笑 | 在地铁上被老奶奶让座是一种什么样的体验?

全世界只有3.14 % 的人关注了数据与算法之美&#xff08;图源网络&#xff0c;侵权删&#xff09;

java access dbq_Java-Access汇总

http://www.doczj.com/doc/3b7b55100b4e767f5acfce38.html/view/d1c01b43a8956bec0975e33e.html在网上看了一些资料&#xff0c;基本上都是一样的。怎么试也行不通。于是我怀疑为什么那些错误的技术文章如此大张旗鼓的到处都是。今天有人问我如果把主机托管出去&#xff0c;也就…

一个IT人的非典型职场十年 (5)

2019独角兽企业重金招聘Python工程师标准>>> 对于非资深IT人来讲&#xff0c;IT咨询(IT Consulting)显得是比较高帅富一些&#xff0c;各种衣着光鲜的咨询报告&#xff0c;各种西装革履的顾问。 这先随便说两句什么是IT咨询&#xff0c;IT顾问究竟是在做啥工作。 I…

基于ABP落地领域驱动设计-01.全景图

什么是领域驱动&#xff1f;领域驱动设计&#xff08;简称&#xff1a;DDD&#xff09;是一种针对复杂需求的软件开发方法。将软件实现与不断发展的模型联系起来&#xff0c;专注于核心领域逻辑&#xff0c;而不是基础设施细节。DDD适用于复杂领域和大规模应用&#xff0c;而不…

伦敦科学博物馆用百年智慧打造的一套探索书,拓展孩子的科学、数学和艺术思维...

▲数据汪特别推荐点击上图进入玩酷屋说到素质教育&#xff0c;我们应该比较熟悉。不过&#xff0c;这几年&#xff0c;比素质教育更火的一个概念是——STEAM教育。教育家们普遍认为&#xff1a;在科学、技术、工程、艺术、数学之间存在着一种相互支撑、相互补充、共同发展的关系…

java中事务特性_「java三分钟」事务的传播特性详解

关注我&#xff0c;每天三分钟&#xff0c;带你轻松掌握一个Java相关知识点。事务传播行为就是多个事务方法相互调用时&#xff0c;事务如何在这些方法间传播。换言之&#xff0c;一个带事务的方法调用了另一个带事务的方法&#xff0c;被调用的方法它怎么处理自己事务和调用方…

基于ABP落地领域驱动设计-02.聚合和聚合根的最佳实践和原则

前言上一篇 基于ABP落地领域驱动设计-01.全景图 概述了DDD理论和对应的解决方案、项目组成、项目引用关系&#xff0c;以及基于ABP落地DDD的通用原则。从这本篇开始&#xff0c;会更加深入地介绍在基于 ABP Framework 落地DDD过程中的最佳实践和原则。围绕DDD和ABP Framework两…

每日一笑 | 实名举报校长拿两份工资!

全世界只有3.14 % 的人关注了数据与算法之美&#xff08;图源网络&#xff0c;侵权删&#xff09;

159个故事串起三千年大历史!这套“儿童版史记”太无敌了!

▲数据汪特别推荐点击上图进入玩酷屋17世纪英国哲学家培根说“读史使人明智”&#xff0c;意思是真实、鲜活的历史&#xff0c;不仅能极大拓宽孩子眼界&#xff0c;更能让孩子以古人为鉴&#xff0c;树立远大的志向&#xff0c;对成长大有助益。在我们的学生时代&#xff0c;认…

再见,REST,你好,gRPC

gRPC是一个开源的远程过程调用框架&#xff0c;用于服务之间的高性能通信。服务之间的通信可以使用各种语言&#xff0c;通过可插拔的负载均衡、追踪、健康检查和身份验证&#xff0c;这让它被认为是一种非常高效的方法。在默认情况下&#xff0c;gRPC使用协议缓冲&#xff08;…

全校师生放6天春假;清华大学设立天文系;郭守敬望远镜光谱数突破千万;《自然》发表最新发现;百度败诉需道歉;这就是今天的大新闻...

今天是3月29日农历二月廿三今天星期五早上上班等了N趟车愣是没挤上去下面是今天的大新闻全校师生放6天“春假”&#xff08;中国青年网&#xff09;3月28日&#xff0c;四川西南航空职业学院发布了《关于“泛美春假”的放假通知》&#xff0c;要求在放假时间总量不变的情况下&a…

jenkins java反序列化_Jenkins “Java 反序列化”过程远程命令执行漏洞

###漏洞原理反序列化是指特定语言中将传递的对象序列化数据重新恢复为实例对象的过程&#xff0c;而在这个过程中会执行一系列的字节流解析和对象实例化操作用于恢复之前序列化时的对象。在原博文所提到的那些 Java 应用里都有特定的接口用于传递序列化对象数据&#xff0c;而在…

WPF DataGrid 在Header中显示行号

在Datagrid中显示行号&#xff0c;如果你绑定的datacontext中没有序号&#xff0c;又想要显示序号的时候&#xff0c;可以按照本文的方法显示喽~效果如下图&#xff1a;来看看代码吧~MainWindow.xaml<Window x:Class"wpfcore.MainWindow"xmlns"http://schema…

每日一笑 | 爱的魔力转圈圈~

全世界只有3.14 % 的人关注了数据与算法之美&#xff08;素材源网络&#xff0c;侵权删&#xff09;

怎样维护成功的开源项目

开源可不仅仅是将代码扔到网上就万事大吉了&#xff0c;将开源项目变成能让自己引以为豪的东西才算成功。那么&#xff0c;你需要注意哪些方面呢&#xff1f; 写好指导性文字 每一个开源项目有三样东西是少不了的&#xff1a;项目目标和方法的简要说明、如何参与和授权许可。最…