實現領域驅動設計 - 使用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
  • Timer是什麼 Timer 是一種用於創建定期粒度行為的機制。 與標準的 .NET System.Threading.Timer 類相似,Orleans 的 Timer 允許在一段時間後執行特定的操作,或者在特定的時間間隔內重覆執行操作。 它在分散式系統中具有重要作用,特別是在處理需要周期性執行的 ...
  • 前言 相信很多做WPF開發的小伙伴都遇到過表格類的需求,雖然現有的Grid控制項也能實現,但是使用起來的體驗感並不好,比如要實現一個Excel中的表格效果,估計你能想到的第一個方法就是套Border控制項,用這種方法你需要控制每個Border的邊框,並且在一堆Bordr中找到Grid.Row,Grid. ...
  • .NET C#程式啟動閃退,目錄導致的問題 這是第2次踩這個坑了,很小的編程細節,容易忽略,所以寫個博客,分享給大家。 1.第一次坑:是windows 系統把程式運行成服務,找不到配置文件,原因是以服務運行它的工作目錄是在C:\Windows\System32 2.本次坑:WPF桌面程式通過註冊表設 ...
  • 在分散式系統中,數據的持久化是至關重要的一環。 Orleans 7 引入了強大的持久化功能,使得在分散式環境下管理數據變得更加輕鬆和可靠。 本文將介紹什麼是 Orleans 7 的持久化,如何設置它以及相應的代碼示例。 什麼是 Orleans 7 的持久化? Orleans 7 的持久化是指將 Or ...
  • 前言 .NET Feature Management 是一個用於管理應用程式功能的庫,它可以幫助開發人員在應用程式中輕鬆地添加、移除和管理功能。使用 Feature Management,開發人員可以根據不同用戶、環境或其他條件來動態地控制應用程式中的功能。這使得開發人員可以更靈活地管理應用程式的功 ...
  • 在 WPF 應用程式中,拖放操作是實現用戶交互的重要組成部分。通過拖放操作,用戶可以輕鬆地將數據從一個位置移動到另一個位置,或者將控制項從一個容器移動到另一個容器。然而,WPF 中預設的拖放操作可能並不是那麼好用。為瞭解決這個問題,我們可以自定義一個 Panel 來實現更簡單的拖拽操作。 自定義 Pa ...
  • 在實際使用中,由於涉及到不同編程語言之間互相調用,導致C++ 中的OpenCV與C#中的OpenCvSharp 圖像數據在不同編程語言之間難以有效傳遞。在本文中我們將結合OpenCvSharp源碼實現原理,探究兩種數據之間的通信方式。 ...
  • 一、前言 這是一篇搭建許可權管理系統的系列文章。 隨著網路的發展,信息安全對應任何企業來說都越發的重要,而本系列文章將和大家一起一步一步搭建一個全新的許可權管理系統。 說明:由於搭建一個全新的項目過於繁瑣,所有作者將挑選核心代碼和核心思路進行分享。 二、技術選擇 三、開始設計 1、自主搭建vue前端和. ...
  • Csharper中的表達式樹 這節課來瞭解一下表示式樹是什麼? 在C#中,表達式樹是一種數據結構,它可以表示一些代碼塊,如Lambda表達式或查詢表達式。表達式樹使你能夠查看和操作數據,就像你可以查看和操作代碼一樣。它們通常用於創建動態查詢和解析表達式。 一、認識表達式樹 為什麼要這樣說?它和委托有 ...
  • 在使用Django等框架來操作MySQL時,實際上底層還是通過Python來操作的,首先需要安裝一個驅動程式,在Python3中,驅動程式有多種選擇,比如有pymysql以及mysqlclient等。使用pip命令安裝mysqlclient失敗應如何解決? 安裝的python版本說明 機器同時安裝了 ...