<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      實現領域驅動設計 - 使用ABP框架 - 聚合

      這是本指南的關鍵部分。我們將通過實例介紹和解釋一些明確的規則。在實現領域驅動設計時,您可以遵循這些規則并將其應用到您的解決方案中

      領域案例

      這些例子將使用GitHub中使用的一些概念,比如Issue, Repository, Label和User,你已經很熟悉了。下圖顯示了一些聚合、聚合根、實體、值對象以及它們之間的關系

      image

      問題聚合由一個問題聚合根組成,其中包含 Comment 和 IssueLabel 集合。其他聚合顯示為簡單的,因為我們將重點關注問題聚合

      聚合

      如前所述,聚合 是一個對象集群,它通過聚合根對象把(實體和值對象)綁定在一起。本節將介紹與聚合相關的原則和規則

      我們將聚合根和子集合實體都稱為實體,除非我們顯式地編寫聚合根實體或子集合實體
      

      聚合 / 聚合根 原則

      業務規則

      實體負責實現與其自身屬性相關的業務規則。
      聚合根實體也負責它們的子集合實體。

      聚合應該通過實現領域規則和約束來保持自身的完整性和有效性。這意味著,與dto不同,實體有實現某些業務邏輯的方法。實際上,我們應該盡可能在實體中實現業務規則

      單個單元

      聚合被檢索并保存為單個單元,包含所有子集合和屬性。例如,如果您想對某個問題添加注釋,您需要這樣做

      • 從數據庫中獲取 Issue,包括所有的子集合( Comments 和 issuelabel )
      • 使用 Issue 類上的方法來添加一個新的注釋,比如 Issue. AddComment();
      • 將Issue(包括所有子集合)作為單個數據庫操作保存到數據庫(更新)

      對于曾經使用 EF Core & Relational Databases 的開發人員來說,這可能看起來很奇怪。獲得問題的所有細節似乎是不必要的和低效的。為什么我們不直接對數據庫執行 SQL Insert 命令?

      答案是,我們應該實現業務規則,并在代碼中保持數據的一致性和完整性。如果我們有像 “用戶不能評論鎖定的問題” 這樣的業務規則,如果不從數據庫中檢索它,我們如何檢查問題的鎖狀態? 因此,只有當相關對象在應用程序代碼中可用時,我們才能執行業務規則

      另一方面,MongoDB 開發人員會發現這個規則非常自然。在MongoDB中,一個聚合對象(帶有子集合)保存在數據庫中的單個集合中(而它分布在關系數據庫中的幾個表中)。因此,當您獲得一個聚合時,所有子集合都已經作為查詢的一部分被檢索,而不需要任何額外的配置。

      ABP框架有助于在應用程序中實現這一原則

      示例:向問題添加評論

      image

      _issueRepository.GetAsync 方法在默認情況下將所有詳細信息(子集合)作為 Issue 的單個單元檢索。雖然這對于MongoDB來說是開箱即用的,但你需要為EF Core配置聚合細節。但是,一旦配置,Repository 就會自動處理它。_issueRepository.GetAsync 方法有一個可選參數 includeDetails,當你需要它時,你可以傳遞 false 來禁用此行為

      關于配置和備選方案,請參閱 EF Core文檔 的加載相關實體一節。

      Issue.AddComment 方法需要兩個參數, 用戶ID 和 評論內容,實現必要的業務規則,并將評論添加到 Issue 的 Comments 集合

      最終,我們使用 _issueRepository.UpdateAsync 把修改保存到數據庫

      EF Core 有變化跟蹤功能。你實際上不需要調用 _issueRepository.UpdateAsync。由于ABP的工作單元系統在方法結束時自動調用 DbContext.SaveChanges(),它將被自動保存。但是,對于MongoDB,您需要顯式地更新更改后的實體

      因此,如果你想編寫獨立于數據庫提供程序的代碼,你應該總是為更改的實體調用 UpdateAsync 方法

      事務邊界

      聚合通常被視為事務邊界。如果用例處理單個聚合,讀取并保存為單個單元,對聚合對象所做的所有更改將作為原子操作一起保存,您不需要顯式的數據庫事務

      然而,在現實生活中,您可能需要在一個用例中更改多個聚合實例,并且需要使用數據庫事務來確保原子更新和數據一致性。正因為如此,ABP框架為用例(應用服務方法邊界)使用顯式的數據庫事務。有關更多信息,請參閱 工作單元 文檔

      聚合/聚合根規則和最佳實踐

      僅通過ID引用其他聚合

      第一條規則說一個聚合只能通過Id引用其他聚合。這意味著不能向其他聚合添加導航屬性

      • 該規則使實現可序列化原則成為可能
      • 它還可以防止不同的聚合相互操作,以及將聚合的業務邏輯泄露給另一個聚合

      在下面的例子中,你可以看到兩個聚合根,GitRepository 和 Issue

      image

      • GitRepository 不應該包含 Issue 集合,因為它們是不同的聚合
      • Issue 不應該有 GitRepository 的導航屬性,因為它是一個不同的聚合
      • Issue 可以有 RepositoryId (作為一個 Guid).

      所以,當你有一個 Issue,并且需要有與這個 Issue 相關的GitRepository 時,你需要通過 RepositoryId 顯式地從數據庫中查詢它

      對于 EF Core 和 關系型數據庫

      在MongoDB中,自然不適合有這樣的導航屬性/集合。如果這樣做,您將在源聚合的數據庫集合中找到目標聚合對象的副本,因為它在保存時被序列化為JSON

      但是,EF Core和關系數據庫開發人員可能會發現這個限制規則是不必要的,因為EF Core可以在數據庫讀寫時處理它。我們認為這是一個重要的規則,它有助于降低域的復雜性,防止潛在的問題,我們強烈建議實現該規則。但是,如果您認為忽略此規則是可行的,請參閱上面關于數據庫無關性原則的討論一節。

      保持聚合小巧

      一個好的做法是保持聚合的簡單和小巧。這是因為聚合將被加載并保存為單個單元,而讀取/寫入大對象有性能問題。請看下面的例子:

      image

      角色聚合具有一組 UserRole 值對象,用于跟蹤為該角色分配的用戶。請注意,UserRole 不是另一個聚合,對于“僅按Id引用其他聚合”規則也沒有問題。然而,這是一個現實問題。在現實場景中,一個角色可能被分配給數千(甚至數百萬)個用戶,當您從數據庫查詢 role 時,加載數千個條目是一個顯著的性能問題(記住:聚合是由它們的子集合作為單個單元加載的)

      另一方面,User 可能有這樣的角色集合,因為用戶在實際中沒有太多角色,當您使用用戶聚合時,擁有一個角色列表可能很有用

      如果仔細考慮,在使用非關系型數據庫(如MongoDB)時,Role 和 User 都有關系列表,這還有一個問題。在這種情況下,相同的信息在不同的集合中重復,很難維護數據的一致性(每當向 User.Roles 添加項時, 你也需要把它添加到 Role.Users 中)

      因此,請根據以下考慮因素確定聚合邊界和大小

      • 對象被一起使用
      • 查詢(加載/保存)性能和內存消耗
      • 數據的完整性、有效性和一致性

      實際上:

      • 大多數聚合根不會有子集合
      • 一個子集合中最多不應該有超過100-150個條目。如果您認為集合可能有更多項,那么不要將集合定義為聚合的一部分,而要考慮為集合內的實體提取另一個聚合根

      聚合根/實體上的主鍵

      • 聚合根的標識符通常只有一個Id屬性(Primark Key: PK)。我們更喜歡Guid作為聚合根實體的PK(參見 Guid生成文檔 了解原因)。
      • 聚合中的實體(不是聚合根)可以使用復合主鍵

      例如,請參閱下面的聚合根和實體:

      image

      • Organization 有一個 Guid 標識符 (Id)
      • OrganizationUser 是 Organization 的子集合,并且具有由OrganizationId 和 UserId 組成的復合主鍵

      這并不意味著子集合實體應該總是具有復合主鍵。它們可能在需要時具有單個Id屬性

      復合主鍵實際上是一個關系數據庫的概念,因為子集合實體有自己的表,需要主鍵。另一方面,例如,在MongoDB中,你根本不需要為子集合實體定義主鍵,因為它們存儲為聚合根的一部分

      聚合根/實體的構造函數

      構造函數位于實體生命周期開始的位置。一個設計良好的構造函數有以下幾點職責:

      • 把必需的實體屬性作為參數,以創建有效實體。應該強制只傳遞必需的參數,把非必需的屬性作為可選參數
      • 檢查參數的有效性
      • 初始化子集合

      示例: Issue(聚合根) 的構造函數

      image

      • Issue 類正確地通過在其構造函數參數中獲取必填屬性,來強制創建有效的實體。
      • 構造函數驗證了輸入, 如果標題為空, Check.NotNullOrWhiteSpace 拋出了異常
      • 它初始化了子集合,所以在創建 Issue 完成后, 你使用 Labels 集合時,不會導致空引用異常
      • 構造函數也接受id并傳遞給基類。我們不會在構造函數中生成Guid,應該將這個職責委托給另一個服務(參見 Guid生成 )。
      • 私有空構造函數是 ORM 必需的。我們將它設置為私有,以防止在我們自己的代碼中意外地使用它

      查看 實體 文檔了解更多關于使用ABP框架創建實體的信息

      實體中的屬性訪問器和方法

      上面的例子可能對你來說很奇怪! 例如,我們強制在構造函數中傳遞一個非空Title。但是,開發人員可以將Title屬性設置為空,不需要任何控制。這是因為上面的示例代碼只關注構造函數

      如果我們用 public set 聲明所有屬性(就像上面的示例Issue類),我們就不能強制實體在其生命周期中保持有效性和完整性。所以

      • 當需要在設置屬性時執行任何邏輯時,請為該屬性使用私有setter
      • 定義公共方法來操作這些屬性

      示例:以受控方式更改屬性的方法

      image

      • RepositoryId 的 setter 被設置為私有,并且在創建 Issue 后沒有辦法更改它,因為這是我們在這個領域中想要的:一個 Issue 不能被移動到另一個倉庫
      • Title 的 setter 是私有的,如果你想在以后以一種可控的方式改變它, 所以 SetTitle方法被創建了
      • TextAssignedUserId 是共有的 setter, 因為對他們沒有限制。它們可以是null或任何其他值。我們認為沒有必要定義單獨的方法來設置它們。如果以后需要,我們可以添加方法并將setter設為私有。在域層中,中斷更改不是問題,因為域層是一個內部項目,它不向客戶端公開
      • IsClosedIssueCloseReason 是一對屬性,定義了關閉和重新打開方法以同時更改它們。這樣,我們就可以確保在關閉問題時必須填寫理由

      實體中的業務邏輯和異常

      在實體中實現驗證和業務邏輯時,經常需要處理異常情況。在這些情況下

      • 創建領域特定的異常
      • 當需要時在實體的方法中拋出這些異常

      示例:

      image

      這里有2個業務規則:

      • 無法重新打開鎖定的問題。
      • 您無法鎖定未關閉的問題

      在這些情況下,Issue 類拋出一個 IssueStateException 來強制執行業務規則, 拋出此類異常有兩個潛在問題:

      • 在出現這種異常的情況下,最終用戶是否應該看到異常(錯誤)消息?如果是,如何本地化異常消息?你不能使用本地化系統,因為你不能在實體中注入和使用 IStringLocalizer
      • 對于一個web應用程序或HTTP API, HTTP狀態代碼應該返回給客戶端什么?

      ABP的 異常處理 系統解決了這些以及類似的問題

      示例:用代碼拋出業務異常

      image

      • IssueStateException類繼承了BusinessException類。對于繼承自BusinessException的異常,ABP默認返回403(禁止的)HTTP狀態碼(而不是500 -內部服務器錯誤)
      • 該 code 在本地化資源文件中用作查找本地化消息的鍵

      現在,我們可以更改 ReOpen 方法,如下所示:

      image

      使用常量而不是魔法字符串。
      然后添加一個本地化資源條目,如下所示:

      "IssueTracking:CanNotOpenLockedIssue" : "Can not open a locked issue! Unlock it first."

      • 當你拋出異常時,ABP會自動使用這個本地化的消息(基于當前的語言)顯示給最終用戶
      • 異常代碼 (IssueTracking:CanNotOpenLockedIssue), 也發送給客戶端,因此它可以通過編程方式處理錯誤情況。

      對于本例,您可以直接拋出BusinessException,而不是定義專門化
      IssueStateException。結果是一樣的。詳細信息請參見 異常處理文檔

      實體中需要外部服務的業務邏輯

      當業務邏輯只使用該實體的屬性時,在實體方法中實現業務規則很簡單。如果業務邏輯需要查詢數據庫或使用任何應該從依賴項注入系統解析的外部服務,該怎么辦? 記住,實體不能注入服務!

      有兩種常見的實現這種業務邏輯的方法:

      • 在實體的方法里實現業務邏輯,把外部依賴作為方法的參數傳遞進來
      • 創建領域服務

      稍后將解釋領域服務。但是,現在讓我們看看如何在實體類中實現它

      示例:業務規則:不能同時為一個用戶分配超過3個開放問題

      image

      • AssignedUserId 的屬性設置器是私有的,改變它的唯一方法是使用 AssignToAsyncCleanAssignment 方法
      • AssignToAsync 需要 AppUser 實體, 實際上,它只使用 User.Id ,你可以傳遞一個Guid值,比如userId。但是,這種方法可以確保Guid值是一個現有用戶的Id,而不是一個隨機的Guid值
      • IUserIssueService 是一個服務,用來獲取已經分配給用戶的問題數量, 調用 AssignToAsync 的代碼負責解析 IUserIssueService 并傳遞到這里
      • 如果業務規則不滿足的話, AssignToAsync 會拋出異常
      • 最好,如果所有事情都順利的話, AssignedUserId 屬性會被賦值

      當您希望將問題分配給用戶時,此方法可以完美地保證應用業務邏輯。然而,它有一些問題

      • 它使實體類依賴于外部服務,這使實體變得復雜
      • 這使得使用實體變得困難。使用實體的代碼現在需要注入IUserIssueService 并傳遞給 AssignToAsync 方法

      實現此業務邏輯的另一種方法是引入領域服務,稍后將對此進行解釋。

      posted @ 2022-06-23 14:10  Broadm  閱讀(688)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 国产精品视频一区二区噜噜| yw尤物av无码国产在线观看| 久久综合久中文字幕青草| 丰满熟女人妻一区二区三| 欧美亚洲色综久久精品国产| 中文字幕日韩有码国产| 噶尔县| 国产成人精品永久免费视频| 精品国产美女福到在线不卡| 亚州中文字幕一区二区| 亚洲免费一区二区av| 丁香婷婷色综合激情五月| 亚洲精品无码日韩国产不卡av| 风韵丰满熟妇啪啪区老熟熟女| 精品无码一区在线观看| 国产成人精彩在线视频| 亚洲午夜亚洲精品国产成人| 老少配老妇老熟女中文普通话| 兴宁市| 午夜男女爽爽影院在线| 国产精品美女久久久久久麻豆| 日韩人妻不卡一区二区三区| 视频一区二区三区刚刚碰| 国产色无码专区在线观看 | 九色综合久99久久精品| 亚洲国产成人无码av在线影院| 午夜色大片在线观看免费| 激情五月开心婷婷深爱| 亚洲av男人电影天堂热app| 国产亚洲欧美精品久久久| 花式道具play高h文调教| 亚洲精品成人片在线观看精品字幕| 国产精品无码专区| 久久96热在精品国产高清| 国产精品乱一区二区三区| 午夜在线观看成人av| 欧美亚洲人成网站在线观看| 国产稚嫩高中生呻吟激情在线视频| 久久99精品久久久学生| 亚洲高清aⅴ日本欧美视频| 久久亚洲精品11p|