有了前面篇章對於 Entity、Value Object 以及 Aggregate 的介紹,我們已經裝備好建構 Domain 層的武器庫,接著來介紹分層式架構的第二層,應用程式層,又被稱為使用案例層。
在這一層,你可以很清楚的看到整個應用程式需要完成的任務有哪些,甚至你可以將這一層可以作為你寫程式的起點。
在使用應用需要注意幾件事情:
本系列會用 3 篇來介紹,一方面介紹 Application Service 的應用,另一方面也會示範如何將 TDD 與 BDD 的概融入現代式分層式架構 (嚴格來說,是 Clean Architecture) 之中。
讓我們再次複習一下在 DDD 中使用的現代式分層式架構圖:
從上圖我們可以看到,Application Service 擔任著居中的角色,因此我們要先來釐清到底哪些是 Application Service 該做的,哪些是他不該做的。
而設計 Application Service 時,我將採用 Clean Architecture 所介紹的「尖叫的架構 (Screaming Architecture)」的風格。他的精神就是「當你看完專案的資料夾結構後,你就可以大概知道這個系統的功能了」。因此,我會把每個使用案例都單獨建立一個檔案,並且每一個檔案都有一個共通的規範:
execute(input): output
method,吃進 Input 吐出 Output。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 都沒有一個統稱的名字,實際使用起來也是很彈性,所以我就先以現代式分層式架構來統稱他們。若是這樣的命名有問題也請多指教。