領域驅動設計實踐:支付系統建模
在 Airwallex,領域驅動設計(DDD)方法被用來指導如何對複雜的業務問題和系統設計進行建模。
在這篇博客中,我們試圖全面介紹用 DDD 模式對支付系統進行建模的做法。
簡介
支付系統是一個相當複雜和多變的系統,從訂單、欺詐、通知、與各種支付方式的整合到資金清算和結算,涉及面很廣。
在處理一個複雜的系統時,大多數開發人員可能會遇到一些問題
-
邊界和責任不明確,只是一個有許多模型和業務邏輯的大應用程序。
-
沒有隔離和模塊化:複雜的業務工作流和流程是混合的,難以擴展。
-
沒有關注點的分離:核心業務邏輯與技術實現細節混在一起。
軟件行業中的許多設計模式都能解決這些問題,在 Airwallex,我們嘗試採用領域驅動設計(DDD)的方法來爲我們的支付系統建模,以管理系統設計中的複雜性。
什麼是 DDD
領域驅動設計(DDD)是由埃裏克 - 埃文斯(Eric Evans)提出的,它是一套思想、原則和模式,有助於根據業務領域的基礎模型設計軟件系統。DDD 有兩個不同的空間:問題空間和解決方案空間。
在問題空間,你是用戰略模式來定義系統的大規模結構,它專注於分析一個領域、子領域和泛在語言。
而在解決方案空間中,採用戰術模式來提供一套設計模式,你可以用它來創建領域模型。這些模式包括有界的上下文、上下文映射、實體、聚合體、領域事件、領域服務、應用服務和基礎設施。這些戰術模式將幫助你設計既鬆散耦合又有凝聚力的微服務。
如何在實踐中應用 DDD
想象一下,有這樣一個場景:
-
一位顧客想在商家的網站上購買一件 T 恤,價格是 10 美元。
-
顧客可以用各種支付方式來支付這件 T 恤,如 Visa 卡或微信錢包。
-
客戶付款後,商家可以從支付網關獲得通知,這樣他們就可以向客戶展示付款成功的頁面。
-
商戶可以在 Airwallex Webapp 中查看付款詳情,這樣他們就可以知道這件 T 恤可以獲得多少資金,Airwallex 扣除了多少費用,以及資金何時會被結算到他的 Airwallex 錢包。
將遵循以下步驟,應用 DDD 對基於上述場景的支付系統進行建模。
-
分析現實世界中的業務用例,以獲得問題空間中的域和子域。通常,在這個階段,Event Storming 是一個很好的工具。
-
定義解決方案空間中的有界上下文
-
在有界限的上下文中,應用戰術性 DDD 模式來定義實體、聚合、領域服務、領域事件等。
-
使用上一步的結果來確定你的團隊中的微服務。
以下是分析結果。
問題空間
- 領域
支付系統
- 子域
-
支付處理:商家可以通過各種支付方式接受客戶的付款
-
金融:對商家的支付資金進行清算和結算。
- 通用語言
在與領域專家討論後,以下是所有團隊接受的通用語言。
-
支付意圖:商家創建的訂單,指定價格、產品、客戶等。
-
付款企圖:商家創建的交易,以接受客戶對特定訂單的付款。
-
付款方式:客戶爲產品或服務付款的方式。
-
付款結算:一批結算到商家錢包的付款。
-
付款視圖:一個聚合的付款細節視圖,包含與一個付款有關的所有數據。
解決方案空間
- 有界上下文
有界上下文(BC)限定了一個領域模型的範圍。從問題空間的分析結果來看,我們可以定義以下有界上下文。
-
支付網關:API 網關,爲商戶提供可靠的 API,以創建或查看付款。
-
支付核心:支付意圖、嘗試、方法資源管理。
-
支付適配器:與一個外部 PSP(微信 / 支付寶 / Visa/Mastercard 等)集成。
-
支付結算:爲商戶計算和結算每筆支付的原則和費用。
-
支付融合:支付細節的聚合視圖。
而上下文地圖將是這樣的:
- 領域模型
從上面我們分析的場景和無所不在的語言中,我們可以確定以下聚合、實體、價值對象和領域事件。
- 領域服務
在我們的實踐中,域服務是爲一個聚合體提供的無狀態業務邏輯服務,遵循單一責任模式。通常情況下,我們會在領域服務中封裝領域倉庫、聚合變化和領域事件發佈。以 PaymentAttemptExecutorService 爲例。
- 領域事件
領域事件可以使系統更具可擴展性,並避免任何耦合 -- 一個聚合體不應該決定其他聚合體應該做什麼,以及時間耦合 -- 付款的成功完成並不取決於所有進程在同一時間可用。
例如,當 PaymentCaptureCommand 將支付狀態改爲已支付時,領域事件 PaymentAttemptCapturedEvent 被髮送,以通知聚合的 PaymentAttempt 被捕獲。在 PaymentAttemptCapturedEvent 的領域事件處理程序中,我們可以把副作用放在業務邏輯上,比如通知支付融合的邊界上下文來更新支付細節和支付結算的邊界上下文來計算結算金額和費用。
- 基礎設施
在 DDD 模式中,基礎設施層被用來將核心業務領域與技術實現細節分開。通常,該層採用反污層(ACL)模式。以領域存儲庫爲例。
領域倉庫只定義了接口,比如他們能做什麼,但實現細節應該隱藏在基礎設施層裏面,比如使用 PostgreSQL 或 MongoDB 來保存數據。例如,在基礎設施層,PaymentAttemptPgRepository 是基於 PostgreSQL 的具體實現,toPO 是用於將域對象 PaymentAttempt 轉換爲持久化對象的映射器。
因此,在領域層,我們只關注領域模型,它與基礎設施技術完全脫鉤。當基礎設施層有任何變化時,不需要在領域層中進行改變。
從領域模型到微服務
現在,我們已經爲支付系統定義了一組有邊界的上下文,並在每個有邊界的上下文中確定了一組實體、集合體和領域事件服務。
下一步就是要從領域模型到應用微服務的設計。
在這裏,我們選擇將一個有界上下文映射到一個微服務。
結論
在這篇博客中,當我們試圖對支付系統進行建模時,我們觸及了領域驅動設計(DDD)模式的各種概念和策略。採用 DDD 可以提供許多好處,例如,在所有的團隊中進行清晰的溝通,以及在設計系統時提供一個成熟的模式來管理複雜性和提供更好的可擴展性。
-
有了無處不在的語言,我們可以實現更多的自我描述的類名和函數名。
-
通過聚合模式,我們可以實現清晰的邊界和單一的責任。
-
通過領域事件模式,我們可以將核心業務流程與聚合體上的副作用分開。
-
通過基礎設施層和 ACL 模式,我們可以將核心業務領域模型與技術實現細節分開。
-
通過有邊界的上下文模式,我們可以推導出潛在的微服務候選人。
DDD 模式是一個龐大的話題,我認爲我們做得還不夠充分,無法全面解釋它們,但我們想介紹一些關鍵的話題和我們實踐該模式的經驗。在未來,我們將繼續深入研究 DDD 模式中的每一個主題,如層管理、領域事件存儲、上下文映射模式等。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/CBgg5FaKjkiGsR7z4Vu5Rg