Skip to content

第十一章:應用層設計 (DTOs & Object Mapping)

11.1 引言:應用層的職責

應用層 (Application Layer) 是軟體的「門面」。它不包含核心業務邏輯 (那是領域層的事),而是負責:

  1. 定義使用案例 (Use Cases):例如「建立訂單」、「取得書籍列表」。
  2. DTO 轉換:將領域物件轉換為適合前端顯示的 DTO。
  3. 協調:呼叫領域服務、Repository、發送 Email 等。
  4. 權限與驗證:確保呼叫者有權限且輸入合法。

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
csharp
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] 屬性。

csharp
[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

csharp
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 或其他微服務),而無需分享實作。

csharp
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 實作)。

csharp
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 基類,您甚至不需要寫任何程式碼!

csharp
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

  1. Author 實體設計 DTOs:AuthorDto, CreateAuthorDto, UpdateAuthorDto
  2. AuthorDto 應包含 ShortBio 屬性,這是一個計算屬性 (取 Bio 的前 50 字元)。

練習 2:自訂映射

  1. 使用 Mapperly 定義 AuthorAuthorDto 的映射方法。
  2. 使用 [MapProperty] 或自訂方法實作 ShortBio 的邏輯。

練習 3:實作 CRUD

  1. 使用 CrudAppService 快速實作 AuthorAppService
  2. 覆寫 CreateAsync 方法,在建立前檢查作者是否已存在 (呼叫 Repository)。

11.6 總結

本章介紹了應用層的設計模式。

  • DTO 保護了領域模型並優化了資料傳輸。
  • ObjectMapper 自動化了繁瑣的轉換工作。
  • ApplicationService 定義了系統的公開行為。

掌握這些,您就能構建出結構清晰、易於維護的 API。下一章,我們將進入 使用者介面 (User Interface),看看如何將這些 API 呈現給使用者。


參考資源

Released under the MIT License.