Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
20230916 - DDDTW - 導入 Domain-Driven Design 的最佳時機
Search
蒼時弦や
October 05, 2023
Programming
480
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
20230916 - DDDTW - 導入 Domain-Driven Design 的最佳時機
蒼時弦や
October 05, 2023
More Decks by 蒼時弦や
See All by 蒼時弦や
2024 - COSCUP - Clean Architecture in Rails
elct9620
2
210
2023 - RubyConfTW - Rethink Rails Architecture
elct9620
0
240
2023 - WebConf - 選擇適合你的技能組合
elct9620
0
690
20230322 - Generative AI 小聚 ft. Happy Designer
elct9620
0
470
2022 - 默默會 - 重新學習 MVC 的 Model
elct9620
1
510
MOPCON 2022 - 從 Domain-Driven Design 看網站開發框架隱藏
elct9620
1
540
2022 - COSCUP - 我想慢慢寫程式該怎麼辦?
elct9620
0
300
2022 - COSCUP - 打造高速 Ruby 專案開發流程
elct9620
0
330
2021 - RubyKaigi - It is time to build your mruby VM on the microcontroller?
elct9620
0
300
Other Decks in Programming
See All in Programming
ふつうのFeature Flag実践入門
irof
7
3.7k
JJUG CCC 2026 Spring: JSpecify で実現する Kotlin フレンドリーな Java API 設計
ternbusty
1
160
jQueryをバージョンアップする前に使いたいjQuery Migrate
matsuo_atsushi
0
200
Developing with AI Agents — Codex, Claude Code & Cowork Practical Guide
x5gtrn
PRO
0
1.2k
Spec Driven Development | AI Summit Lisbon
danielsogl
PRO
0
180
肥大化するレガシーコードに立ち向かうためのインターフェース分離と依存の逆転 / JJUG CCC 2026 Spring
hirokunimaeta
0
530
Signal Forms: Beyond the Basics @ngBaguette 2026 in Paris
manfredsteyer
PRO
0
240
Contextとはなにか
chiroruxx
0
290
Composerを使ったサプライチェーン攻撃の様子を眺めてみる #phpstudy
o0h
PRO
2
240
技術記事、 専門家としてのプログラマ、 言語化
mizchi
4
1.8k
Why Laravel apps break—Mastering the fundamentals to keep them maintainable
kentaroutakeda
1
350
New "Type" system on PicoRuby
pocke
1
820
Featured
See All Featured
[SF Ruby Conf 2025] Rails X
palkan
2
1.1k
How to optimise 3,500 product descriptions for ecommerce in one day using ChatGPT
katarinadahlin
PRO
1
3.6k
First, design no harm
axbom
PRO
2
1.2k
Applied NLP in the Age of Generative AI
inesmontani
PRO
4
2.3k
AI Search: Implications for SEO and How to Move Forward - #ShenzhenSEOConference
aleyda
1
1.3k
Ten Tips & Tricks for a 🌱 transition
stuffmc
0
130
My Coaching Mixtape
mlcsv
0
140
How To Speak Unicorn (iThemes Webinar)
marktimemedia
1
480
How to Build an AI Search Optimization Roadmap - Criteria and Steps to Take #SEOIRL
aleyda
1
2.1k
Chasing Engaging Ingredients in Design
codingconduct
0
220
Product Roadmaps are Hard
iamctodd
PRO
55
12k
How to build a perfect <img>
jonoalderson
1
5.6k
Transcript
導入 Domain-Driven Design 的最佳時機 The best time to use Domain-Driven
Design in your project
蒼時弦也 Software Developer https://blog.aotoki.me
None
困難 敏捷 架構 導入
困難 敏捷 架構 導入
困難 敏捷 架構 導入
困難 敏捷 架構 導入
起點 為何開始領域驅動設計
Ruby on Rails 社群很少直接討論 Domain-Driven Design 自己在 2020 年之前,完全不知道有這一套理論
另一方面 Ruby on Rails 有不少「慣例」讓開發變得簡單的同時, 更難以將 Domain-Driven Design 的觀念放到裡面
慘痛經驗 因為設計失誤造成問題
2019 年參與一套非常複雜的系統開發,業主說資料表都設計好,只 要解決他們最複雜客戶其他就能支援,結果完全無法支援其他客戶
在沒有了解使用者需求下設計系統代價極大,這次慘痛的經驗讓自 己下定決心更關注系統設計的議題
挑戰 實踐過程中要克服的事情
Knowledge 純自學的前提,從讀完書到能夠熟練應用,大致上花費兩年時間實作、補充知識
Team 說服團隊使用需要所有人都具備知識,並且積極的進行討論才有機會成功
Legacy Code 想要修改現有系統,在大型專案中很容易互相影響,要先切出邊界
Meetings Event Storming 總是進行很久,總是有考慮不完的問題
Standard Rails 沒有官方標準和正式寫法,Golang 能找到三種以上的實作樣板
想要一次性解決問題非常困難,需要漸進式的把問題解決才有機會
敏捷 小增量的進行改進
Epic 1 - Q1 Epic 2 - Q2 過去在規劃專案時,時間較長範圍也較多,做系統分析時就會很費時
Epic 1 - Q1 Epic 2 - Q2 Feat 1
- W1 Feat 2 - W3 Feat 3 - W1 Feat 4 - W2 從敏捷的角度,我們把一次改版切割成以功能單位來看
Epic 1 - Q1 Epic 2 - Q2 Feat 1
- W1 Feat 2 - W3 Feat 3 - W1 Feat 4 - W2 Feat 5 - W3 也可以很彈性對應變化,調整某個功能的優先順序
當需要討論範圍變小,耗費時間的設計會議就能夠縮短時間
重構 當一切快速進行,必有代價
快速迭代必然會需要持續的重構,我們最終實作跟預測差異多少, 是否更有價值?
Event Storming 我們使用 Event Storming 對系統開發的幫助是什麼?
透過 Event Storming 能夠幫助我們全面的了解系統,避免在開發 過程中失去控制
然而,在快速變化、有許多不確定性的情境中,還能在(前期)有 幫助嗎?
敏捷開發要能夠成立所需的條件非常多,也包括工程師的自律以及 需要有一定水準的能力等問題
規格 透過約定確保底線
利用「驗收」規格來確保成果是可預期的,讓 User Interface 跟使 用者、客戶期待的一致
UI Application 即使系統實作完全失控,至少使用者還能順利操作跟使用
Command Process / System Event 從 Event Storming 的角度看是很初期的實現,然而關鍵的 Command
/ Event 有被找出來
我們是否能夠將 Event Storming 分階段進行,是否可以從 User Story 中推導出 Event Storming 的內容?
介面 保持系統的彈性
經常跟 Domain-Driven Design 一起談的是 Clean Architecture, 這背後是如何保持彈性的技巧
Input Function Output 在生活經驗中,我們大多可以意識到一個動作會有「輸入」跟「輸出」成對
Command Process / System Event 對應到 Event Storming 上也可以得到相同的結構
// Golang // ... package type interface error error {
(context.Context) (context.Context) } daemon Service Start Stop
// Golang package type struct func * error return func
* error return { http.Server } (s HttpServer) (ctx context.Context) { s. () } (s HttpServer) (ctx context.Context) { s. (ctx) } main HttpServer Start Stop ListenAndServe Shutdown
// Golang // ... package import func := & &
if := != ( ) () { d daemon. ( HttpServer{ Addr: }, GrpcServer{ Addr: }, ) err d. (context. ()); err { (err) } } main myapp/pkg/daemon main " " ":8080" ":8081" New Run Background nil panic
在這個例子,我們約定了 Daemon 會使用 Service 介面來啟動服 務,如此一來我們只要符合條件就能夠被 Daemon 使用
Scenario When Then : I click I can see When
user click "Toggle" button and show "Hello" "Toggle" "Hello" # 介面:Button() # 輸入:ClickEvent(Label="Toggle") # 輸出:String("Hello")
從文件、規格的角度來看,我們實際上都是在定義介面。
架構 有了介面就能劃分出邊界
介面反應了邊界(Boundary)因此我們有了架構上的邊界,或者在 Domain-Driven Design 中的上下文(Context)邊界
Presenter Application Domain Model 從 Layered Architecture 作為例子,每一層之間都有一個約定的介面存在
User HTTP Protocol Presenter 以網站來說,使用者跟 Presenter 約定的介面可能會是 HTTP 協定
Presenter User Flow Application Presenter 跟 Application 共同約定的介面是使用者流程
Application Business Logic Domain Model Application 跟 Domain Model 共同約定的介面是商業邏輯
介面有點像是「黏著劑」想要將金屬跟玻璃緊密的黏在一起,就需 要使用正確的黏著劑,軟體系統也是類似的
然而,如果黏著劑想要對應多種材質就很可能造成不牢固、難以清 理等問題(跨層的依賴)
依賴 用介面來控制依賴
想實現快速迭代的目標,就需要容易重構、擴充,那麼將物件的耦 合(依賴)控制在最小就變得很重要
Presenter Application Domain Model Infrastructure 以 Layered Architecture 常見的介紹方式,很難說明 Infrastructure
的依賴關係
Presenter Application Domain Model Infrastructure 我認為這張圖的樣子更加合理一些,然而 Domain Model 該依賴其他物件嗎?
Presenter Application Infrastructure Domain Model 在我的經驗裡面,Domain Model 是沒有依賴的
大多數時候,只要遵循「只依賴相鄰的類型」以及「保持單向依 賴」兩個原則,加上善用介面,大多能在必要時很好的進行抽換
導入 有策略地進行應用
在經驗中,初期就開始引入 Domain-Driven Design 的概念會讓專 案更容易維護,然而有許多限制,因此需要轉換成工作上的原則
案例 近期的案例分享
這是一個活動的報到 App 後端,當參加者查詢狀態時會顯示活動資 訊,並且紀錄上一次使用的時間
Attendee Get Status AttendeeInfo GET /status?token=[TOKEN]
Attendee Get Status AttendeeInfo Actor 是 Attendee,透過 `token` 參數識別
Attendee Get Status AttendeeInfo 我們要實作一個功能,查詢以及更新資訊
Attendee Get Status AttendeeInfo 回傳的結果是 AttendeeInfo 並且包含「名稱」和「票種」
#language:zh-TW 功能 場景 假設 | token | name | type
| | 1234567890 | 蒼時 | 一般票 | 當 那麼 : : 有一張票券 我發出 GET 請求到 我會看到 JSON 格式的回應 票券資訊 當查詢票券資訊時,可以看到名稱和票種 "/status?token=1234567890" """ { "name": "蒼時", "type": "一般票" } """
Presenter Application Infrastructure Domain Model 從 Presenter(使用者介面) 開始處理
interface extends : interface : : export async function :
: return { ; } { ; ; } ( ) < > { { name: , type: , }; } AttendeeInfoRequest HttpRequest AttendeeInfoResponse statusHandler AttendeeInfoRequest Promise AttendeeInfoResponse token name type request string string string // ... '蒼時' '一般票'
Presenter Application Infrastructure Domain Model 接著再處理 Application(流程)的機制
interface : : & export async function : : const
= const = await return { ; } HttpRequest ( ) < > { { , } request; attendeeUsecase. (token); { name: attendee.name, type: attendee.type, } } AttendeeInfoRequest AttendeeUsecase statusHandler AttendeeInfoRequest Promise AttendeeInfoResponse getAttendee token attendeeUsecase request string token attendeeUsecase attendee // ... // ...
interface : : export class async : : return {
; ; } { ( ) < > { { name: , type: , }; } } AttendeeInfo AttendeeUsecase getAttendee Promise AttendeeInfo name type token string string string // ... '蒼時' '一般票'
Presenter Application Infrastructure Domain Model 根據分析的結果,實作 Domain Model(領域模型)必要的部分
// ... // ... export class public readonly : public
readonly : private ?: constructor : : = = = new { ; ; ; ( , ) { .name name; .type type; } () { ._lastUsedAt (); } } Attendee AttendeeType Date AttendeeType touch name type _lastUsedAt name type string string this this this Date
Presenter Application Infrastructure Domain Model 進一步把 Domain Model 提供的功能加入到 Use
Case 中
export class private : async : : const : =
await await return { ; ( ) < > { .attendees. (token); attendee. (); .attendees. (attendee); { name: attendee.name, type: attendee.type, }; } } AttendeeUsecase AttendeeRepository getAttendee Promise AttendeeInfo Attendee findByToken touch save // Next // ... attendees token string attendee this this
Presenter Application Infrastructure Domain Model 將底層依賴最後再做判斷,也能減少實作受到底層依賴的限制(如:資料表)
type = : : export class async : : const
= return new { ; ; }; { ( ) < > { .database . ( , [token]) . < >(token); ({ name: res.name, type: res.type, }); } } AttendeeSchema PostgresAttendeeRepository findByToken Promise Attendee prepare first AttendeeSchema Attendee name type token string number string res this // ... '[SQL]'
AttendeeInfoRequest / AttendeeInfoResponse Application Infrastructure Domain Model Presenter 的介面是 API
Interface 的定義
AttendeeInfoRequest / AttendeeInfoResponse getAttendee(token) / AttendeeInfo Infrastructure Domain Model Application
的介面是 UseCase 的方法和回傳
AttendeeInfoRequest / AttendeeInfoResponse getAttendee(token) / AttendeeInfo Infrastructure Attendee Domain Model
的介面是 Entity、Service 等等物件
AttendeeInfoRequest / AttendeeInfoResponse getAttendee(token) / AttendeeInfo AttendeeRepository Attendee Infrastructure 的介面會照
Presenter / Application 需要而定, 沒辦法契合時則會實作 Adapter 來對應
綜合敏捷、Clean Architecture 和 Domain-Driven Design 的優 點,在一些地方作出讓步,能得到不錯的平衡
從另一個角度來看,開發初期加入 Domain-Driven Design 似乎會 多耗費一點開發的時間,然而隨著架構完善實作的速度反而能加快
開發初期就開始導入跟敏捷並不太衝突,需要的是視情況而定做一 些取捨跟調整,反覆檢查是否有偏離即可
Blog https://blog.aotoki.me 歡迎關注我的部落格,目前週更的主題都圍繞在今天的分享內容上