實現領域驅動設計 - 使用ABP框架 - 創建實體

来源:https://www.cnblogs.com/broadm/archive/2022/06/24/16407775.html
-Advertisement-
Play Games

用例演示 - 創建實體 本節將演示一些示例用例並討論可選場景。 創建實體 從實體/聚合根類創建對象是實體生命周期的第一步。聚合/聚合根規則和最佳實踐部分 建議為Entity類創建一個主構造函數,以保證創建一個有效的實體。因此,無論何時我們需要創建實體的實例,我們都應該使用那個構造函數 參見下麵的問題 ...


用例演示 - 創建實體

本節將演示一些示例用例並討論可選場景。

創建實體

從實體/聚合根類創建對象是實體生命周期的第一步。聚合/聚合根規則和最佳實踐部分 建議為Entity類創建一個主構造函數,以保證創建一個有效的實體。因此,無論何時我們需要創建實體的實例,我們都應該使用那個構造函數

參見下麵的問題聚合根類:

public class Issue : AggregateRoot<Guid>
{
    public Guid RepositoryId { get; private set; }
    public string Title { get; private set; }
    public string Text { get; set; }
    public Guid? AssignedUserId { get; private set; }
    
    public Issue(
        Guid id, 
        Guid repositoryId,
        string title,
        string text = null
    ) : base(id)
    {
        RepositoryId = repositoryId;
        Title = Check.NotNullOrWhiteSpace(title, nameof(title));
        Text = text; // 允許空值
    }

    private Issue() { //為ORM保留的空構造函數 }

    public void SetTitle(string title)
    {
        Title = Check.NotNullOrWhiteSpace(title, nameof(title));
    }
}
  • 該類保證通過其構造函數創建有效的實體

  • 如果你需要更改標題,你需要使用 SetTitle 方法保證標題在一個有效狀態

  • 如果您想將這個問題分配給用戶,您需要使用 IssueManager (它在分配之前實現了一些業務規則, 請參閱我之前關於 領域服務 的文章)。

  • Text 屬性有一個公共setter,因為它也接受null值,並且這個示例沒有任何驗證規則。它在構造函數中也是可選的

讓我們看看用於創建問題的Application Service方法:

public class IssueAppService : ApplicationService, IIssueAppService
{
    //省略了Repository和DomainService的依賴註入

    [Authorize]
    public async Task<IssueDto> CreateAsync(IssueCreationDto input)
    {
        //創建一個有效的問題實體
        var issue = new Issue(
            GuidGenerator.Create(),
            input.RepositoryId,
            input.Title,
            input.Text
        );

        //如果傳入了被分配人,則把該問題法分配給這個用戶
        if(input.AssignedUserId.HasValue)
        {
            var user = await _userRepository.GetAsync(input.AssignedUserId.Value);
            await _issueManager.AssignToAsync(issue, user);
        }

        // 把問題實體保存到資料庫
        await _issueRepository.InsertAsync(issue);

        //返回表示這個新的問題的DTO
        return ObjectMapper.Map<Issue, IssueDto>(issue);
    }
}

CreateAsync 方法:

  • 使用 Issue 構造函數創建有效的問題。它使用 IGuidGenerator 服務傳遞Id。這裡不使用自動對象映射
  • 如果客戶端希望在對象創建時將這個問題分配給用戶,它會使用IssueManager 來完成,允許 IssueManager 在分配之前執行必要的檢查。
  • 保存實體到資料庫
  • 最後使用 IObjectMapper 返回一個 IssueDto ,該 IssueDto 是通過映射從新的 Issue 實體自動創建的

使用領域規則創建實體

上述示例, Issue 沒有關於實體創建的業務規則,除了在構造函數中進行一些形式的驗證。但是,在某些情況下,實體創建應該檢查一些額外的業務規則

例如,假設您不希望在完全相同的標題已經存在問題的情況下創建問題。在哪裡實現這個規則? 在 Application Service 中實現此規則是不合適的,因為它是一個應該始終檢查的 核心業務(領域)規則

該規則應該在 領域服務 (在本例中是 IssueManager )中實現。因此,我們需要強制應用層總是使用 IssueManager 來創建一個新的 Issue

首先,我們可以將 Issue 構造函數設置為 internal ,而不是 public:

public class Issue : AggregateRoot<Guid>
{
    internal Issue(
        Guid id, 
        Guid repositoryId,
        string title,
        string text = null
    ) : base(id)
    {
        //...
    }
}

這阻止了應用服務直接使用構造函數,所以它們將使用 IssueManager 。然後我們可以在 IssueManager 中添加一個 CreateAsync 方法:

public class IssueManager : DomainService
{
    //省略了依賴註入

    public async Task<IssueDto> CreateAsync(
        Guid repositoryId,
        string title,
        string text = null
    )
    {
        //如果存在相同標題的問題,直接拋錯
        if(await _issueRepository.AnyAsync(i => i.Title == title))
        {
            throw new BusinessException("IssueTracking:IssueWithSameTitleExists");
        }

        //創建一個有效的問題實體
        return new Issue(
            GuidGenerator.Create(),
            repositoryId,
            title,
            text
        );
    }
}
  • CreateAsync 方法檢查相同標題是否已經存在問題,併在這種情況下拋出業務異常
  • 如果沒有重覆,則創建並返回一個新的Issue

為了使用上述方法,IssueAppService 被修改如下:

public class IssueAppService : ApplicationService, IIssueAppService
{
    //省略了依賴註入

    public async Task<IssueDto> CreateAsync(IssueCreationDto input)
    {
        //★修改為通過領域服務創建有效的問題實體, 而不是直接new
        var issue = await _issueManager.CreateAsync(
            GuidGenerator.Create(),
            input.RepositoryId,
            input.Title,
            input.Text
        );

        //如果傳入了被分配人,則把該問題法分配給這個用戶
        if(input.AssignedUserId.HasValue)
        {
            var user = await _userRepository.GetAsync(input.AssignedUserId.Value);
            await _issueManager.AssignToAsync(issue, user);
        }

        // 把問題實體保存到資料庫
        await _issueRepository.InsertAsync(issue);

        //返回表示這個新的問題的DTO
        return ObjectMapper.Map<Issue, IssueDto>(issue);
    }
}

討論:為什麼問題沒有在 IssueManager 中保存到資料庫?

你可能會問 “為什麼 IssueManager 不把問題保存到資料庫中?” 我們認為這是應用服務的責任

因為,在保存問題對象之前,應用程式服務可能需要對其進行額外的更改/操作。如果領域服務保存它,則保存操作將重覆

  • 兩次資料庫往返會導致性能損失
  • 需要顯式的資料庫事務來包含這兩個操作
  • 如果由於業務規則的原因,其他操作取消了實體創建,則應該在資料庫中回滾事務

當你檢查 IssueAppService 時,你會看到在 IssueManager.CreateAsync 中不保存 Issue 到資料庫的好處。否則,我們將需要執行一次插入(在 IssueManager 中)和一次更新(在分配問題之後)

討論:為什麼不在應用程式服務中實現重覆標題檢查?

我們可以簡單地說 “因為它是一個核心領域邏輯,應該在領域層中實現”。然而,這帶來了一個新的問題: “您如何判斷它是核心領域邏輯,而不是應用程式邏輯?” (稍後我們將詳細討論其中的差異)

對於這個例子,一個簡單的問題可以幫助我們做出決定: “如果我們有另一種方法(用例)來創建一個問題,我們是否仍然應用相同的規則?” 你可能會想 “為什麼我們有第二種製造問題的方式?” 然而,在現實生活中,你有:

  • 應用程式的最終用戶可能會在應用程式的標準UI中創建問題(比如在github的網頁端創建問題)
  • 您可能有第二個後臺應用程式,由您自己的員工使用,您可能希望提供一種創建問題的方法(在本例中可能使用不同的授權規則)
  • 您可能有一個對第三方客戶端開放的HTTP API,他們會創建問題。
  • 您可能有一個 background worker service,如果它檢測到一些故障,它會做一些事情並創建問題。這樣,它將在沒有任何用戶交互的情況下(可能沒有任何標準的授權檢查)創建問題。
  • 您甚至可以在UI上設置一個按鈕,將某些內容 (例如,討論) 轉換為問題

綜上所述,不同的應用程式始終遵循這樣的規則:新問題的標題不能與任何現有問題的標題相同!他們與應用層無關! 這就是為什麼該邏輯是核心領域邏輯,應該位於領域層中,而不應該在應用程式服務中實現為重覆的代碼。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.路徑處理 1.找模塊:sys.path import sys print(sys.path) - 1.理解 - 1.是python去查找包或模塊 - 2.項目開始根目錄,python內置的目錄 - 3.雖然說python的安裝目錄下也可以存放我們寫的模塊,但是不建議(太多了,不大好找) - 4. ...
  • 本篇內容將在上一篇已有的內容基礎上,進一步的聊一下項目中使用JPA的一些高階複雜場景的實踐指導,覆蓋了主要核心的JPA使用場景,可以讓你在需求開發的時候對JPA的使用更加的游刃有餘。 ...
  • 前言 Steam是由美國電子游戲商Valve於2003年9月12日推出的數字發行平臺,被認為是電腦游戲界最大的數位發行平臺之一,Steam平臺是全球最大的綜合性數字發行平臺之一。玩家可以在該平臺購買、下載、討論、上傳和分享游戲和軟體。 而每周的steam會開啟了一輪特惠,可以讓游戲打折,而玩家就會 ...
  • Hi,大家好,我是Mic 一個工作5年的粉絲找到我。 他說: “Mic老師,你要是能回答出這個問題,我就佩服你” 我當場就懵了,現在打賭都這麼隨意了嗎? 我問他問題是什麼,他說“Kafka如何避免重覆消費的問題!” 下麵看看普通人和高手的回答! 普通人: Kafka怎麼避免重覆消費就是我們可以通過 ...
  • 前言 今天給大家分享一下我自己寫的筆記,純純的都是乾貨,關於字好像也能看。這是我學python整理出來的一些資料,希望對大家 有用。想要更多的資料那就的給一個關註了… python學習交流Q群:903971231### #導入Counter from collections import Count ...
  • Zookeeper3.7源碼剖析 能力目標 掌握Zookeeper中Session的管理機制 能基於Client進行Debug測試Session創建/刷新操作 能搭建Zookeeper集群源碼配置 掌握集群環境下Leader選舉啟動過程 能說出Zookeeper選舉過程中的概念 能說出Zookeep ...
  • 表弟大學快畢業了,學了一個學期Python居然還不會寫學生管理系統,真的給我丟臉啊,教他又不肯學,還讓我直接給他寫,我真想兩巴掌上去,最終還是寫了給他,誰讓他是我表弟呢,關鍵時候還是得幫他一把! 寫完了放在那也是放著,所以今天分享給大家吧! 話不多說,咱們直接開始吧! 代碼解析 一、登錄頁面 1、定 ...
  • 領域邏輯 & 應用邏輯 如前所述,領域驅動設計中的業務邏輯分為兩部分(層):領域邏輯和應用邏輯: 領域邏輯由系統的核心領域規則組成,應用邏輯實現應用特定的用例 雖然定義很明確,但實現起來可能並不容易。您可能無法決定哪些代碼應該位於應用程式層,哪些代碼應該位於領域層。本節試圖解釋其中的差異 多個應用程 ...
一周排行
    -Advertisement-
    Play Games
  • Dapr Outbox 是1.12中的功能。 本文只介紹Dapr Outbox 執行流程,Dapr Outbox基本用法請閱讀官方文檔 。本文中appID=order-processor,topic=orders 本文前提知識:熟悉Dapr狀態管理、Dapr發佈訂閱和Outbox 模式。 Outbo ...
  • 引言 在前幾章我們深度講解了單元測試和集成測試的基礎知識,這一章我們來講解一下代碼覆蓋率,代碼覆蓋率是單元測試運行的度量值,覆蓋率通常以百分比表示,用於衡量代碼被測試覆蓋的程度,幫助開發人員評估測試用例的質量和代碼的健壯性。常見的覆蓋率包括語句覆蓋率(Line Coverage)、分支覆蓋率(Bra ...
  • 前言 本文介紹瞭如何使用S7.NET庫實現對西門子PLC DB塊數據的讀寫,記錄了使用電腦模擬,模擬PLC,自至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1.Windows環境下鏈路層網路訪問的行業標準工具(WinPcap_4_1_3.exe)下載鏈接:http ...
  • 從依賴倒置原則(Dependency Inversion Principle, DIP)到控制反轉(Inversion of Control, IoC)再到依賴註入(Dependency Injection, DI)的演進過程,我們可以理解為一種逐步抽象和解耦的設計思想。這種思想在C#等面向對象的編 ...
  • 關於Python中的私有屬性和私有方法 Python對於類的成員沒有嚴格的訪問控制限制,這與其他面相對對象語言有區別。關於私有屬性和私有方法,有如下要點: 1、通常我們約定,兩個下劃線開頭的屬性是私有的(private)。其他為公共的(public); 2、類內部可以訪問私有屬性(方法); 3、類外 ...
  • C++ 訪問說明符 訪問說明符是 C++ 中控制類成員(屬性和方法)可訪問性的關鍵字。它們用於封裝類數據並保護其免受意外修改或濫用。 三種訪問說明符: public:允許從類外部的任何地方訪問成員。 private:僅允許在類內部訪問成員。 protected:允許在類內部及其派生類中訪問成員。 示 ...
  • 寫這個隨筆說一下C++的static_cast和dynamic_cast用在子類與父類的指針轉換時的一些事宜。首先,【static_cast,dynamic_cast】【父類指針,子類指針】,兩兩一組,共有4種組合:用 static_cast 父類轉子類、用 static_cast 子類轉父類、使用 ...
  • /******************************************************************************************************** * * * 設計雙向鏈表的介面 * * * * Copyright (c) 2023-2 ...
  • 相信接觸過spring做開發的小伙伴們一定使用過@ComponentScan註解 @ComponentScan("com.wangm.lifecycle") public class AppConfig { } @ComponentScan指定basePackage,將包下的類按照一定規則註冊成Be ...
  • 操作系統 :CentOS 7.6_x64 opensips版本: 2.4.9 python版本:2.7.5 python作為腳本語言,使用起來很方便,查了下opensips的文檔,支持使用python腳本寫邏輯代碼。今天整理下CentOS7環境下opensips2.4.9的python模塊筆記及使用 ...