實現領域驅動設計 - 使用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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...