本文主要介紹了通過建構函式和領域服務建立實體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)