1. 程式人生 > >ABP(現代ASP.NET樣板開發框架)系列之16、ABP應用層——資料傳輸物件(DTOs)

ABP(現代ASP.NET樣板開發框架)系列之16、ABP應用層——資料傳輸物件(DTOs)

基於DDD的現代ASP.NET開發框架--ABP系列之16、ABP應用層——資料傳輸物件(DTOs)

ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。

資料傳輸物件(Data Transfer Objects)用於應用層和展現層的資料傳輸。

展現層傳入資料傳輸物件(DTO)呼叫一個應用服務方法,接著應用服務通過領域物件執行一些特定的業務邏輯並且返回DTO給展現層。這樣展現層和領域層被完全分離開了。在具有良好分層的應用程式中,展現層不會直接使用領域物件(倉庫,實體)。

資料傳輸物件的作用

為每個應用服務方法建立DTO看起來是一項乏味耗時的工作。但如果你正確使用它們,這將會解救你的專案。為啥呢?

(1)抽象領域層 (Abstraction of domain layer)

在展現層中資料傳輸物件對領域物件進行了有效的抽象。這樣你的層(layers)將被恰當的隔離開來。甚至當你想要完全替換展現層時,你還可以繼續使用已經存在的應用層和領域層。反之,你可以重寫領域層,修改資料庫結構,實體和ORM框架,但並不需要對展現層做任何修改,只要你的應用層沒有發生改變。

(2)資料隱藏 (Data hiding)

想象一下,你有一個User實體擁有屬性Id, Name, EmailAddress和Password。如果UserAppService的GetAllUsers()方法的返回值型別為List。這樣任何人都可以檢視所有人的密碼,即使你沒有將它列印在螢幕上。這不僅僅是安全問題,這還跟資料隱藏有關。應用服務應只返回展現層所需要的,不多不少剛剛好。

(3)序列化 & 惰性載入 (Serialization & lazy load problems)

當你將資料(物件)返回給展現層時,資料有可能會被序列化。舉個例子,在一個返回Json的MVC的Action中,你的物件需要被序列化成JSON併發送給客戶端。直接返回實體給展現層將有可能會出現麻煩。

在真實的專案中,實體會引用其他實體。User實體會引用Role實體。所以,當你序列化User時,Role也將被序列化。而且Role還擁有一個List並且Permission還引用了PermissionGroup等等….你能想象這些物件都將被序列化嗎?這有很有可能使整個資料庫資料意外的被序列化。那麼該如何解決呢?將屬性標記為不可序列化?不行,因為你不知道屬性何時該被序列化何時不該序列化。所以在這種情況下,返回一個可安全序列化,特別定製的資料傳輸物件是不錯的選擇哦。

幾乎所有的ORM框架都支援惰性載入。只有當你需要載入實體時它才會被載入。比如User型別引用Role型別。當你從資料庫獲取User時,Role屬性並沒有被填充。當你第一次讀取Role屬性時,才會從資料庫中載入Role。所以,當你返回這樣一個實體給展現層時,很容易引起副作用(從資料庫中載入)。如果序列化工具讀取實體,它將會遞迴地讀取所有屬性,這樣你的整個資料庫都將會被讀取。

在展現層中使用實體還會有更多的問題。最佳的方案就是展現層不應該引用任何包含領域層的程式集。

 DTO 約定 & 驗證

ABP對資料傳輸物件提供了強大的支援。它提供了一些相關的(Conventional)型別 & 介面並對DTO命名和使用約定提供了建議。當你像這裡一樣使用DTO,ABP將會自動化一些任務使你更加輕鬆。

一個例子 (Example)

讓我們來看一個完整的例子。我們相要編寫一個應用服務方法根據name來搜尋people並返回people列表。Person實體程式碼如下:

public class Person : Entity
{
    public virtual string Name { get; set; }
    public virtual string EmailAddress { get; set; }
    public virtual string Password { get; set; }
}

首先,我們定義一個應用服務介面:

public interface IPersonAppService : IApplicationService
{
    SearchPeopleOutput SearchPeople(SearchPeopleInput input);
}

ABP建議命名input/ouput物件類似於MethodNameInput/MethodNameOutput,對於每個應用服務方法都需要將Input和Output進行分開定義。甚至你的方法只接收或者返回一個值,也最好建立相應的DTO型別。這樣,你的程式碼才會更具有擴充套件性,你可以新增更多的屬性而不需要更改方法的簽名,這並不會破壞現有的客戶端應用。

當然,方法返回值有可能是void,之後你新增一個返回值並不會破壞現有的應用。如果你的方法不需要任何引數,那麼你不需要定義一個Input Dto。但是建立一個Input Dto可能是個更好的方案,因為該方法在將來有可能會需要一個引數。當然是否建立這取決於你。 Input和Output DTO型別定義如下:

public class SearchPeopleInput : IInputDto
{
    [StringLength(40, MinimumLength = 1)]
    public string SearchedName { get; set; }
}

public class SearchPeopleOutput : IOutputDto
{
    public List<PersonDto> People { get; set; }
}

public class PersonDto : EntityDto
{
    public string Name { get; set; }
    public string EmailAddress { get; set; }
}

驗證:作為約定,Input DTO實現IInputDto 介面,Output DTO實現IOutputDto介面。當你宣告IInputDto引數時, 在方法執行前ABP將會自動對其進行有效性驗證。這類似於ASP.NET MVC驗證機制,但是請注意應用服務並不是一個控制器(Controller)。ABP對其進行攔截並檢查輸入。檢視DTO 驗證(DTO Validation)文件獲取更多資訊。 EntityDto是一個簡單具有與實體相同的Id屬性的簡單型別。如果你的實體Id不為int型你可以使用它泛型版本。EntityDto也實現了IDto介面。你可以看到PersonDto並不包含Password屬性,因為展現層並不需要它。

跟進一步之前我們先實現IPersonAppService:

public class PersonAppService : IPersonAppService
{
    private readonly IPersonRepository _personRepository;

    public PersonAppService(IPersonRepository personRepository)
    {
        _personRepository = personRepository;
    }
    public SearchPeopleOutput SearchPeople(SearchPeopleInput input)
    {
        //獲取實體
        var peopleEntityList = _personRepository.GetAllList(person => person.Name.Contains(input.SearchedName));

        //轉換成DTO
        var peopleDtoList = peopleEntityList
            .Select(person => new PersonDto
                                {
                                    Id = person.Id,
                                    Name = person.Name,
                                    EmailAddress = person.EmailAddress
                                }).ToList();

        return new SearchPeopleOutput { People = peopleDtoList };
    }
}

我們從資料庫獲取實體,將實體轉換成DTO並返回output。注意我們沒有手動檢測Input的資料有效性。ABP會自動驗證它。ABP甚至會檢查Input是否為null,如果為null則會丟擲異常。這避免了我們在每個方法中都手動檢查資料有效性。

但是你很可能不喜歡手動將Person實體轉換成PersonDto。這真的是個乏味的工作。Peson實體包含大量屬性時更是如此。

DTO和實體間的自動對映

還好這裡有些工具可以讓對映(轉換)變得十分簡單。AutoMapper就是其中之一。你可以通過nuget把它新增到你的專案中。讓我們使用AutoMapper來重寫SearchPeople方法:

public SearchPeopleOutput SearchPeople(SearchPeopleInput input)
{
    var peopleEntityList = _personRepository.GetAllList(person => person.Name.Contains(input.SearchedName));
    return new SearchPeopleOutput { People = Mapper.Map<List<PersonDto>>(peopleEntityList) };
}

這就是全部程式碼。你可以在實體和DTO中新增更多的屬性,但是轉換程式碼依然保持不變。在這之前你只需要做一件事:對映

Mapper.CreateMap<Person, PersonDto>();

AutoMapper建立了對映的程式碼。這樣,動態對映就不會成為效能問題。真是快速又方便。AutoMapper根據Person實體建立了PersonDto,並根據命名約定來給PersonDto的屬性賦值。命名約定是可配置的並且很靈活。你也可以自定義對映和使用更多特性,檢視AutoMapper的文件獲取更多資訊。

使用特性(attributes)和擴充套件方法來對映 (Mapping using attributes and extension methods)

ABP提供了幾種attributes和擴充套件方法來定義對映。使用它你需要通過nuget將Abp.AutoMapper新增到你的專案中。使用AutoMap特性(attribute)可以有兩種方式進行對映,一種是使用AutoMapFrom和AutoMapTo。另一種是使用MapTo擴充套件方法。定義對映的例子如下:

[AutoMap(typeof(MyClass2))] //定義對映(這樣有兩種方式進行對映)
public class MyClass1
{
    public string TestProp { get; set; }
}

public class MyClass2
{
    public string TestProp { get; set; }
}

接著你可以通過MapTo擴充套件方法來進行對映:

var obj1 = new MyClass1 { TestProp = "Test value" };
var obj2 = obj1.MapTo<MyClass2>(); //建立了新的MyClass2物件,並將obj1.TestProp的值賦值給新的MyClass2物件的TestProp屬性。
上面的程式碼根據MyClass1建立了新的MyClass2物件。你也可以對映已存在的物件,如下所示:
var obj1 = new MyClass1 { TestProp = "Test value" };
var obj2 = new MyClass2();
obj1.MapTo(obj2); //根據obj1設定obj2的屬性

 輔助介面和型別

ABP還提供了一些輔助介面,定義了常用的標準化屬性。

ILimitedResultRequest定義了MaxResultCount屬性。所以你可以在你的Input DTO上實現該介面來限制結果集數量。

IPagedResultRequest擴充套件了ILimitedResultRequest,它添加了SkipCount屬性。所以我們在SearchPeopleInput實現該介面用來分頁:

public class SearchPeopleInput : IInputDto, IPagedResultRequest
{
    [StringLength(40, MinimumLength = 1)]
    public string SearchedName { get; set; }

    public int MaxResultCount { get; set; }
    public int SkipCount { get; set; }
}

對於分頁請求,你可以將實現IHasTotalCount的Output DTO作為返回結果。標準化屬性幫助我們建立可複用的程式碼和規範。可在Abp.Application.Services.Dto名稱空間下檢視其他的介面和型別。

希望更多國內的架構師能關注到ABP這個專案,也許這其中有能幫助到您的地方,也許有您的參與,這個專案可以發展得更好。

歡迎加QQ群:

ABP架構設計交流群:134710707 ABP架構設計交流群      ABP架構設計交流2群: 579765441ABP架構設計交流群2

相關推薦

ABP(現代ASP.NET樣板開發框架)系列16ABP應用——資料傳輸物件DTOs

基於DDD的現代ASP.NET開發框架--ABP系列之16、ABP應用層——資料傳輸物件(DTOs) ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 資料傳輸物件(Data Transfer Objects)用於應用層

ABP(現代ASP.NET樣板開發框架)系列10ABP領域——實體

基於DDD的現代ASP.NET開發框架--ABP系列之10、ABP領域層——實體 ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 本文由深圳-Carl提供翻譯 實體是DDD(領域驅動設計)的核心概念之一。Eric Eva

ABP(現代ASP.NET樣板開發框架)系列4ABP模組系統

基於DDD的現代ASP.NET開發框架--ABP系列之4、ABP模組系統 ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。  本文由東莞-天道提供翻譯  ABP模組系統簡介 ABP框架提供了建立和組裝模組的基礎,一個模組

ABP(現代ASP.NET樣板開發框架)系列6ABP依賴注入

基於DDD的現代ASP.NET開發框架--ABP系列之6、ABP依賴注入 ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。  本文由 上海-半冷 提供翻譯 什麼是依賴注入 如果你已經知道依賴注入的概念,建構函式和屬性注入

ABP(現代ASP.NET樣板開發框架)系列9ABP設定管理

基於DDD的現代ASP.NET開發框架--ABP系列之9、ABP設定管理 ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 本文由山東-李偉提供翻譯 介紹 每個應用程式需要儲存一些設定並在應用程式的某個地方使用這些設定。

ABP(現代ASP.NET樣板開發框架)系列2ABP入門教程

基於DDD的現代ASP.NET開發框架--ABP系列之2、ABP入門教程 ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 ASP.NET Boilerplate是一個用最佳實踐和流行技術開發現代WEB應用程式的新起點,它旨在成為一個通用的

ABP(現代ASP.NET樣板開發框架)系列17ABP應用——引數有效性驗證

基於DDD的現代ASP.NET開發框架--ABP系列之17、ABP應用層——引數有效性驗證 ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 應用程式的輸入資料首先應該被檢驗是否有效。輸入的資料能被使用者或其他應用程式提

ABP(現代ASP.NET樣板開發框架)系列13ABP領域——資料過濾器Data filters

基於DDD的現代ASP.NET開發框架--ABP系列之13、ABP領域層——資料過濾器(Data filters) ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 介紹 在資料庫開發中,我們一般會運用軟刪除(soft

ABP(現代ASP.NET樣板開發框架)系列21ABP展現——Javascript函式庫

基於DDD的現代ASP.NET開發框架--ABP系列之21、ABP展現層——Javascript函式庫 ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 ASP.NET Boilerplate的js庫提供了一些讓java

ABP(現代ASP.NET樣板開發框架)系列14ABP領域——領域事件Domain events

基於DDD的現代ASP.NET開發框架--ABP系列之14、ABP領域層——領域事件(Domain events) ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 在C#中,一個類可以定義其專屬的事件並且其它類可以註冊該事

ABP(現代ASP.NET樣板開發框架)系列5ABP啟動配置

基於DDD的現代ASP.NET開發框架--ABP系列之5、ABP啟動配置 ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。  本文由 東莞-天道 提供翻譯 譯者注:在看這一節的內容之前,建議大家先下載module-ze

ABP(現代ASP.NET樣板開發框架)系列3ABP分層架構

基於DDD的現代ASP.NET開發框架--ABP系列之3、ABP分層架構 ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 前言 為了減少複雜性和提高程式碼的可重用性,採用分層架構是一種被廣泛接受的技術。為了實現分層的

ABP(現代ASP.NET樣板開發框架)系列19ABP應用——審計日誌

基於DDD的現代ASP.NET開發框架--ABP系列之19、ABP應用層——審計日誌 ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 維基百科定義:審計跟蹤(也稱為稽核日誌)是一個安全相關的時間順序記錄,記錄這些記錄的

ABP(現代ASP.NET樣板開發框架)系列15ABP應用——應用服務Application services

基於DDD的現代ASP.NET開發框架--ABP系列之15、ABP應用層——應用服務(Application services) ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 本文由東莞-天道提供翻譯 應用服務用於將領

ABP(現代ASP.NET樣板開發框架)系列23ABP展現——異常處理

基於DDD的現代ASP.NET開發框架--ABP系列之23、ABP展現層——異常處理 ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 在 web 應用程式中,異常通常是在 MVC Controller actions

ABP(現代ASP.NET樣板開發框架)系列8ABP日誌管理

基於DDD的現代ASP.NET開發框架--ABP系列之8、ABP日誌管理 ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 本文由東莞-天道提供翻譯 Server side(伺服器端) ASP.NET Boilerpla

ABP(現代ASP.NET樣板開發框架)系列1ABP總體介紹

基於DDD的現代ASP.NET開發框架--ABP系列之1、ABP總體介紹 ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 ASP.NET Boilerplate是一個用最佳實踐和流行技術開發現代WEB應用程式的新起點,它旨在成為一個通用的

ABP(現代ASP.NET樣板開發框架)系列12ABP領域——工作單元Unit Of work

基於DDD的現代ASP.NET開發框架--ABP系列之12、ABP領域層——工作單元(Unit Of work) ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 通用連線和事務管理方法 連線和事務管理是使用資料庫的應用程

ABP(現代ASP.NET樣板開發框架)系列22ABP展現——導航欄設定

基於DDD的現代ASP.NET開發框架--ABP系列之22、ABP展現層——導航欄設定 ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 每一個WEB應用程式都有導航選單,Abp也為使用者提供了通用的建立和顯示選單方式。

ABP(現代ASP.NET樣板開發框架)系列7ABP Session管理

基於DDD的現代ASP.NET開發框架--ABP系列之7、ABP Session管理 ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 簡介 如果一個應用程式需要登入,則它必須知道當前使用者執行了什麼操作。因此ASP.