基于ABP实现DDD--实体创建和更新

??本文主要介绍了通过构造函数和领域服务创建实体2种方式,后者多用于在创建实体时需要其它业务规则检测的场景。最后介绍了在应用服务层中如何进行实体的更新操作。
一.通过构造函数创建实体 假如Issue的聚合根类为:

public class Issue : AggregateRoot { 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 _issueRepository; //Issue仓储 private readonly IRepository _userRepository; //User仓储// 公有构造函数 public IssueAppService(IssueManager issueManager, IRepository issueRepository, IRepository userRepository) { _issueManager = issueManager; _issueRepository = issueRepository; _userRepository = userRepository; }// 通过构造函数创建Issue public async Task 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); }

二.通过领域服务创建实体 ??什么样的情况下会用领域服务创建实体,而不是通过实体构造函数来创建实体呢?主要用在创建实体时需要其它业务规则检测的场景。比如,在创建Issue的时候,不能创建Title相同的Issue。通过Issue实体构造函数来创建Issue实体,这个是控制不住的。所以才会有通过领域服务创建实体的情况。
阻止从Issue的构造函数来创建Issue实体,需要将其构造函数的访问权限由public修改为internal:
public class Issue : AggregateRoot { // ...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 _issueRepository; // Issue的仓储// 公有构造函数,注入仓储 public IssueManager(IRepository issueRepository) { _issueRepository=issueRepository; }public async Task 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 _issueRepository; //Issue的仓储 private readonly IRepository _userRepository; //User的仓储// 公共的构造函数,注入所需的依赖 public IssueAppService(IssueManager issueManager, IRepository issueRepository, IRepository userRepository){ _issueManager=issueManager; _issueRepository=issueRepository; _userRepository=userRepository; } // 创建一个Issue public async Task 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); } }// 定义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 _issueRepository; //Issue仓储 private readonly IRepository _userRepository; //User仓储// 公有构造函数,注入依赖 public IssueAppService(IssueManager issueManager, IRepository issueRepository, IRepository userRepository){ _issueManager=issueManager; _issueRepository=issueRepository; _userRepository=userRepository; }// 更新Issue public async Task 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); } }

需要在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/25... (访问密码: 2096)
【基于ABP实现DDD--实体创建和更新】本文由mdnice多平台发布

    推荐阅读