實現領域驅動設計 - 使用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
  • 使用原因: 在我們服務端調用第三方介面時,如:支付寶,微信支付,我們服務端需要模擬http請求並加上一些自己的邏輯響應給前端最終達到我們想要的效果 1.使用WebClient 引用命名空間 using System.Net; using System.Collections.Specialized; ...
  • WPF 實現帶蒙版的 MessageBox 消息提示框 WPF 實現帶蒙版的 MessageBox 消息提示框 作者:WPFDevelopersOrg 原文鏈接: https://github.com/WPFDevelopersOrg/WPFDevelopers.Minimal 框架使用大於等於.N ...
  • 一、JSON(JavaScript Object Notation)的簡介: ① JSON和XML類似,主要用於存儲和傳輸文本信息,但是和XML相比,JSON更小、更快、更易解析、更易編寫與閱讀。 ② C、Python、C++、Java、PHP、Go等編程語言都支持JSON。 二、JSON語法規則: ...
  • 1.避免Scoped模式註冊的服務變成Singleton模式 當提供一個生命周期模式為Singleton的服務實例時,如果發現該服務中還依賴生命周期模式為Scoped的服務實例(Scoped服務實例將被一個Singleton服務實例所引用),那麼這個被依賴的Scoped服務實例最終會成為一個Sing ...
  • 索引時資料庫提高數據查詢處理性能的一個非常關鍵的技術,索引的使用可以對性能產生上百倍甚至上千倍的影響。接下來,會介紹索引的基本原理、概念,並深入學習資料庫中所使用的索引結構和存儲方式,以及如何管理、維護索引等。 1.索引的基本概念 索引時用來快速查詢表記錄的一種存儲結構,一般使用索引有一下兩個方面: ...
  • django2 路由控制器 Route路由,是一種映射關係。路由是把客戶端請求的url路徑和用戶請求的應用程式,這裡意指django裡面的視圖進行綁定映射的一種關係。 請求路徑和視圖函數不是一一對應的關係 在django中所有的路由最終都被保存到一個叫urlpatterns的文件里,並且該文件必須在 ...
  • 1、我們的目標是獲取微博某博主的全部圖片、視頻 2、拿到網址後 我們先觀察 打開F12 隨著下滑我們發現載入出來了一個叫mymblog的東西,展開響應發現需要的東西就在裡面 3、重點來了!!! 通過觀察發現第二頁比第一頁多了參數since_id 而第二頁的since_id參數剛好在上一頁中能獲取到, ...
  • 一、實現原理 在Servlet3協議規範中,包含在JAR文件/META-INFO/resources/路徑下的資源可以直接訪問。 二、舉例說明 如下圖所示,是我新建的一個Spring Boot Starter項目:zimug-minitor-threadpool,用於實現可配置、可觀測的線程池。其中 ...
  • 精華筆記: static final常量:應用率高 必須聲明同時初始化 由類名打點來訪問,不能被改變 建議:常量所有字母都大寫,多個單詞用_分隔 編譯器在編譯時會將常量直接替換為具體的數,效率高 何時用:數據永遠不變,並且經常使用 抽象方法: 由abstract修飾 只有方法的定義,沒有具體的實現( ...
  • Python有一個for...else語法,它的寫法如下 for i in range(0,100): if i == 3: break else: print("Not found") 該語句表示:若for迴圈遍歷完畢,則執行else部分的語句。也就是說上述代碼不會有任何輸出,而下述代碼會輸出“N ...