基于ABP落地领域驱动设计-04.领域服务和应用服务的最佳实践和原则
圍繞DDD和ABP Framework兩個核心技術,后面還會陸續發布核心構件實現、綜合案例實現系列文章,敬請關注!?ABP Framework 研習社(QQ群:726299208)?ABP Framework 學習及實施DDD經驗分享;示例源碼、電子書共享,歡迎加入!
領域服務
領域服務實現領域邏輯,它:
?依賴于服務和倉儲。?需要多個聚合,以實現單個聚合無法處理的邏輯。
領域服務與領域對象一起使用,其方法可以獲取和返回實體、值對象、原始類型等。然而,它并不獲取/返回DTOs,DTOs屬于應用層。
示例:將問題分配給用戶
回想一下,我們之前是如何實現將問題分配給用戶的
public class Issue:AggregateRoot<Guid> {//..//問題關聯的用戶IDpublic Guid? AssignedUserId{get;private set;}//分配方法public async Task AssignToAsync(AppUser user,IUserIssueService userIssueService){var openIssueCount = await userIssueService.GetOpenIssueCountAsync(user.Id);if(openIssueCount >=3 ){throw new BusinessException("IssueTracking:CanNotOpenLockedIssue");}AssignedUserId=user.Id;}public void CleanAssignment(){AssignedUserId=null;} }現在,我們將邏輯遷移到領域服務中。首先,修改 Issue 類:
public class Issue:AggregateRoot<Guid> {//...public Guid? AssignedUserId{get;internal set;} }?在聚合中移除?AssignToAsync?方法(因為需要在對應的領域服務中實現該方法。)?將?AssignedUserId?屬性設置器從私有改為內部internal,以允許從領域服務中設置它。
接下來,創建一個領域服務?IssueManager?定義方法?AssignToAsync?將指定?Issue?分配給指定用戶。
public class IssueManager:DomainService {private readonly IRepository<Issue,Guid> _issueRepository;public IssueManager(IRepository<Issue,Guid> issueRepository){_issueRepository=issueRepository;}public async Task AssignToAsync(Issue issue,AppUser user){//獲取關聯用戶處于打開狀態問題的數量var openIssueCount=await _issueRepository.CountAsync(i=>i.AssingedUserId==user.Id && !i.IsClosed);//超過3個,則拋出異常if(openIssueCount>=3){throw new BusinessException("IssueTracking:ConcurrentOpenIssueLimit");}issue.AssignedUserId=user.Id;} }IssueManager在構造函數中注入需要的倉儲,用于查詢分配給用戶處于打開狀態的Issue。
建議使用Manager后綴命名來命名領域服務。
這種設計的唯一問題是:Issue.AssignedUserId現在是?public?,可以在任何外部類中設置。然而,它不應該是公共的,訪問范圍應該是程序集內部internal,只有在同一個程序集(IssueTracking.Domain)項目中才可以調用。
這個例子的解決方案就是如此,我們認為這很合理:
?領域層開發者在使用 IssueManager 時,已經熟知領域規則。?應用層開發者強制使用 IssueManager,因此無法直接修改實體。
以上我們展示了將問題分配給用戶的兩種實現方式,兩種方式權衡之下,我們更加推薦當業務邏輯需要與外部服務協同工作時,創建領域服務。
如果沒有一個充分的理由,我們認為沒有必要去為領域服務創建接口,比如:為?IssueManager?創建?IIssueManger?接口。
應用服務
應用服務是無狀態服務,實現應用程序用例。一個應用服務通常使用領域對象實現用例,獲取或返回數據傳輸對象DTOs,被展示層調用。
應用服務通用原則:
?實現特定用例的應用邏輯,不能在應用服務中實現領域邏輯(需要理清應用邏輯和領域邏輯二者的區別)。?應用服務方法不能返回實體,因為這樣會打破領域層的封裝性,始終只返回DTO。
示例:分配問題給用戶
using System; using System.Threading.Tasks; using IssueTracking.Users; using Microsoft.AspNetCore.Authorization; using Volo.Abp.Application.Services; using Volo.Abp.Domain.Repositories;namespace IssueTracking.Issues {public class IssueAppService :ApplicationService.IIssueAppService{private readonly IssueManager _issueManager;private readonly IRepository<Issue,Guid> _issueRepository;private readonly IRepository<AppUser,Guid> _userRepository;public IssueAppService(IssueManager issueManager,IRepository<Issue,Guid> issueRepository,IRepository<AppUser,Guid> userRepository){_issueManager=issueManager;_issueRepository=issueRepository;_userRepository=userRepository;}[Authorize]public async Task AssignAsync(IssueAssignDto input){var issue=await _issueRepository.GetAsync(input.IssueId);var user=await _userRepository.GetAsync(inpu.UserId);await _issueManager.AssignToAsync(issue,user);await _issueRepository.UpdateAsync(issue);//沒有對issue做任何修改,為什么要更新?在IssueManager中進行了狀態修改。}} }一個應用服務方法通常有三個步驟:
?從數據庫獲取關聯的領域對象?使用領域對象(領域服務、實體等)執行業務邏輯?在數據庫中更新實體(如果已修改)
當時使用EF Core時,最后的 Update 更新操作并不是必須的,應為有 狀態變更跟蹤。但是建議顯式調用,適配其他數據庫提供程序。
示例中?IssueAssignDto?是一個簡單的 DTO 類:
using System; namespace IssueTracking.Issues {public class IssueAssignDto{public Guid IssueId{get;set;}public Guid UserId{get;set;}} }學習幫助
圍繞DDD和ABP Framework兩個核心技術,后面還會陸續發布核心構件實現、綜合案例實現系列文章,敬請關注!
ABP Framework 研習社(QQ群:726299208)?專注 ABP Framework 學習及DDD實施經驗分享;示例源碼、電子書共享,歡迎加入!
總結
以上是生活随笔為你收集整理的基于ABP落地领域驱动设计-04.领域服务和应用服务的最佳实践和原则的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 基于ABP落地领域驱动设计-03.仓储和
- 下一篇: 基于ABP落地领域驱动设计-01.全景图