第十一章:應用層設計 (DTOs & Object Mapping)
11.1 引言:應用層的職責
應用層 (Application Layer) 是軟體的「門面」。它不包含核心業務邏輯 (那是領域層的事),而是負責:
- 定義使用案例 (Use Cases):例如「建立訂單」、「取得書籍列表」。
- DTO 轉換:將領域物件轉換為適合前端顯示的 DTO。
- 協調:呼叫領域服務、Repository、發送 Email 等。
- 權限與驗證:確保呼叫者有權限且輸入合法。
11.2 DTO (Data Transfer Object) 設計
DTO 是單純的資料容器,不包含任何行為。
1. 為什麼需要 DTO?
- 解耦:避免將領域層的內部結構 (Entity) 直接暴露給外部 (API/UI)。
- 安全性:避免意外暴露敏感欄位 (如 PasswordHash)。
- 效能:只傳輸需要的資料,減少頻寬。
- 版本相容:當 Entity 修改時,DTO 可以保持不變,維持 API 相容性。
2. DTO 命名慣例
- 輸入:
CreateBookDto,UpdateBookDto,BookCreateUpdateDto(若共用)。 - 輸出:
BookDto,BookListDto(若列表與詳情不同)。 - 查詢:
GetBooksInput(繼承自PagedAndSortedResultRequestDto)。
3. 繼承 ABP 基類
ABP 提供了許多好用的 DTO 基類:
EntityDto<TKey>:包含Id屬性。AuditedEntityDto<TKey>:包含CreationTime,CreatorId等審計屬性。PagedAndSortedResultRequestDto:包含SkipCount,MaxResultCount,Sorting。
public class BookDto : AuditedEntityDto<Guid>
{
public string Name { get; set; }
public float Price { get; set; }
}11.3 物件映射 (Object Mapping)
手動編寫 entity.Name = dto.Name 是乏味且容易出錯的。ABP V10 引入了 Mapperly 作為預設的物件對映器。
Mapperly 是一個基於 Source Generator 的 .NET 物件對映器。與 AutoMapper 不同,它在編譯時生成對映程式碼,因此具有極高的效能且不依賴執行時期的 Reflection。
1. 定義映射 (Mapper)
在 Application 專案中,定義一個繼承自 CreateMap 的部分類別,並標記 [Mapper] 屬性。
[Mapper]
public partial class BookStoreObjectMapper
{
// Entity -> DTO
public partial BookDto BookToBookDto(Book book);
// DTO -> Entity (用於 Create/Update)
public partial Book CreateUpdateBookDtoToBook(CreateUpdateBookDto dto);
}注意:ABP V10 仍然支援 AutoMapper,但建議新專案使用 Mapperly 以獲得最佳效能。
2. 使用 ObjectMapper
在 Application Service 中注入 IObjectMapper。
public async Task<BookDto> GetAsync(Guid id)
{
var book = await _bookRepository.GetAsync(id);
// 自動轉換
return ObjectMapper.Map<Book, BookDto>(book);
}3. 進階映射技巧
- 忽略屬性:csharp
[MapperIgnoreTarget(nameof(Book.CreationTime))] public partial Book CreateBookDtoToBook(CreateBookDto dto); - 自訂解析:csharp
[MapProperty(nameof(Book.Author.Name), nameof(BookDto.AuthorName))] public partial BookDto BookToBookDto(Book book);
11.4 應用服務 (Application Service) 實作
1. 定義介面 (Contracts)
介面定義在 Application.Contracts 專案中。這允許我們將介面分享給客戶端 (如 Blazor WebAssembly 或其他微服務),而無需分享實作。
public interface IBookAppService : IApplicationService
{
Task<BookDto> GetAsync(Guid id);
Task<PagedResultDto<BookDto>> GetListAsync(GetBooksInput input);
Task<BookDto> CreateAsync(CreateUpdateBookDto input);
Task UpdateAsync(Guid id, CreateUpdateBookDto input);
Task DeleteAsync(Guid id);
}2. 實作類別
繼承 ApplicationService (或 CrudAppService 以獲得預設 CRUD 實作)。
public class BookAppService : ApplicationService, IBookAppService
{
private readonly IRepository<Book, Guid> _bookRepository;
public BookAppService(IRepository<Book, Guid> bookRepository)
{
_bookRepository = bookRepository;
}
public async Task<BookDto> GetAsync(Guid id)
{
var book = await _bookRepository.GetAsync(id);
return ObjectMapper.Map<Book, BookDto>(book);
}
// ... 其他實作
}3. 使用 CrudAppService (快速開發)
對於標準的 CRUD,ABP 提供了 CrudAppService 基類,您甚至不需要寫任何程式碼!
public class BookAppService :
CrudAppService<Book, BookDto, Guid, PagedAndSortedResultRequestDto, CreateUpdateBookDto>,
IBookAppService
{
public BookAppService(IRepository<Book, Guid> repository) : base(repository)
{
}
}這會自動實作 Get, GetList, Create, Update, Delete 並包含權限檢查、驗證與映射。
11.5 實戰練習
練習 1:設計 DTO
- 為
Author實體設計 DTOs:AuthorDto,CreateAuthorDto,UpdateAuthorDto。 AuthorDto應包含ShortBio屬性,這是一個計算屬性 (取 Bio 的前 50 字元)。
練習 2:自訂映射
- 使用 Mapperly 定義
Author到AuthorDto的映射方法。 - 使用
[MapProperty]或自訂方法實作ShortBio的邏輯。
練習 3:實作 CRUD
- 使用
CrudAppService快速實作AuthorAppService。 - 覆寫
CreateAsync方法,在建立前檢查作者是否已存在 (呼叫 Repository)。
11.6 總結
本章介紹了應用層的設計模式。
- DTO 保護了領域模型並優化了資料傳輸。
- ObjectMapper 自動化了繁瑣的轉換工作。
- ApplicationService 定義了系統的公開行為。
掌握這些,您就能構建出結構清晰、易於維護的 API。下一章,我們將進入 使用者介面 (User Interface),看看如何將這些 API 呈現給使用者。
參考資源: