實現領域驅動設計 - 使用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
  • 用例演示 - 創建實體 本節將演示一些示例用例並討論可選場景。 創建實體 從實體/聚合根類創建對象是實體生命周期的第一步。聚合/聚合根規則和最佳實踐部分 建議為Entity類創建一個主構造函數,以保證創建一個有效的實體。因此,無論何時我們需要創建實體的實例,我們都應該使用那個構造函數 參見下麵的問題 ...
  • 領域邏輯 & 應用邏輯 如前所述,領域驅動設計中的業務邏輯分為兩部分(層):領域邏輯和應用邏輯: 領域邏輯由系統的核心領域規則組成,應用邏輯實現應用特定的用例 雖然定義很明確,但實現起來可能並不容易。您可能無法決定哪些代碼應該位於應用程式層,哪些代碼應該位於領域層。本節試圖解釋其中的差異 多個應用程 ...
  • 表弟大學快畢業了,學了一個學期Python居然還不會寫學生管理系統,真的給我丟臉啊,教他又不肯學,還讓我直接給他寫,我真想兩巴掌上去,最終還是寫了給他,誰讓他是我表弟呢,關鍵時候還是得幫他一把! 寫完了放在那也是放著,所以今天分享給大家吧! 話不多說,咱們直接開始吧! 代碼解析 一、登錄頁面 1、定 ...
  • Zookeeper3.7源碼剖析 能力目標 掌握Zookeeper中Session的管理機制 能基於Client進行Debug測試Session創建/刷新操作 能搭建Zookeeper集群源碼配置 掌握集群環境下Leader選舉啟動過程 能說出Zookeeper選舉過程中的概念 能說出Zookeep ...
  • 前言 今天給大家分享一下我自己寫的筆記,純純的都是乾貨,關於字好像也能看。這是我學python整理出來的一些資料,希望對大家 有用。想要更多的資料那就的給一個關註了… python學習交流Q群:903971231### #導入Counter from collections import Count ...
  • Hi,大家好,我是Mic 一個工作5年的粉絲找到我。 他說: “Mic老師,你要是能回答出這個問題,我就佩服你” 我當場就懵了,現在打賭都這麼隨意了嗎? 我問他問題是什麼,他說“Kafka如何避免重覆消費的問題!” 下麵看看普通人和高手的回答! 普通人: Kafka怎麼避免重覆消費就是我們可以通過 ...
  • 前言 Steam是由美國電子游戲商Valve於2003年9月12日推出的數字發行平臺,被認為是電腦游戲界最大的數位發行平臺之一,Steam平臺是全球最大的綜合性數字發行平臺之一。玩家可以在該平臺購買、下載、討論、上傳和分享游戲和軟體。 而每周的steam會開啟了一輪特惠,可以讓游戲打折,而玩家就會 ...
  • 本篇內容將在上一篇已有的內容基礎上,進一步的聊一下項目中使用JPA的一些高階複雜場景的實踐指導,覆蓋了主要核心的JPA使用場景,可以讓你在需求開發的時候對JPA的使用更加的游刃有餘。 ...
  • 1.路徑處理 1.找模塊:sys.path import sys print(sys.path) - 1.理解 - 1.是python去查找包或模塊 - 2.項目開始根目錄,python內置的目錄 - 3.雖然說python的安裝目錄下也可以存放我們寫的模塊,但是不建議(太多了,不大好找) - 4. ...
  • Go 語言入門練手項目系列 01 基於命令行的圖書的增刪查改 02 文件管理 持續更新中... > 本文來自博客園,作者:Arway,轉載請註明原文鏈接:https://www.cnblogs.com/cenjw/p/gobeginner-proj-bookstore-cli.html 介紹 這是一 ...