基於ABP實現DDD--實體創建和更新
- 2022 年 7 月 24 日
- 筆記
本文主要介紹了通過構造函數和領域服務創建實體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實現領域驅動設計://url39.ctfile.com/f/2501739-616007877-f3e258?p=2096 (訪問密碼: 2096)

