過去十年軟體工程界最昂貴的集體狂熱,莫過於對「微服務架構(Microservices Architecture)」的盲目崇拜:

無數團隊在業務規模只有百萬級流量、研發人員只有十幾個人的情況下,僅僅讀了幾篇 Netflix 或 Uber 的高併發分享,就急匆匆地把原本健康的單體應用,硬生生切成了二十幾個獨立的微服務。

幾年下來,團隊迎來的不是彈性與高吞吐,而是一場全方位的工程災難:

  • 本地開發時,工程師必須在筆電上跑 20 個 Docker 容器,風扇狂轉、記憶體爆滿,啟動一次要花 15 分鐘;
  • 簡單的業務修改,需要跨四個不同的 Git Repository 同時發 PR,部署時因順序錯誤導致線上一再報錯;
  • 原本在單體內只需要 1 毫秒的記憶體指針調用,變成了經過 API Gateway、服務發現、JSON 序列化與 TCP 握手的 30 毫秒 RPC 網路請求;
  • 為了解決跨服務的一致性,團隊不得不引入難以維護的 Saga 分散式交易或兩階段提交(2PC),最後在排查對帳單時流乾眼淚。

業界給這種畸形怪物起了一個貼切的名字:分散式單體(Distributed Monolith)——既承受了單體時代所有緊耦合的痛苦,又背負了分散式系統的所有運維代價。

這個災難的起點,本質上源於對領域驅動設計(DDD)核心概念的重大誤讀:很多人誤以為「一個限界上下文(Bounded Context),就必須是一個獨立部署的微服務」。

模組化單體(Modular Monolith)與 AI Agent 語義隔離防禦體系 頂部展示微服務迷思破除與架構收斂核心維度(進程內零網路開銷 vs 100% 硬隔離邊界)。中間三列由靜至動展示進程內隔離三大支柱:編譯期物理隔絕(TS Project References / Go internal)、語義圍欄規範(BOUNDARY.md 架構契約)、CI 自動化執法(Dependency-Cruiser 阻斷非法跨模組 Import)。底部為防禦鐵律。後微服務收斂核心準則:① Bounded Context ≠ 微服務|② 單進程內零網路跳躍開銷|③ 硬體級編譯與 CI 隔離執法編譯期防禦Compile Boundary工程級進程內硬隔離【語言內核物理防線】• TS Project References 隔離• Go internal package 限制• 僅暴露 index.ts 公開介面• 禁止跨目錄相對路徑穿透• 模組單元測試 100% 獨立運行隔離層級:編譯器與語言 AST 層破防代價:tsc / go build 直接失敗運行效能:0ms 記憶體指針直接調用語義圍欄Prompt BoundaryAI Agent 語義沙盒【架構即 Prompt 規範】• 模組專屬 BOUNDARY.md 宣告• 明確禁止跨域依賴清單• 規定 Agent 修改範圍受限度• 阻斷未授權檔案讀寫權限• 強制採用純 ID 與領域事件隔離層級:Agent 上下文心智模型層破防代價:Agent 生成期自律避免越界核心價值:杜絕幻覺導入其他模組內部CI 執法防線CI Enforcement零信任架構硬性阻斷【自動化架構守門人】• Dependency-Cruiser 拓撲驗證• 嚴禁環狀依賴 (Circular Dep)• 禁止私自 import 內部非導出檔• PR 閘門自動拒絕違規 Commit• 生成即時架構拓撲依存圖隔離層級:Git Pre-push / GitHub Action破防代價:CI 亮紅燈,嚴禁合併入主線核心價值:終結軟體架構不可逆熵增【架構收斂終極鐵律】用單體的部署極簡度與零網路成本,換取微服務同等級的限界自治• 拒絕微服務拆過頭的分散式大泥球: 網路延遲、RPC 序列化開銷與分散式兩階段提交(2PC)是過度拆分的沉重代價。• 模組化單體是現代團隊的最佳平衡點: 程式碼在同一個 Repo 與單一進程內運作,但透過編譯器、Prompt 邊界與 CI 規則建立物理隔絕,進可拆微服務、退可保最高開發吞吐率。
圖解:模組化單體(Modular Monolith)與 AI Agent 的三道語義隔離護欄

觀念糾偏:限界上下文 ≠ 微服務

Eric Evans 在提出 DDD 時,從未將「限界上下文」與「實體網路進程」畫上等號。

  • 限界上下文(Bounded Context)是「語義邊界」: 它是領域模型的生命週期與語言契約的範圍,決定的是代碼的心理模型與認知邊界。
  • 微服務(Microservice)是「實體部署單元」: 它決定的是伺服器進程、網路拓撲與獨立發布管線。

你完全可以在同一個單體應用(Monolith)、同一個作業系統進程、甚至同一個資料庫實例中,切分出四個壁壘分明、內部高內聚、邊界零依賴的限界上下文。

這就是近年來頂級架構圈(如 Shopify、GitHub、Basecamp)全面回歸的架構範式:模組化單體(Modular Monolith)。

在模組化單體中:

  1. 零網路傳輸開銷: 上下文之間的協同透過進程內的記憶體直接調用,呼叫延遲是納秒級(nanoseconds),徹底告別網路連線超時與重試風暴。
  2. 極致的開發體驗(DX): 一個 git clone、一條指令即可在本機跑起整套系統,單元測試在幾秒鐘內執行完畢。
  3. 保留演進主動權: 如果某個子域(如高併發動態定價)未來真的面臨極端吞吐瓶頸,因為它的邊界早已具備 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 戰略設計實戰:從業務邊界到系統架構》前四篇工具文先建立了從領域到系統邊界的判斷順序,之後再由案例篇檢驗這些工具:

  1. 子域劃分(Subdomains): 用「開源即倒閉測試」找出核心業務護城河,將 70% 的工程預算鎖死在刀刃上;
  2. 限界上下文(Bounded Context): 用四大工程訊號(語義、步調、團隊、交易)揮動手術刀,將臃腫危險的 God Object 解構為純淨的多維模型;
  3. 上下文映射與防腐層(ACL): 認清組織權力拓撲,在外部不可靠上游前築起「Facade → Adapter → Translator」三道海關,絕不被動順從;
  4. 模組化單體(Modular Monolith): 破除微服務迷思,用進程內硬隔離、Prompt 圍欄與 CI 拓撲執法,在享受單體極致開發速度的同時,具備微服務等級的自治能力。

架構的選擇不應由服務數量決定,而應由邊界要解決的問題決定。 若你的問題是 AI 變更太大、設計審查跟不上,可以接著讀《AI 寫碼變快,微服務能隔離故障,不能替你審查程式》;它把合併前的變更品質和上線後的故障隔離拆成兩項獨立決策。