過去十年軟體工程界最昂貴的集體狂熱,莫過於對「微服務架構(Microservices Architecture)」的盲目崇拜:
無數團隊在業務規模只有百萬級流量、研發人員只有十幾個人的情況下,僅僅讀了幾篇 Netflix 或 Uber 的高併發分享,就急匆匆地把原本健康的單體應用,硬生生切成了二十幾個獨立的微服務。
幾年下來,團隊迎來的不是彈性與高吞吐,而是一場全方位的工程災難:
- 本地開發時,工程師必須在筆電上跑 20 個 Docker 容器,風扇狂轉、記憶體爆滿,啟動一次要花 15 分鐘;
- 簡單的業務修改,需要跨四個不同的 Git Repository 同時發 PR,部署時因順序錯誤導致線上一再報錯;
- 原本在單體內只需要 1 毫秒的記憶體指針調用,變成了經過 API Gateway、服務發現、JSON 序列化與 TCP 握手的 30 毫秒 RPC 網路請求;
- 為了解決跨服務的一致性,團隊不得不引入難以維護的 Saga 分散式交易或兩階段提交(2PC),最後在排查對帳單時流乾眼淚。
業界給這種畸形怪物起了一個貼切的名字:分散式單體(Distributed Monolith)——既承受了單體時代所有緊耦合的痛苦,又背負了分散式系統的所有運維代價。
這個災難的起點,本質上源於對領域驅動設計(DDD)核心概念的重大誤讀:很多人誤以為「一個限界上下文(Bounded Context),就必須是一個獨立部署的微服務」。
觀念糾偏:限界上下文 ≠ 微服務
Eric Evans 在提出 DDD 時,從未將「限界上下文」與「實體網路進程」畫上等號。
- 限界上下文(Bounded Context)是「語義邊界」: 它是領域模型的生命週期與語言契約的範圍,決定的是代碼的心理模型與認知邊界。
- 微服務(Microservice)是「實體部署單元」: 它決定的是伺服器進程、網路拓撲與獨立發布管線。
你完全可以在同一個單體應用(Monolith)、同一個作業系統進程、甚至同一個資料庫實例中,切分出四個壁壘分明、內部高內聚、邊界零依賴的限界上下文。
這就是近年來頂級架構圈(如 Shopify、GitHub、Basecamp)全面回歸的架構範式:模組化單體(Modular Monolith)。
在模組化單體中:
- 零網路傳輸開銷: 上下文之間的協同透過進程內的記憶體直接調用,呼叫延遲是納秒級(nanoseconds),徹底告別網路連線超時與重試風暴。
- 極致的開發體驗(DX): 一個
git clone、一條指令即可在本機跑起整套系統,單元測試在幾秒鐘內執行完畢。 - 保留演進主動權: 如果某個子域(如高併發動態定價)未來真的面臨極端吞吐瓶頸,因為它的邊界早已具備 100% 的自治純度,抽離成獨立微服務只需花費三天,而不是進行傷筋動骨的半年度重構。
但此時工程師會問:如果大家都在同一個代碼庫裡,如何保證菜鳥工程師或 AI Coding Agent 不會私自隨手跨目錄 import 破壞邊界?
答案不是靠道德自律,而是靠三層鋼鐵護欄。
護欄一:編譯期物理隔絕(Compile-Time Isolation)
進程內隔離的第一道防線,是利用語言編譯器本身的特性,在靜態分析階段將私有代碼鎖死在模組內部。
1. TypeScript Project References(專案參照硬邊界)
在 Node.js / TypeScript 生態中,絕大多數團隊只用目錄分類(如 src/modules/billing),這給了代碼隨意穿透的機會。
正確的做法是將每個限界上下文視為一個獨立的 TS 專案:
// packages/billing/tsconfig.json
{
"compilerOptions": {
"composite": true,
"declaration": true,
"rootDir": "src",
"outDir": "dist"
},
"references": [
// 嚴格限定只能引用身份模組暴露的型別,不能引用倉儲模組
{ "path": "../identity" }
]
}
2. 嚴格暴露公開介面(Public Seam)
每個上下文目錄下,只有根目錄的 index.ts 是合法的對外窗口,所有內部實體(如 CustomerInternalState.ts)嚴禁直接被外部引用:
src/contexts/billing/
├── index.ts <-- ✅ 唯一公開 API (Facade / Public DTO)
├── internal/
│ ├── Customer.ts <-- ❌ 外部嚴禁直接引用
│ ├── BillingRepository.ts <-- ❌ 外部嚴禁直接引用
│ └── TaxEngine.ts
└── tsconfig.json
在 Go 語言中,這項機制更加天然——直接將私有邏輯置於 internal/ 目錄下,Go 編譯器會在語法層面直接阻斷任何跨模組的非法訪問。
護欄二:架構即 Prompt(AI Coding Agent 語義圍欄)
進入 2026 年,軟體開發最大的新變數是 AI Coding Agent(如 Claude Code、Gemini CLI 等)。
AI Agent 具備驚人的代碼生成速度,但它們本能地傾向於走「阻力最小的路徑」:當 Agent 發現當前函式缺少一個使用者名稱時,它會毫不猶豫地在檔案頂端加上:
// ❌ AI Agent 的本能衝動:隨意跨模組深層引用私有實體
import { UserEntity } from "../../identity/internal/UserEntity";
在一夜之間,AI 能幫你寫出幾千行代碼,但同時也把你的架構降級成了無法挽回的「大泥球(Big Ball of Mud)」。
為了約束 Agent 的行為,我們必須將戰略架構轉譯為 Agent 能理解的圍欄規範——在每個限界上下文目錄下放置專屬的 BOUNDARY.md:
<!-- src/contexts/billing/BOUNDARY.md -->
# Billing Context 邊界隔離憲章
## 1. 模組自治範圍
本模組負責所有帳務、發票統編與支付授權邏輯。核心實體為 `Customer`。
## 2. 絕對禁止的跨模組依賴 (Anti-Patterns)
- ❌ 嚴禁 import `../identity/internal/*` 或 `../fulfillment/*` 下的任何代碼。
- ❌ 嚴禁在資料庫中對 `identity_accounts` 建立物理外鍵 (Foreign Key)。
- ❌ 嚴禁直接調用其他上下文的 Repository 或 Service。
## 3. 合法的外部溝通途徑
- ✅ 外部僅能透過 `src/contexts/billing/index.ts` 暴露的 `BillingFacade` 進行調用。
- ✅ 引用其他上下文實體時,只能使用強型別純 ID(例如 `AccountId`)。
- ✅ 跨上下文狀態同步必須透過非同步領域事件 (`DomainEvent`) 訂閱。
這份文件就是 AI Agent 的認知圍欄。當 Agent 在該模組執行任務時,系統會強制將其載入 Context,從生成源頭消滅跨界引用的念頭。
護欄三:CI 自動化執法(零信任架構門面)
「防君子不防小人,防人類不防幻覺。」無論文檔寫得多麼詳盡,沒有自動化檢查的邊界都是紙老虎。
我們必須在 CI/CD Pipeline 與 Git Pre-push Hook 中引入架構守門人工具,例如 Dependency-Cruiser:
1. 宣告架構拓撲規則(.dependency-cruiser.cjs)
// .dependency-cruiser.cjs
module.exports = {
forbidden: [
{
name: "no-cross-context-internal-imports",
comment: "嚴禁任何模組跨越界線引用其他上下文的 internal 私有檔案",
severity: "error",
from: { path: "^src/contexts/([^/]+)/" },
to: {
path: "^src/contexts/(?<otherContext>[^/]+)/internal/",
pathNot: "^src/contexts/\\1/", // 允許自己引用自己的 internal
},
},
{
name: "no-circular-context-dependencies",
comment: "嚴禁上下文之間存在任何形式的循環依賴",
severity: "error",
from: { path: "^src/contexts/([^/]+)/" },
to: {
path: "^src/contexts/[^/]+/",
circular: true,
},
},
{
name: "core-domain-must-not-depend-on-infrastructure",
comment: "核心領域層嚴禁依賴基礎設施層",
severity: "error",
from: { path: "^src/contexts/([^/]+)/domain/" },
to: { path: "^src/contexts/([^/]+)/infrastructure/" },
},
],
};
2. CI 阻斷閘門(GitHub Actions)
在 CI 工作流中,這項檢查擁有「一票否決權」:
# 只要有任何工程師或 AI Agent 違規跨模組引用,立即以 Exit Code 1 阻斷 PR
pnpm depcruise --config .dependency-cruiser.cjs src
當 AI Agent 提交了一個自作聰明跨目錄引用的 PR,CI 會在 10 秒內報錯並精確標出違規行數。這種機械級的冷酷執法,才是確保模組化單體在三年後依然井然有序的終極保證。
前四篇工具文建立的邊界
《DDD 戰略設計實戰:從業務邊界到系統架構》前四篇工具文先建立了從領域到系統邊界的判斷順序,之後再由案例篇檢驗這些工具:
- 子域劃分(Subdomains): 用「開源即倒閉測試」找出核心業務護城河,將 70% 的工程預算鎖死在刀刃上;
- 限界上下文(Bounded Context): 用四大工程訊號(語義、步調、團隊、交易)揮動手術刀,將臃腫危險的 God Object 解構為純淨的多維模型;
- 上下文映射與防腐層(ACL): 認清組織權力拓撲,在外部不可靠上游前築起「Facade → Adapter → Translator」三道海關,絕不被動順從;
- 模組化單體(Modular Monolith): 破除微服務迷思,用進程內硬隔離、Prompt 圍欄與 CI 拓撲執法,在享受單體極致開發速度的同時,具備微服務等級的自治能力。
架構的選擇不應由服務數量決定,而應由邊界要解決的問題決定。 若你的問題是 AI 變更太大、設計審查跟不上,可以接著讀《AI 寫碼變快,微服務能隔離故障,不能替你審查程式》;它把合併前的變更品質和上線後的故障隔離拆成兩項獨立決策。
