5.1 大應用拆小應用:業務邊界與 DDD 簡介#
單體撐不住了#
第四章我們把資料庫拆了,但應用層還是「一個大 War 包」。雙十一前夕,這個大應用的痛點全部爆發:改一行促銷邏輯,全站要一起重新部署;商品模組 CPU 吃滿,訂單、會員也被拖慢;上百人擠在同一個程式碼庫,合併衝突天天上演。
flowchart TD
subgraph MONO[單體大應用:一個 War 包]
A[商品] --- B[訂單]
B --- C[會員]
C --- D[促銷]
D --- A
end
MONO -->|改一處、全量重啟| RISK[發布風險高]
MONO -->|一處慢、全部慢| SLOW[資源互相干擾]這時淘寶的選擇不是「把機器加更大」,而是「把應用拆更小」:按業務邊界做垂直拆分。
怎麼拆:跟著業務邊界走#
拆分的原則只有一句話:高內聚、低耦合的,不再是類別,而是應用。
- 按業務領域拆:商品中心、交易中心、會員中心、促銷中心各自獨立部署。
- 拆完各有自己的程式碼庫、自己的資料庫(呼應 4.2 節垂直分庫)。
- 應用之間不再直接調用 DAO,只能走網路介面。
flowchart LR
GW[前端 / Nginx] --> P[商品中心]
GW --> T[交易中心]
GW --> M[會員中心]
P -. RPC .-> T
T -. RPC .-> M| 拆分方式 | 做法 | 缺點 |
|---|---|---|
| 按技術層拆 | Web 層、Service 層各一包 | 跨層呼叫頻繁,沒解決耦合 |
| 按業務邊界拆 | 商品、交易、會員各一應用 | 初期要處理跨應用調用 |
| 按團隊拆(反向) | 一個團隊一應用 | 若邊界錯,團隊互相等待 |
DDD 限界上下文:拆分的尺#
怎麼判斷邊界對不對?領域驅動設計(DDD) 給了一把尺:限界上下文(Bounded Context)。
同一個詞,在不同上下文意義不同,就是拆分訊號:
- 商品上下文的「訂單」:只是銷量統計。
- 交易上下文的「訂單」:價格、優惠、支付狀態、履約的核心實體。
// 商品上下文:輕量的 OrderSnapshot
class OrderSnapshot {
Long orderId;
Long itemId;
int count;
}
// 交易上下文:完整的 Order 聚合根
class Order {
Long orderId;
Money payAmount;
OrderStatus status;
List<OrderItem> items;
void pay() { /* 狀態機 + 不變條件 */ }
}
實務做法:先畫上下文映射圖,再決定拆幾個應用。初期寧可少拆(3~5 個),跑順了再細拆。過早拆成幾十個微服務,只會提前掉進 6.4 節的運維地獄。
拆分的三條紀律#
- 獨立部署:各應用有自己的發布節奏,促銷天天發版不影響交易。
- 獨立資料:不允許跨庫 Join,要數據就調介面。
- 顯式介面:依賴關係寫在介面上,而不是偷連資料庫。
本章小結#
- 大應用拆小的動力:發布風險、資源干擾、團隊協作三重壓力。
- 正確拆法是按業務邊界垂直拆分,而非按技術層拆。
- DDD 限界上下文是判斷邊界的工具:一詞多義處即是邊界。
- 紀律:獨立部署、獨立資料、顯式介面。
- 拆完的新問題:應用間如何高效調用?下一節用 RPC 回答。
想一想#
- 為什麼「按技術層拆分」解決不了單體的根本問題?它和「按業務拆」差在哪裡?
- 舉一個電商例子,說明同一個「商品」在不同限界上下文中有什麼不同含義?
- 如果一開始就拆成 50 個微服務,可能帶來什麼代價?為什麼建議先少拆?