實現領域驅動設計 - 使用ABP框架 - 應用程式服務

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

應用程式服務 應用程式服務是一種無狀態的服務,它實現應用程式的用例。應用程式服務通常獲取和返回dto。它由表示層使用。它使用並協調領域對象(實體、存儲庫等)來實現用例 應用程式服務的常見原則如下: 實現特定於當前用例的應用程式邏輯。不要在應用程式服務內部實現核心領域邏輯。我們將回到應用程式領域邏輯之 ...


應用程式服務

應用程式服務是一種無狀態的服務,它實現應用程式的用例。應用程式服務通常獲取和返回dto。它由表示層使用。它使用並協調領域對象(實體、存儲庫等)來實現用例

應用程式服務的常見原則如下:

  • 實現特定於當前用例的應用程式邏輯。不要在應用程式服務內部實現核心領域邏輯。我們將回到應用程式領域邏輯之間的差異
  • 永遠不要為應用程式服務方法獲取或返回實體。這打破了領域層的封裝。總是獲取和返回dto

示例:分配問題給用戶

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

    [Authorize]
    public async Task AssignAsync(IssueAssignDto input)
    {
        var issue = await _issueRepository.GetAsync(input.IssueId);
        var user = await _userRepository.GetAsync(input.UserId);

        await _issueManager.AssignToAsync(issue, user);
        await _issueRepository.UpdateAsync(issue);
    }
}

應用程式服務方法通常有三個步驟,在這裡實現了

  1. 從資料庫中獲取相關的領域對象來實現用例
  2. 使用領域對象(領域服務、實體等)執行實際操作
  3. 更新已更改的實體到資料庫中

如果你正在使用EF Core,上面的更新是不必要的,因為它有一個更改跟蹤系統。如果你想利用這個EF Core特性,請參閱 關於資料庫無關心原則的討論部分

本例中的 IssueAssignDto 是一個簡單的DTO類:

public class IssueAssignDto
{
    public Guid IssueId { get; set; }
    public Guid UserId { get; set; }
}

數據傳輸對象(DTO)

DTO是一個簡單的對象,用於在應用程式層和表示層之間傳輸狀態(數據)。因此,應用程式服務方法獲取和返回dto

通用DTO原則和最佳實踐:

  • 就其本質而言,DTO應該是可序列化的。因為,大多數時候它是通過網路傳輸的。因此,它應該有一個無參數(空)構造函數。
  • 不應該包含業務邏輯。
  • 永遠不要繼承或引用實體。

輸入dto(傳遞給應用程式服務方法的dto)與輸出dto(從應用程式服務方法返回的dto)具有不同的性質。所以,他們會被區別對待

輸入DTO最佳實踐

不要為輸入dto定義未使用的屬性

只定義用例所需的屬性! 否則,客戶端在使用Application Service方法時會感到困惑。您當然可以定義可選屬性,但是當客戶端提供它們時,它們應該影響用例的工作方式

首先,這條規則似乎沒有必要。誰會為方法定義未使用的參數(輸入DTO屬性)?但是,這種情況會發生,尤其是當您試圖重用輸入dto時。

不要復用輸入dto

為每個用例定義專門的輸入DTO(應用程式服務方法)。 否則,某些屬性在某些情況下不使用,這違反了上面定義的規則:不要為輸入dto定義未使用的屬性

有時,為兩個用例重用同一個DTO類似乎很有吸引力,因為它們幾乎是相同的。即使他們現在是一樣的,他們可能會變成不同的時候,你會遇到相同的問題。代碼複製是比耦合用例更好的實踐

重用輸入dto的另一種方法是相互繼承dto。雖然這在一些罕見的情況下是有用的,但大多數情況下它會使你達到相同的目的

示例:用戶應用服務

public interface IUserAppService : IApplicationService
{
    Task CreateAsync(UserDto input);
    Task UpdateAsync(UserDto input);
    Task ChangePasswordAsync(UserDto input);
}

IUserAppService 在所有方法(用例)中使用 UserDto 作為輸入DTO。UserDto的定義如下:

public class UserDto
{
    public Guid UserId { get; set; }
    public string UserName { get; set; }
    public string Email { get; set; }
    public string Password { get; set; }
    public DateTime CreationTime { get; set; }
}

對於這個示例:

  • Id 在創建中不使用,因為伺服器會在創建用戶時自動生成它
  • Password 沒有在更新中使用,因為我們有另一個更新密碼的方法
  • CreationTime 從不被使用,因為我們不能允許客戶端發送創建時間。它應該在伺服器中設置

真正的實現可以是這樣的:

public interface IUserAppService : IApplicationService
{
    Task CreateAsync(UserCreationDto input);
    Task UpdateAsync(UserUpdateDto input);
    Task ChangePasswordAsync(UserChangePasswordDto input);
}

儘管編寫了更多的代碼,但這是一種更易於維護的方法

例外情況:該規則可能有一些例外:如果您總是希望並行開發兩個方法,它們可能共用相同的輸入DTO(通過繼承或直接重用)。例如,如果您有一個具有一些過濾器的報告頁面,並且您有多個Application Service方法(如屏幕報告、excel報告和csv報告方法)使用相同的過濾器但返回不同的結果,您可能希望重用相同的過濾器輸入DTO來耦合這些用例。因為,在本例中,無論何時更改過濾器,您都必須對所有方法進行必要的更改,以擁有一致的報告系統。

輸入DTO驗證邏輯

  • 只在DTO內部實現形式驗證。使用數據註釋驗證屬性或實現IValidatableObject 進行形式驗證。
  • 不要執行領域驗證。例如,不要嘗試檢查dto中的唯一用戶名約束

示例:使用數據註釋屬性

public class UserCreationDto
{
    [Required]
    [StringLength(UserConsts.MaxUserNameLength)]
    public string UserName { get; set; }

    [Required]
    [EmailAddress]
    [StringLength(UserConsts.MaxEmailLength)]
    public string Email { get; set; }

    [Required]
    [StringLength(UserConsts.MaxPasswordLength, MinimumLength = UserConsts.MaxMinPasswordLength)]
    public string Password { get; set; }
}

ABP框架自動驗證輸入的dto,拋出AbpValidationException,併在無效輸入的情況下向客戶端返回HTTP狀態400

一些開發人員認為最好將驗證規則和DTO類分開。我們認為聲明式(數據註釋)方法是實用和有用的,並且不會導致任何設計問題。但是,如果您喜歡其他方法,ABP也支持 FluentValidation集成

有關所有驗證選項,請參 閱驗證文檔

輸出DTO最佳實踐

  • 保持輸出DTO計數最小。在可能的情況下重用(例外:不要將輸入dto重用為輸出dto)
  • 輸出dto可以包含比客戶端代碼中使用的更多的屬性。
  • 從創建和更新方法返回實體DTO。

這些建議的主要目的是:

  • 使客戶端代碼易於開發和擴展

    1. 在客戶端處理類似但不相同的dto是有問題的
    2. 將來在UI/客戶端上添加其他屬性是很常見的。返回實體的所有屬性(通過考慮安全性和特權)使客戶端代碼很容易改進,而不需要接觸後端代碼
    3. 如果你將API開放給第三方客戶,而你不知道每個客戶的需求
  • 使伺服器端代碼易於開發和擴展

    1. 你需要理解和維護的類更少
    2. 您可以重用 Entity->DTO 對象映射代碼
    3. 從不同的方法返回相同的類型使創建新方法變得簡單明瞭

示例:從不同的方法返回不同的dto

public interface IUserAppService : IApplicationService
{
    UserDto Get(Guid id);
    List<UserNameAndEmailDto> GetUserNameAndEmail(Guid id);
    List<string> GetRoles(Guid id);
    List<UserListDto> GetList();
    UserCreationResultDto Create(UserCreationDto input);
    UserUpdateResultDto Update(UserUpdateDto input);
}

(我們沒有使用非同步方法使示例更清晰,但在您的實際應用程式中使用非同步!)

上面的示例代碼為每個方法返回不同的DTO類型。您可以猜到,在查詢數據、將實體映射到dto時,會有很多代碼重覆

上面的IUserAppService服務可以被簡化:

public interface IUserAppService : IApplicationService
{
    UserDto Get(Guid id);
    List<UserDto> GetList();
    UserDto Create(UserCreationDto input);
    UserDto Update(UserUpdateDto input);
}

統一使用單個輸出DTO:

public class UserDto
{
    public Guid Id { get; set; }
    public string UserName { get; set; }
    public string Email { get; set; }
    public DateTime CreationTime { get; set; }
    public List<string> Roles { get; set; }
}
  • 刪除了GetUserNameAndEmail 和 GetRoles,因為Get方法已經返回必要的信息
  • GetList 現在與Get返回相同的結果
  • 創建和更新也返回相同的UserDto

如前所述,使用相同的輸出DTO有許多優點。例如,設想一個場景,您在UI上顯示一個Users數據網格。在更新用戶之後,您可以獲得返回值併在UI上更新它。你不需要再調用GetList。這就是為什麼我們建議返回實體DTO(這裡是UserDto)作為創建和更新操作的返回值

討論

有些輸出DTO建議可能不適合所有場景。由於性能原因,可以忽略這些建議,特別是當返回的數據集很大,或者當您為自己的UI創建服務時,您有太多的併發請求

在這些情況下,您可能希望創建包含最少信息的專用輸出dto。以上建議特別適用於那些維護代碼庫比忽略不計的性能損失更重要的應用程式

對象映射

當兩個對象具有相同或相似的屬性時,自動 對象映射 是將值從一個對象複製到另一個對象的有用方法

DTO和實體類通常具有相同/相似的屬性,通常需要從實體創建DTO對象。ABP的 對象映射系統AutoMapper 集成使得這些操作比手動映射更容易。

  • 只對實體使用自動對象映射來輸出DTO映射
  • 不要對輸入DTO到實體的映射使用自動對象映射。

有一些原因不應該使用輸入DTO來進行實體自動映射:

  • 實體類通常有一個接受參數並確保有效創建對象的構造函數。自動對象映射操作通常需要一個空的構造函數
  • 大多數實體屬性將具有私有設置器,您應該使用方法以可控的方式更改這些屬性
  • 您通常需要仔細地驗證和處理用戶/客戶端輸入,而不是盲目地映射到實體屬性

雖然其中一些問題可以通過映射配置來解決(例如,AutoMapper允許定義自定義映射規則),但它使您的業務代碼隱式/隱藏,並與基礎設施緊密耦合。我們認為業務代碼應該是明確的、清晰的和容易理解的

請參閱下麵的實體創建一節,以獲得本節建議的示例實現。


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

-Advertisement-
Play Games
更多相關文章
  • 前言 618過去了,前兩天我幹了一件驚天動地的大事,估計這件大事是很多小伙伴都想乾的。我居然用python搶購淘寶商品,沒想到 吧,最勇敢的還是我。關於搶購的思路以及代碼,我將會在這篇文章中詳細的介紹,感興趣的可以往下看喲!!! 目錄 1.項目環境 2.某寶搶購流程分析 3.程式實現思路 4.代碼實 ...
  • #雙色球系統 ##案例: ##中獎條件及獎金錶 ##代碼及解釋 ###main方法代碼: public static void main(String[] args) { //1.隨機6個紅球號碼(1~33,不能重覆),隨機一個藍球號碼(1~16) int[] num = createLuckNum ...
  • 來源:https://www.51cto.com/article/628604.html 你是否還在大量控制台視窗中監控容器,還是對使用終端命令充滿熱情?而使用Docker的圖形用戶界面(GUI)工具,則可以更簡單的對容器進行管理,並提高效率。而且它們都是免費的。 1.Portainer Porta ...
  • Hi,大家好,我是Mic 一個工作3年的粉絲,早上6點給我微信發語音,把我直接嚇醒。 我以為什麼天大的事情,結果一問才知道。 面試官問了他一個問題沒答上來,問題是“Spring裡面,如果兩個id相同的bean會報錯嗎?如果會,在哪個階段報錯?” 下麵看看普通人和高手的回答! 普通人: 兩個id相同的 ...
  • 作為SpringData JPA系列內容的第二篇,此處以SpringBoot項目為基準,講一下集成SpringData JPA的相關要點,帶你快速的上手SpringData JPA,並用實例演示常見的DB操作場景,讓你分分鐘輕鬆玩轉JPA。 ...
  • 前言 不知道大家在工作無聊時,有沒有一種衝動:總想掏出手機,看看微博熱搜在討論什麼有趣的話題,但又不方便直接打開微博瀏 覽,今天就和大家分享一個有趣的小爬蟲,定時採集微博熱搜榜&熱評,下麵讓我們來看看具體的實現方法。 頁面分析 熱搜頁 熱榜首頁:https://s.weibo.com/top/sum ...
  • Zookeeper3.7源碼剖析 能力目標 能基於Maven導入最新版Zookeeper源碼 能說出Zookeeper單機啟動流程 理解Zookeeper預設通信中4個線程的作用 掌握Zookeeper業務處理源碼處理流程 能夠在Zookeeper源碼中Debug測試通信過程 1 Zookeeper ...
  • 系列文章彙總 前言: 最近看到ABP官網的一本電子書,感覺寫的很好,翻譯出來,一起學習下 Implementing Domain Driven Design 實現領域驅動設計 - 使用ABP框架 - 什麼是領域驅動設計? 實現領域驅動設計 - 使用ABP框架 - 解決方案概覽 實現領域驅動設計 - ...
一周排行
    -Advertisement-
    Play Games
  • 1. 說明 /* Performs operations on System.String instances that contain file or directory path information. These operations are performed in a cross-pla ...
  • 視頻地址:【WebApi+Vue3從0到1搭建《許可權管理系統》系列視頻:搭建JWT系統鑒權-嗶哩嗶哩】 https://b23.tv/R6cOcDO qq群:801913255 一、在appsettings.json中設置鑒權屬性 /*jwt鑒權*/ "JwtSetting": { "Issuer" ...
  • 引言 集成測試可在包含應用支持基礎結構(如資料庫、文件系統和網路)的級別上確保應用組件功能正常。 ASP.NET Core 通過將單元測試框架與測試 Web 主機和記憶體中測試伺服器結合使用來支持集成測試。 簡介 集成測試與單元測試相比,能夠在更廣泛的級別上評估應用的組件,確認多個組件一起工作以生成預 ...
  • 在.NET Emit編程中,我們探討了運算操作指令的重要性和應用。這些指令包括各種數學運算、位操作和比較操作,能夠在動態生成的代碼中實現對數據的處理和操作。通過這些指令,開發人員可以靈活地進行算術運算、邏輯運算和比較操作,從而實現各種複雜的演算法和邏輯......本篇之後,將進入第七部分:實戰項目 ...
  • 前言 多表頭表格是一個常見的業務需求,然而WPF中卻沒有預設實現這個功能,得益於WPF強大的控制項模板設計,我們可以通過修改控制項模板的方式自己實現它。 一、需求分析 下圖為一個典型的統計表格,統計1-12月的數據。 此時我們有一個需求,需要將月份按季度劃分,以便能夠直觀地看到季度統計數據,以下為該需求 ...
  • 如何將 ASP.NET Core MVC 項目的視圖分離到另一個項目 在當下這個年代 SPA 已是主流,人們早已忘記了 MVC 以及 Razor 的故事。但是在某些場景下 SSR 還是有意想不到效果。比如某些靜態頁面,比如追求首屏載入速度的時候。最近在項目中回歸傳統效果還是不錯。 有的時候我們希望將 ...
  • System.AggregateException: 發生一個或多個錯誤。 > Microsoft.WebTools.Shared.Exceptions.WebToolsException: 生成失敗。檢查輸出視窗瞭解更多詳細信息。 內部異常堆棧跟蹤的結尾 > (內部異常 #0) Microsoft ...
  • 引言 在上一章節我們實戰了在Asp.Net Core中的項目實戰,這一章節講解一下如何測試Asp.Net Core的中間件。 TestServer 還記得我們在集成測試中提供的TestServer嗎? TestServer 是由 Microsoft.AspNetCore.TestHost 包提供的。 ...
  • 在發現結果為真的WHEN子句時,CASE表達式的真假值判斷會終止,剩餘的WHEN子句會被忽略: CASE WHEN col_1 IN ('a', 'b') THEN '第一' WHEN col_1 IN ('a') THEN '第二' ELSE '其他' END 註意: 統一各分支返回的數據類型. ...
  • 在C#編程世界中,語法的精妙之處往往體現在那些看似微小卻極具影響力的符號與結構之中。其中,“_ =” 這一組合突然出現還真不知道什麼意思。本文將深入剖析“_ =” 的含義、工作原理及其在實際編程中的廣泛應用,揭示其作為C#語法奇兵的重要角色。 一、下劃線 _:神秘的棄元符號 下劃線 _ 在C#中並非 ...