iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 20
4
Software Development

Think in Domain-Driven Design系列 第 20

DDD 戰術設計:Application Service

  • 分享至 

  • xImage
  •  

DDD 戰術設計:Application Service

有了前面篇章對於 Entity、Value Object 以及 Aggregate 的介紹,我們已經裝備好建構 Domain 層的武器庫,接著來介紹分層式架構的第二層,應用程式層,又被稱為使用案例層。

在這一層,你可以很清楚的看到整個應用程式需要完成的任務有哪些,甚至你可以將這一層可以作為你寫程式的起點。

在使用應用需要注意幾件事情:

  1. Application Service 層可以操作 Domain Model 去完成業務需求,並把計算與業務邏輯交給 Domain Model。
  2. Application Service 層專注在系統的使用案例,並不在乎與外部系統的 IO 互動細節。那些細節將交給外面一層處理。

本系列會用 3 篇來介紹,一方面介紹 Application Service 的應用,另一方面也會示範如何將 TDD 與 BDD 的概融入現代式分層式架構 (嚴格來說,是 Clean Architecture) 之中。

Application Service 的工作範圍

讓我們再次複習一下在 DDD 中使用的現代式分層式架構圖:

https://ithelp.ithome.com.tw/upload/images/20191006/20111997mf5v8N9vOL.png

從上圖我們可以看到,Application Service 擔任著居中的角色,因此我們要先來釐清到底哪些是 Application Service 該做的,哪些是他不該做的。

而設計 Application Service 時,我將採用 Clean Architecture 所介紹的「尖叫的架構 (Screaming Architecture)」的風格。他的精神就是「當你看完專案的資料夾結構後,你就可以大概知道這個系統的功能了」。因此,我會把每個使用案例都單獨建立一個檔案,並且每一個檔案都有一個共通的規範:

  1. 有一個簡單資料結構的 Input (或稱 Request Model)
  2. 有一個簡單資料結構的 Output (或稱 Response Model)
  3. 有一個 Class 以使用案例做命名
  4. 呈上,那個 Class 必須實作 execute(input): output method,吃進 Input 吐出 Output。
  5. 使用這個 Application Service 的客戶端只能  使用 execute ˊ 這個 method。

於是我們可以先定義一個 Generic Interface 給 Application Service:

interface ApplicationService<Input, Output> {
  execute(input: Input): Promise<Output>;
}

接著依照 Generic Interface 建立一個 Application Service:

// applicationService/order/registerMember.ts

interface RegisterMemberInput {
  name: string;
  email: string;
  password: string;
}

// 要傳出去的 Data Transfer Object
interface MemberDto {
  name: string;
  email: string;
}

interface RegisterMemberOutput {
  success: boolean;
  member?: MemberDto;
  errorMessage?: string;
}

class RegisterMember
  implements ApplicationService<RegisterMemberInput, RegisterMemberOutput> {
  private memberRepo: MemberRepository;
  constructor(memberRepo: MemberRepository) {
    this.memberRepo = memberRepo;
  }

  async execute(input: RegisterMemberInput): RegisterMemberOutput {
    try {
      // delegate business logic to domain model
      const [error: string|undefinedd, member: Member] = Member.registerMember(input);

      if (error) {
        // handle domain errors
        return {
          success: false,
          errorMessage: error,
          member: undefined
        }
      }

      await this.memberRepo.add(member);

      const dto = {
        name: member.name,
        email: member.email
      };

      //
      return {
        success: true,
        member: dto,
        errorMessage: undefined
      };
    } catch (error) {
      return {
        success: false,
        member: undefined,
        errorMessage: 'Unexpected Error'
      }
    }
  }
}

註:其實這類架構不論是 Onion、Clean 還是 Port-Adapter Architecture 都沒有一個統稱的名字,實際使用起來也是很彈性,所以我就先以現代式分層式架構來統稱他們。若是這樣的命名有問題也請多指教。

Reference


上一篇
DDD 戰術設計:Repository 資源庫
下一篇
DDD 戰術設計:Application Service 2 - 結合 TDD/BDD
系列文
Think in Domain-Driven Design30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言