# 單體到微服務演進導論

::: tip 前言
**沒有哪個架構是「最好的」，只有「最適合當前階段的」。** 從單體到微服務不是一步到位的跳躍，而是隨著業務規模和團隊規模增長，逐步演進的過程。過早拆分微服務和過晚拆分一樣危險。
:::

**這篇文章會帶你學什麼？**

學完這章後，你將獲得：

- **演進路徑**：理解從單體到微服務的四個階段
- **拆分時機**：知道什麼時候該拆、什麼時候不該拆
- **拆分策略**：掌握按業務域拆分的方法論
- **通訊模式**：了解服務間同步和非同步通訊的選擇
- **資料拆分**：理解資料庫拆分的挑戰和方案

| 章節 | 內容 | 核心概念 |
|-----|------|---------|
| **第 1 章** | 架構演進路徑 | 單體→模組化→SOA→微服務 |
| **第 2 章** | 拆分時機與原則 | Conway 定律、團隊自治 |
| **第 3 章** | 拆分策略 | DDD 限界上下文、絞殺者模式 |
| **第 4 章** | 服務通訊 | REST、gRPC、訊息佇列 |
| **第 5 章** | 資料拆分 | 資料庫拆分、資料同步 |

---

## 1. 架構演進路徑

架構演進不是技術驅動的，而是**組織規模驅動的**。當團隊從 5 人增長到 500 人時，單體架構的協作效率會急劇下降。

| 階段 | 架構 | 團隊規模 | 特點 |
|------|------|---------|------|
| 起步期 | 單體應用 | 1~10 人 | 所有程式碼在一個專案中，部署簡單 |
| 成長期 | 模組化單體 | 10~50 人 | 程式碼按模組劃分，但仍然一起部署 |
| 擴張期 | SOA（面向服務） | 50~200 人 | 按業務線拆分為粗粒度服務 |
| 規模期 | 微服務 | 200+ 人 | 細粒度服務，每個團隊獨立開發部署 |

<ArchEvolutionDemo />

::: tip Conway 定律
「設計系統的組織，其產生的架構等同於組織的溝通結構。」——Melvin Conway

簡單說：3 個團隊做一個系統，最終會變成 3 個服務。架構拆分本質是**組織拆分**。

**反向 Conway 定律**：既然組織結構決定了系統架構，那麼想要什麼樣的架構，就先調整成什麼樣的組織結構。比如你想拆出獨立的支付服務，就先組建一個獨立的支付團隊。很多公司微服務拆分失敗，不是技術問題，而是組織沒有跟著調整。
:::

---

## 2. 拆微服務的時機界定

不是所有系統都需要微服務。過早拆分會帶來不必要的複雜性。

| 訊號 | 說明 | 建議 |
|------|------|------|
| 部署衝突頻繁 | 多個團隊改同一個程式碼庫，經常衝突 | 考慮拆分 |
| 某模組需要獨立擴容 | 搜尋模組需要 10 倍於其他模組的資源 | 考慮拆分 |
| 技術棧需要差異化 | AI 模組用 Python，主站用 Java | 考慮拆分 |
| 團隊 < 10 人 | 溝通成本低，單體足夠 | 不要拆 |
| 業務還在探索期 | 需求變化快，邊界不清晰 | 不要拆 |
| 沒有 DevOps 能力 | 沒有 CI/CD、容器化、監控體系 | 不要拆 |

---

## 3. 拆分策略

### 3.1 按業務域拆分（DDD 限界上下文）

DDD（領域驅動設計）的限界上下文（Bounded Context）是拆分微服務的最佳指導原則。每個限界上下文對應一個獨立的業務域，有自己的資料模型和業務規則。

**什麼是限界上下文？** 同一個詞在不同業務域中含義不同。比如「使用者」在使用者域是指註冊資訊（姓名、信箱），在訂單域是指下單人（收貨地址、支付方式），在推薦域是指行為畫像（瀏覽歷史、偏好標籤）。限界上下文就是劃定一個邊界，在這個邊界內，術語和模型有明確統一的含義。

```
┌─────────────┐  ┌─────────────┐  ┌─────────────┐
│   使用者域     │  │   訂單域     │  │   支付域     │
│             │  │             │  │             │
│ User        │  │ Order       │  │ Payment     │
│ Profile     │  │ OrderItem   │  │ Refund      │
│ Address     │  │ Cart        │  │ Transaction │
│             │  │             │  │             │
│ 使用者服務    │  │ 訂單服務     │  │ 支付服務    │
└──────┬──────┘  └──────┬──────┘  └──────┬──────┘
       │                │                │
       └────── API 呼叫 / 事件通訊 ───────┘
```

| 限界上下文 | 核心實體 | 對應服務 |
|-----------|---------|---------|
| 使用者域 | User、Profile、Address | 使用者服務 |
| 商品域 | Product、Category、SKU | 商品服務 |
| 訂單域 | Order、OrderItem | 訂單服務 |
| 支付域 | Payment、Refund | 支付服務 |
| 物流域 | Shipment、Tracking | 物流服務 |

### 3.2 絞殺者模式（Strangler Fig Pattern）

不要一次性重寫整個單體，而是像絞殺榕一樣，逐步用新服務替換舊模組：

1. 在單體外部建立新服務
2. 透過代理層將部分流量路由到新服務
3. 驗證新服務穩定後，逐步遷移更多流量
4. 最終完全替換舊模組

---

## 4. 服務通訊模式

| 方式 | 協定 | 特點 | 適用場景 |
|------|------|------|---------|
| REST | HTTP/JSON | 簡單通用，生態好 | 對外 API、CRUD 操作 |
| gRPC | HTTP/2 + Protobuf | 高效能，強型別 | 內部服務間高頻呼叫 |
| 訊息佇列 | AMQP/Kafka | 非同步解耦，削峰填谷 | 事件通知、非同步任務 |
| GraphQL | HTTP/JSON | 客戶端按需查詢 | BFF 層、行動端 |

::: tip 同步 vs 非同步的選擇
- **需要立即回傳結果** → 同步（REST/gRPC）
- **不需要立即回傳** → 非同步（訊息佇列）
- **一個事件觸發多個動作** → 非同步（發布-訂閱）

經驗法則：能非同步就非同步，同步呼叫鏈越長，系統越脆弱。
:::

---

## 5. 資料拆分：最難的部分

微服務拆分中最痛苦的不是程式碼拆分，而是資料庫拆分。每個服務應該擁有自己的資料庫，但這意味著跨服務查詢變得困難。

| 挑戰 | 描述 | 解決方案 |
|------|------|---------|
| 跨服務 JOIN | 不能直接 JOIN 兩個服務的表 | API 組合查詢、資料冗餘 |
| 分散式事務 | 跨庫事務無法用本地事務 | Saga、本地訊息表 |
| 資料一致性 | 多個服務的資料可能暫時不一致 | 最終一致性、事件驅動 |
| 資料遷移 | 從共享庫遷移到獨立庫 | 雙寫過渡、資料同步工具 |

---

## 總結

從單體到微服務是一個漸進的過程，不是一蹴可幾的革命。

回顧本章的關鍵要點：

1. **演進路徑**：單體→模組化單體→SOA→微服務，每一步都有明確的驅動力
2. **拆分時機**：團隊規模、部署衝突、擴容需求是拆分的訊號
3. **拆分策略**：用 DDD 限界上下文指導拆分，用絞殺者模式漸進遷移
4. **通訊選擇**：能非同步就非同步，同步呼叫鏈越短越好
5. **資料拆分**：最難但最重要，接受最終一致性是關鍵心態轉變

## 延伸閱讀

- [Building Microservices](https://www.oreilly.com/library/view/building-microservices-2nd/9781492034018/) - Sam Newman 微服務經典
- [Monolith to Microservices](https://www.oreilly.com/library/view/monolith-to-microservices/9781492047834/) - 漸進式遷移指南
- [Domain-Driven Design](https://www.domainlanguage.com/ddd/) - Eric Evans 的 DDD 經典
- [The Strangler Fig Pattern](https://martinfowler.com/bliki/StranglerFigApplication.html) - Martin Fowler 的絞殺者模式
