1. 程式人生 > 其它 >基於ABP落地領域驅動設計-06.正確區分領域邏輯和應用邏輯

基於ABP落地領域驅動設計-06.正確區分領域邏輯和應用邏輯

目錄

系列文章

圍繞DDDABP Framework兩個核心技術,後面還會陸續釋出核心構件實現綜合案例實現系列文章,敬請關注!
ABP Framework 研習社(QQ群:726299208)


ABP Framework 學習及實施DDD經驗分享;示例原始碼、電子書共享,歡迎加入!

領域邏輯和應用邏輯

正如前面提到的,領域驅動設計中的業務邏輯拆分為兩部分:領域邏輯應用邏輯

領域邏輯由系統的核心領域規則組成,而應用程式邏輯實現特定於應用程式的用例。

雖然定義很清楚,但實現可能並不容易,常常無法決定哪些程式碼應該放在應用層,哪些程式碼應該放在領域層。本節試圖解釋這些差異。

多應用層

當你的系統很大時,DDD有助於處理複雜性問題。特別是,在一個領域中開發多個應用,那麼領域邏輯與應用邏輯的分離就變得更加重要。

假設你正在構建一個有多個應用程式的系統:

  • 一個Web應用程式,使用 ASP .NET Core MVC,展示產品給使用者。瀏覽產品,不需要進行身份驗證;只有當用戶執行某些操作時,比如向購物車中新增產品,才會要求登陸。
  • 一個後臺管理應用程式,使用Angular UI+ REST APIs構建。此應用由公司辦公人員做系統管理,比如:編輯產品描述。
  • 一個移動應用程式,和 Web應用程式 相比UI更加簡單,通過REST APIs或其他技術(如:TCP/Socket)與伺服器通訊。

每一個應用都需要解決不同的需求,實現不同用例(應用服務方法),不同DTO,不同驗證和授權規則等等。

將所有這些邏輯混合到一個應用層中,將使你的服務包含太多的判斷條件和複雜的業務邏輯,使程式碼更難開發、維護和測試,並導致潛在的Bug。

如果你有單個領域關聯多個應用程式:

  • 為每個應用程式或客戶端建立單獨的應用層,在這些單獨層中實現特定於應用的業務邏輯。
  • 使用單個領域層共享核心領域邏輯

這樣的設計使得區分領域邏輯應用邏輯變得更加重要。

為了更清楚地實現,可以為每種應用程式型別建立不同的專案(.csproj)。

例如:

  • 後臺管理應用建立 IssueTracker.Admin.ApplicationIssueTracker.Admin.Application.Contracts 專案
  • WEB應用建立 IssueTracker.Public.ApplicationIssueTracker.Public.Application.Contracts
  • 移動應用建立 IssueTracker.Mobile.ApplicationIssueTracker.Mobile.Application.Contracts

示例:正確區分應用邏輯和領域邏輯

本節包含一些應用服務和領域服務示例,討論如何決定在這些服務中放置業務邏輯。

示例:在領域服務中建立 Organization (組織)

public class OrganizationManager:DomainService
{
    private readonly IRepository<Organization> _organizationRepository;
    private readonly ICurrentUser _currentUser;
    private readonly IAuthorizationService _authorizationService;
    private readonly IEmailSender _emailSender;

    public OrganizationManager(
        IRepository<Organization> organizationRepository,
        ICurrentUser currentUser,
        IAuthorizationService authorizationService,
        IEmailSender emailSender
    )
    {
        _organizationRepository=organizationRepository;
        _currentUser=currentUser;
        _authorizationService=authorizationService;
        _emailSender=emailSender;
    }
    //建立組織
    public async Task<Organization> CreateAsync(string name)
    {
        //檢測是否存在同名組織,存在則丟擲異常。
        if(await _organizationRepository.AnyAsync(x=>x.Name==name))
        {
            throw new BusinessException("IssueTracking:DuplicateOrganizationName");
        }
        //檢測是否擁有建立許可權
        await _authorizationService.CheckAsync("OrganizationCreationPermission");
        //記錄日誌
        Logger.LogDebug($"Creating organization {name} by {_currentUser.UserName}");
        //建立組織例項
        var organization = new Organization();
        //傳送提醒郵件
        await _emailSender.SendAsync(
            "[email protected]",
            "新組織",
            "新組織名稱:"+name
        );
        //返回組織例項
        return organization;
    }
}

讓我們一步一步來分析 CreateAsync 方法中的程式碼是否都應該放在領域服務中:

  • 正確:組織名重複檢測,存在重複名稱則丟擲異常。該檢測與核心領域規則相關,不允許重名。
  • 錯誤:領域服務不應該進行許可權驗證。許可權驗證應該放在應用層。
  • 錯誤:記錄日誌包含當前使用者的使用者名稱。領域服務不應該依賴當前使用者,當前使用者(Session)應該是展示層或應用層中的相關概念。
  • 錯誤:建立新組織傳送郵件,我們仍然認為這是業務邏輯。可以能會根據用例來建立不同型別郵件。

示例:在應用層建立新組織

public class OrganizationAppService:ApplicationService
{
    private readonly OrganizationManager _organizationManager;
    private readonly IPaymentService _paymentService;
    private readonly IEmailSender _emailSender;

    public OrganizaitonAppService(
        OrganizationManager organizationManager,
        IPaymentService paymentService,
        IEmailSender emailSender
    )
    {
        _organizationManager=organizationManager;
        _paymentService=paymentService;
        _emailSender=emailSender;
    }
    [UnitOfWork]
    [Authorize("OrganizationCreationPermission")]
    public async Task<Organization> CreateAsync(CreateOrganizationDto input)
    {
        //支付組織費用
        await _paymentService.ChargeAsync(
            CurrentUser.Id,
            GetOrganizationPrice()
        );
        //建立組織例項
        var organization = await _organizationManager.CreateAsync(input.Name);
        //儲存組織到資料庫
        await _organizationManager.InsertAsync(organization);
        //傳送提醒郵件
        await _emailSender.SendAsync(
            "[email protected]",
            "新組織",
            "新組織名稱:"+name
        );
        //返回例項
        return organization;
    }
    private double GetOrganizationPrice()
    {
        return 42;//Gets form somewhere...
    }
}

讓我們看看 CreateAsync 方法,一步一步討論其中的程式碼是否應該放在應用服務:

  • 正確:應用服務方法應該是工作單元,ABP框架工作單元系統自動實現,可以不用新增[UnitOfWork]特性。
  • 正確:許可權驗證應該放在應用層,可以使用 [Authorize] 特性。
  • 正確:在我們的業務邏輯中建立組織是付費服務,當前操作呼叫基礎設施服務進行支付操作。
  • 正確:應用服務方法負責儲存變更到資料庫。
  • 正確:給系統管理員傳送郵件通知。
  • 錯誤:不能返回實體,應該返回DTO。

討論:為什麼我們不應該將支付邏輯放在領域服務中?

你可能想知道為什麼付款程式碼不在 OrganizationManager 裡面。這是一件很重要的事情,我們絕不希望付款出錯。

然而,業務的重要性並不意味著要將其視為核心業務邏輯。我們可能有其他的支付用例,在這些用例中,我們不收取費用來建立一個新的組織。

例如:

  • 後臺辦公系統中的管理員使用者可以建立新組織,不用考慮支付。
  • 系統資料匯入、整合、同步,也可能需要在沒有任何支付操作的情況下,建立組織。

如您所見,支付不是建立一個有效組織的必要操作。它是一個特定於用例的應用邏輯。

示例:CRUD操作

public class IssueAppService
{
    private readonly IssueManager _issueManager;
    public IssueAppService(IssueManager issueManager)
    {
        _issueManager=issueManager;
    }
    public async Task<IssueDto> GetAsync(Guid id)
    {
        return await _issueManager.GetAsync(id);
    }
    public async Task CreateAsync(IssueCreationDto input)
    {
        await _issueManager.CreateAsync(input);
    }
    public async Task UpdateAsync(UpdateIssueDto input)
    {
        await _issueManager.UpdateAsync(input);
    }
    public async Task DeleteAsync(Guid id)
    {
        await _issueManager.DeleteAsync(id);
    }
}

應用服務並沒有做任何事情,而是委託給領域服務來處理。只接收DTO引數,並傳遞給 IssueManger

  • 不要建立只實現簡單 CRUD 操作的領域服務方法,而不帶任何領域邏輯。
  • 不要傳遞 DTO 給領域服務,或領域服務方法返回 DTO。

應用服務可以直接使用倉儲,實現查詢、建立、更新或刪除資料,除非執行這些操作時需要處理領域邏輯,這種情況下,建立領域服務方法,但只針對那些真正需要的方法。

不要因為將來可能會需要這些CRUD領域服務方法,就去提前建立這些方法! 當需要時再去建立,並重構現有的程式碼。由於抽象了應用層,重構領域層不會影響到UI層和其他客戶端。

學習幫助

圍繞DDDABP Framework兩個核心技術,後面還會陸續釋出核心構件實現綜合案例實現系列文章,敬請關注!

ABP Framework 研習社(QQ群:726299208)
專注 ABP Framework 學習及DDD實施經驗分享;示例原始碼、電子書共享,歡迎加入!