基于ABP实现DDD--实体创建和更新
本文主要介绍了通过构造函数和领域服务创建实体2种方式,后者多用于在创建实体时需要其它业务规则检测的场景。最后介绍了在应用服务层中如何进行实体的更新操作。
一.通过构造函数创建实体
假如Issue的聚合根类为:
public class Issue : AggregateRoot<Guid>
{
public Guid RepositoryId { get; private set; } //不能修改RepositoryId,原因是不支持把一个Issue移动到另外一个Repository下面
public string Title { get; private set; } //不能直接修改Title,可以通过SetTitle()修改,主要是在该方法中要加入对Title不能重复的校验
public string Text { get; set; } //可以直接修改
public Guid? AssignedUserId { get; internal set; } //同一个程序集中是可以修改AssignedUserId的
// 公有构造函数
public Issue(Guid id, Guid repositoryId, string title, string text=null) : base(id)
{
RepositoryId = repositoryId;
Title = Check.NotNullOrWhiteSpace(title, nameof(title));
Text = text; //可为空或者null
}
// 私有构造函数
private Issue() {}
// 修改Title的方法
public void SetTitle(string title)
{
// Title不能重复
Title = Check.NotNullOrWhiteSpace(title, nameof(title));
}
// ...
}
在应用服务层创建一个Issue的过程如下:
public class IssueAppService : ApplicationService.IIssueAppService
{
private readonly IssueManager _issueManager; //Issue领域服务
private readonly IRepository<Issue, Guid> _issueRepository; //Issue仓储
private readonly IRepository<AppUser, Guid> _userRepository; //User仓储
// 公有构造函数
public IssueAppService(IssueManager issueManager, IRepository<Issue, Guid> issueRepository, IRepository<AppUser, Guid> userRepository)
{
_issueManager = issueManager;
_issueRepository = issueRepository;
_userRepository = userRepository;
}
// 通过构造函数创建Issue
public async Task<IssueDto> CreateAssync(IssueCreationDto input)
{
var issue = new Issue(GuidGenerator.Create(), input.RepositoryId, input.Title, input.Text);
}
if(input.AssigneeId.HasValue)
{
// 获取分配给Issue的User
var user = await _userRepository.GetAsync(input.AssigneeId.Value);
// 通过Issue的领域服务,将Issue分配给User
await _issueManager.AssignAsync(issue, user);
}
// 插入和更新Issue
await _issueRepository.InsertAsync(issue);
// 返回IssueDto
return ObjectMapper.Map<Issue, IssueDto>(issue);
}
二.通过领域服务创建实体
什么样的情况下会用领域服务创建实体,而不是通过实体构造函数来创建实体呢?主要用在创建实体时需要其它业务规则检测的场景。比如,在创建Issue的时候,不能创建Title相同的Issue。通过Issue实体构造函数来创建Issue实体,这个是控制不住的。所以才会有通过领域服务创建实体的情况。
阻止从Issue的构造函数来创建Issue实体,需要将其构造函数的访问权限由public修改为internal:
public class Issue : AggregateRoot<Guid>
{
// ...
internal Issue(Guid id, Guid repositoryId, string title, string text = null) : base(id)
{
RepositoryId = repositoryId;
Title = Check.NotNullOrEmpty(title, nameof(title));
Text = text; //允许为空或者null
}
// ...
}
通过领域服务IssueManager中的CreateAsync()方法来判断创建的Issue的Title是否重复:
public class IssueManager:DomainService
{
private readonly IRepository<Issue,Guid> _issueRepository; // Issue的仓储
// 公有构造函数,注入仓储
public IssueManager(IRepository<Issue,Guid> issueRepository)
{
_issueRepository=issueRepository;
}
public async Task<Issue> CreateAsync(Guid repositoryId, string title, string text=null)
{
// 判断Issue的Title是否重复
if(await _issueRepository.AnyAsync(i=>i.Title==title))
{
throw new BusinessException("IssueTracking:IssueWithSameTitleExists");
}
// 返回创建的Issue实体
return new Issue(GuidGenerator.Create(), repositoryId, title, text);
}
}
在应用服务层IssueAppService中通过IssueManager.CreateAsync()创建实体如下:
public class IssueAppService :ApplicationService.IIssueAppService
{
private readonly IssueManager _issueManager; //Issue的领域服务
private readonly IRepository<Issue,Guid> _issueRepository; //Issue的仓储
private readonly IRepository<AppUser,Guid> _userRepository; //User的仓储
// 公共的构造函数,注入所需的依赖
public IssueAppService(IssueManager issueManager, IRepository<Issue,Guid> issueRepository, IRepository<AppUser,Guid> userRepository){
_issueManager=issueManager;
_issueRepository=issueRepository;
_userRepository=userRepository;
}
// 创建一个Issue
public async Task<IssueDto> CreateAsync(IssueCreationDto input)
{
// 通过领域服务的_issueManager.CreateAsync()创建实体,主要是保证Title不重复
var issue=await _issueManager.CreateAsync(input.RepositoryId, input.Title, input.Text);
// 获取User,并将Issue分配给User
if(input.AssignedUserId.HasValue)
{
var user =await _userRepository.GetAsync(input.AssignedUserId.Value);
await _issueManager.AssignToAsynce(issue,user);
}
// 插入和更新数据库
await _issueRepository.InsertAsync(issue);
// 返回IssueDto
return ObjectMapper.Map<Issue,IssueDto>(issue);
}
}
// 定义Issue的创建DTO为IssueCreationDto
public class IssueCreationDto
{
public Guid RepositoryId{get;set;}
[Required]
public string Title {get;set;}
public Guid? AssignedUserId{get;set;}
public string Text {get;set;}
}
现在有个疑问是为什么不把Title的重复检测放在领域服务层中来做呢,这就涉及一个区分核心领域逻辑还是应用逻辑的问题了。显然这里Title不能重复属于核心领域逻辑,所以放在了领域服务中来处理。为什么标题重复检测不在应⽤服务中实现?详细的解释参考[1]。
三.实体的更新操作
接下来介绍在应用层IssueAppService中来update实体。定义UpdateIssueDto如下:
public class UpdateIssueDto
{
[Required]
public string Title {get;set;}
public string Text{get;set;}
public Guid? AssignedUserId{get;set;}
}
实体更新操作的UpdateAsync()方法如下所示:
public class IssueAppService :ApplicationService.IIssueAppService
{
private readonly IssueManager _issueManager; //Issue领域服务
private readonly IRepository<Issue,Guid> _issueRepository; //Issue仓储
private readonly IRepository<AppUser,Guid> _userRepository; //User仓储
// 公有构造函数,注入依赖
public IssueAppService(IssueManager issueManager, IRepository<Issue,Guid> issueRepository, IRepository<AppUser,Guid> userRepository){
_issueManager=issueManager;
_issueRepository=issueRepository;
_userRepository=userRepository;
}
// 更新Issue
public async Task<IssueDto> UpdateAsync(Guid id, UpdateIssueDto input)
{
// 从Issue仓储中获取Issue实体
var issue = await _issueRepository.GetAsync(id);
// 通过领域服务的issueManager.ChangeTitleAsync()方法更新Issue的标题
await _issueManager.ChangeTitleAsync(issue,input.Title);
// 获取User,并将Issue分配给User
if(input.AssignedUserId.HasValue)
{
var user = await _userRepository.GetAsync(input.AssignedUserId.Value);
await _issueManager.AssignToAsync(issue, user);
}
issue.Text=input.Text;
// 更新和保存Issue
// 保存实体更改是应用服务方法的职责
await _issueRepository.UpdateAsync(issue);
// 返回IssueDto
return ObjectMapper.Map<Issue,IssueDto>(issue);
}
}
需要在IssueManager中添加ChangeTitle():
public async Task ChangeTitleAsync(Issue issue,string title)
{
// Title不变就返回
if(issue.Title==title)
{
return;
}
// Title重复就抛出异常
if(await _issueRepository.AnyAsync(i=>i.Title==title))
{
throw new BusinessException("IssueTracking:IssueWithSameTitleExists");
}
// 请它情况更新Title
issue.SetTitle(title);
}
修改Issue类中SetTitle()方法的访问权限为internal:
internal void SetTitle(string title)
{
Title=Check.NotNullOrWhiteSpace(title,nameof(title));
}
参考文献:
[1]基于ABP Framework实现领域驱动设计:https://url39.ctfile.com/f/2501739-616007877-f3e258?p=2096 (访问密码: 2096)
基于ABP实现DDD--实体创建和更新的更多相关文章
- 基于ABP落地领域驱动设计-05.实体创建和更新最佳实践
目录 系列文章 数据传输对象 输入DTO最佳实践 不要在输入DTO中定义不使用的属性 不要重用输入DTO 输入DTO中验证逻辑 输出DTO最佳实践 对象映射 学习帮助 系列文章 基于ABP落地领域驱动 ...
- 基于ABP落地领域驱动设计-02.聚合和聚合根的最佳实践和原则
目录 前言 聚合 聚合和聚合根原则 包含业务原则 单个单元原则 事务边界原则 可序列化原则 聚合和聚合根最佳实践 只通过ID引用其他聚合 用于 EF Core 和 关系型数据库 保持聚合根足够小 聚合 ...
- 基于ABP落地领域驱动设计-00.目录和小结
<实现领域驱动设计> -- 基于 ABP Framework 实现领域驱动设计实用指南 翻译缘由 自 ABP vNext 1.0 开始学习和使用该框架,被其优雅的设计和实现吸引,适逢 AB ...
- 基于ABP落地领域驱动设计-04.领域服务和应用服务的最佳实践和原则
目录 系列文章 领域服务 应用服务 学习帮助 系列文章 基于ABP落地领域驱动设计-00.目录和前言 基于ABP落地领域驱动设计-01.全景图 基于ABP落地领域驱动设计-02.聚合和聚合根的最佳实践 ...
- 基于ABP落地领域驱动设计-06.正确区分领域逻辑和应用逻辑
目录 系列文章 领域逻辑和应用逻辑 多应用层 示例:正确区分应用逻辑和领域逻辑 学习帮助 系列文章 基于ABP落地领域驱动设计-00.目录和前言 基于ABP落地领域驱动设计-01.全景图 基于ABP落 ...
- [ABP教程]第三章 创建、更新和删除图书
Web应用程序开发教程 - 第三章: 创建,更新和删除图书 关于本教程 在本系列教程中, 你将构建一个名为 Acme.BookStore 的用于管理书籍及其作者列表的基于ABP的应用程序. 它是使用以 ...
- ABP(现代ASP.NET样板开发框架)系列之10、ABP领域层——实体
点这里进入ABP系列文章总目录 基于DDD的现代ASP.NET开发框架--ABP系列之10.ABP领域层——实体 ABP是“ASP.NET Boilerplate Project (ASP.NET样板 ...
- ABP领域层——实体
ABP领域层——实体 基于DDD的现代ASP.NET开发框架--ABP系列之10.ABP领域层——实体 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的 ...
- ABP+AdminLTE+Bootstrap Table权限管理系统第三节--abp分层体系,实体相关及ABP模块系统
返回总目录:ABP+AdminLTE+Bootstrap Table权限管理系统一期 ABP模块系统 说了这么久,还没有详细说到abp框架,abp其实基于DDD(领域驱动设计)原则的细看分层如下: 再 ...
随机推荐
- Shell脚本实战:日志关键字监控+自动告警
一个执着于技术的公众号 该程序使用场景说明:主要用于Linux服务器监控程序日志,如出现关键字异常则触发相应的动作或告警操作,通知到邮件联系人. 一.安装邮件服务 1.解压 tar -jxf mail ...
- .NET混合开发解决方案8 WinForm程序中通过设置固定版本运行时的BrowserExecutableFolder属性集成WebView2控件
系列目录 [已更新最新开发文章,点击查看详细] 在我的博客<.NET混合开发解决方案7 WinForm程序中通过NuGet管理器引用集成WebView2控件>中介绍了WinForm ...
- 基础路径规划算法(Dijikstra、A*、D*)总结
引言 在一张固定地图上选择一条路径,当存在多条可选的路径之时,需要选择代价最小的那条路径.我们称这类问题为最短路径的选择问题.解决这个问题最经典的算法为Dijikstra算法,其通过贪心选择的步骤从源 ...
- 有了这10个GitHub仓库,开发者如同buff加持
摘要:列出了10个极好的仓库,它们为所有web和软件开发人员提供了巨大的价值. 本文分享自华为云社区<所有开发者都应该知道的10个GitHub仓库>,作者: Ocean2022 . 除了作 ...
- Springboot目录结构分析
1 src/main/java 存储源码 2 src/main/resource 资源文件夹 (1)src/main/resource/static 用于存放静态资源,如css.js.图片.文件 ...
- unity---世界坐标和本地坐标的转换
会受到缩放的影响 用的比较多的是 本地坐标转换为世界坐标,比如在自己周围释放物体 当物体发生旋转,方向会发生变化
- BUUCTF刷题记录(更新中...)
极客大挑战 2019]EasySQL-1 直接通过输入万能密码:' or 1=1#实现注入: 思考:服务端sql语句应该为:select * from users where username='xx ...
- npm init cabloy背后的故事
背景 我们知道许多框架会提供一个脚手架工具,我们先下载安装脚手架工具,然后再通过脚手架命令行来创建项目.在npm@6.1.0中引入了npm init <initializer>的语法.简单 ...
- 2020.09.12【NOIP提高组&普及组】模拟赛C组 总结
总结:这次比赛成绩并不理想,虽然策略得当 \(P.S.\):太多题有多组数据,但是样例只有一个数据 各题题解和改题情况 T1 匹配 题面 描述 给你一个由{a,b-z,A,B-.Z}组成的字符串,我们 ...
- C语言- 基础数据结构和算法 - 08 栈的应用_就近匹配20220611
听黑马程序员教程<基础数据结构和算法 (C版本)>, 照着老师所讲抄的, 视频地址https://www.bilibili.com/video/BV1vE411f7Jh?p=1 喜欢的朋友 ...