實現領域驅動設計 - 使用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
  • 1.部署歷史 猿友們好,作為初來實習的我,已經遭受社會的“毒打”,所以請容許我在下麵環節適當吐槽,3Q! 傳統部署 ​ 回顧以往在伺服器部署webapi項目(非獨立發佈),dotnet環境、守護進程兩個逃都逃不掉,正常情況下還得來個nginx代理。不僅僅這仨,可能牽扯到yum或npm。node等都要 ...
  • 隨著技術的進步,跨平臺開發已經成為了標配,在此大背景下,ASP.NET Core也應運而生。本文主要基於ASP.NET Core+Element+Sql Server開發一個校園圖書管理系統為例,簡述基於MVC三層架構開發的常見知識點,前一篇文章,已經簡單介紹瞭如何搭建開發框架,和登錄功能實現,本篇... ...
  • 這道題只要會自定義cmp恰當地進行排序,其他部分沒有什麼大問題。 上代碼: 1 #include<bits/stdc++.h> 2 using namespace std; 3 int n,s,h1,h2,cnt; 4 struct apple{ 5 int height,ns;//height為蘋 ...
  • 這篇文章主要描述RPC的路由策略,包括為什麼需要請求隔離,為什麼不在註冊中心中實現請求隔離以及不同粒度的路由策略。 ...
  • 簡介: 中介者模式,屬於行為型的設計模式。用一個中介對象來封裝一系列的對象交互。中介者是各對象不需要顯式地相互引用,從而使其耦合鬆散,而且可以獨立地改變他們之間的交互。 適用場景: 如果平行對象間的依賴複雜,可以使用中介者解耦。 優點: 符合迪米特法則,減少成員間的依賴。 缺點: 不適用於系統出現對 ...
  • 【前置內容】Spring 學習筆記全系列傳送門: Spring學習筆記 - 第一章 - IoC(控制反轉)、IoC容器、Bean的實例化與生命周期、DI(依賴註入) Spring學習筆記 - 第二章 - 註解開發、配置管理第三方Bean、註解管理第三方Bean、Spring 整合 MyBatis 和 ...
  • 簡介: 享元模式,屬於結構型的設計模式。運用共用技術有效地支持大量細粒度的對象。 適用場景: 具有相同抽象但是細節不同的場景中。 優點: 把公共的部分分離為抽象,細節依賴於抽象,符合依賴倒轉原則。 缺點: 增加複雜性。 代碼: //用戶類 class User { private $name; fu ...
  • 這次設計一個通用的多位元組SPI介面模塊,特點如下: 可以設置為1-128位元組的SPI通信模塊 可以修改CPOL、CPHA來進行不同的通信模式 可以設置輸出的時鐘 狀態轉移圖和思路與多位元組串口發送模塊一樣,這裡就不給出了,具體可看該隨筆。 一、模塊代碼 1、需要的模塊 通用8位SPI介面模塊 `tim ...
  • AOP-03 7.AOP-切入表達式 7.1切入表達式的具體使用 1.切入表達式的作用: 通過表達式的方式定義一個或多個具體的連接點。 2.語法細節: (1)切入表達式的語法格式: execution([許可權修飾符] [返回值類型] [簡單類名/全類名] [方法名]([參數列表]) 若目標類、介面與 ...
  • 測試一、虛繼承與繼承的區別 1.1 單個繼承,不帶虛函數 1>class B size(8): 1> + 1> 0 | + (base class A) 1> 0 | | _ia //4B 1> | + 1> 4 | _ib //4B 有兩個int類型數據成員,占8B,基類邏輯存在前面 1.2、單個 ...