【從一台 Server 到分散式架構】第 12 篇:第一個 Microservice——服務邊界與 API Gateway
上一篇,小明終於承認了一件事:單體架構的痛苦,不是出在效能,而是出在開發效率。
「一改全站都要 deploy」、「課程組壞掉、登入也壞掉」、「新人不敢改 code,因為不知道動哪裡會炸掉哪裡」——這些問題讓小明他們每週的部署會議都變成壓力測試。
他們決定:要開始拆了。
但站在白板前,小明和技術主管小傑面面相覷。
「那……我們從哪裡下刀?」
🏗️ 本篇開始前的架構 — 識別出高度耦合、準備切分的單體
後端仍然是一個大程式,但我們已經在上一篇識別出了三塊核心邏輯,現在要把它們真正分開。
服務邊界要怎麼劃?
拆微服務最難的不是技術,而是「要怎麼決定哪些東西應該在一起、哪些應該分開」。劃錯邊界,會比不拆還要痛苦——你會得到一堆互相緊耦合的「分散式大泥球」。
有幾個實用的思考角度:
1. 按業務領域(Domain)來切
小明的課程平台,從業務上自然可以分成幾個「關注領域」:
- 使用者(User):註冊、登入、個人資料、認證授權
- 課程(Course):課程資訊、章節、影片、師資
- 訂單(Order):購買、付款、退款、發票
這三塊有各自的業務語言、各自的資料主體、各自的變更原因。使用者組改密碼規則,跟課程組改章節排序,本來就是完全不相干的兩件事。這樣的「天然分界」就是服務邊界的好選擇。
2. 按「變更頻率」和「部署需求」來切
小明他們發現,金流邏輯幾乎每個月都要因為銀行 API 升版而修改,而課程管理邏輯幾乎不動。把金流獨立成一個服務,其他業務就不再被它的部署節奏拖著走。
3. 按「誰的 Data 就歸誰管」
服務邊界的一個核心原則是:每個服務只對自己的資料有「寫入」的控制權。
- 課程服務才有權限修改課程資料表。
- 訂單服務才有權限修改訂單資料表。
- 使用者服務才有權限修改用戶資料表。
如果兩個服務都可以直接寫同一張資料表,那它們就不是真的「分開的服務」,只是「跑在不同 Process 的同一個服務」。
生活化的比喻:街道上的專門工作室
把它想像成一條餐飲街:
- 有一間壽司吧(課程服務)——只做壽司,有自己的冰鮮進貨系統。
- 有一間甜點店(使用者服務)——只做甜點,有自己的員工與食材管理。
- 有一間收銀台(訂單服務)——負責結帳,管理所有的訂單帳本。
這三間店各有自己的冰箱(資料庫)、各自的廚師(程式邏輯)。壽司吧想換一批新鮮鮭魚(更新課程章節)?完全不需要通知甜點店(使用者服務)暫停。
但客人(使用者)進到這條街,他面對的不是三扇門,而是——
一個服務台(API Gateway)。
客人把需求告訴服務台:「我要查我的帳單(訂單)」、「我要更新我的個人資料(使用者)」、「我要繼續看課(課程)」。服務台幫他找到對的廚房,把問題轉進去,再把答案帶回來。客人不用知道這條街上有幾間店、每間店的電話是多少。
API Gateway 在做什麼?
API Gateway 是微服務架構裡「對外的那道門」,承擔的不只是「轉發請求」這麼簡單,它通常還包含了這些責任:
路由(Routing)
把 GET /courses/123 轉給課程服務,把 POST /orders 轉給訂單服務,把 PATCH /users/me 轉給使用者服務。外面的前端只需要打「同一個 API 入口」,不需要知道後面每個服務各自的位址。
統一認證(Authentication)
過去在單體裡,驗證 JWT 的邏輯寫在每個 Controller 前面,每個服務都要抄一份。現在,只要在 Gateway 做一次驗證:「這個 Token 是不是合法的?是誰發出來的?」,通過了才把請求往後送,同時把解碼後的用戶資訊(如 user_id、role)附在 Header 裡傳下去。
限流與保護(Rate Limiting)
Gateway 是所有請求的必經之路,自然也是做全站限流的最佳位置——例如每個 IP 每秒只能打 100 個請求,超過就直接擋在門口,不讓這些請求打進每個服務。
日誌與追蹤(Logging & Tracing)
每筆請求進來,在 Gateway 這裡就可以統一產生一個 Request ID,並把它貼在後續所有請求的 Header 裡。這樣不論一筆請求最終經過了多少個服務,都可以用同一個 ID 把日誌串起來,大幅降低排查問題的難度(這是第 20 篇分散式追蹤的先備知識)。
實務上,API Gateway 通常用什麼來實現?
小明問了一個很好的問題:「這個『服務台』到底是一個程式,還是什麼東西?我們能用 Nginx 嗎?」
答案是:可以從 Nginx 開始,但有很多選擇。
1. 反向代理(如 Nginx / HAProxy)
這是最常見的入門選擇。Nginx 的效能極高,對轉發路徑(Routing)非常拿手。
- 優點:輕量、穩定、大家都會。
- 缺點:如果要做複雜的邏輯(例如動態讀取資料庫權限、或是針對不同用戶有複雜的限流策略),需要寫 Lua 腳本或額外的模組,難度較高。
2. 專門的 API Gateway 軟體(如 Kong / APISIX / Tyk)
這些軟體通常是基於 Nginx 或是 Go 開發的,但它們內建了強大的「外掛(Plugin)系統」。
- 特色:你可以用點選或設定的方式,直接開啟「JWT 認證外掛」、「限流外掛」、「快取外掛」等。它們通常提供強大的管理後台或 API。
3. 雲端託管服務(如 AWS API Gateway / Google Cloud API Gateway)
如果你是走雲端運算,雲端供應商都提供了「隨開即用」的服務。
- 優點:伺服器維護由雲端負責,能自動水平擴展,且與內部的認證系統(如 AWS IAM)整合得非常好。
- 缺點:相對較貴,且容易被該雲端供應商綁定(Lock-in)。
4. 程式碼實作(如 Go / Node.js 寫一個 Proxy)
有些團隊會用自己熟悉的語言,手寫一個極簡單的 Proxy 程式。這提供了最高的彈性,你可以把公司特有的認證邏輯全部寫在裡面。
小明的選擇建議: 初期如果只是做簡單的路徑轉發,Nginx 就非常夠用了。等團隊大了、服務多了,需要更精細的外掛管理時,再考慮升級到 Kong 或是直接使用雲端的 API Gateway。
小明怎麼拆第一刀?
小明和小傑討論了很久,決定一個重要的原則:
不要一次全拆,先找最獨立的那一塊。
最理想的「第一個微服務」,應該是:
- 和其他模組的耦合最低
- 有清晰的業務邊界
- 資料表相對獨立,不會和其他模組交叉 join
觀察下來,**使用者服務(User Service)**是最合適的候選人——它雖然被課程、訂單模組呼叫(取用者資訊),但它本身很少需要主動呼叫其他模組。把它切出去,對其他模組的影響最小。
實際執行步驟大概是這樣:
- 新建獨立的 Repo 和服務:User Service 有自己的程式碼庫、自己的 CI/CD 流程。
- 遷移資料表:把
users相關的資料表從共用 DB 遷移到 User Service 專屬的 DB。 - 替換呼叫方式:課程服務和訂單服務原本直接 Join
users資料表,現在改成透過 HTTP API 向 User Service 查詢。 - 在舊單體裡刪除使用者邏輯:確認新服務穩定後,才把單體裡的對應程式碼移除。
這個策略有個名字,叫做 Strangler Fig Pattern(絞殺者模式):不把舊系統全部打掉重練,而是讓新的微服務像藤蔓一樣慢慢覆蓋、替換舊的邏輯,讓整個遷移過程可以漸進、可以隨時暫停。
拆完之後,服務怎麼彼此溝通?
當 User Service 從單體裡獨立出來,課程服務要拿「用戶名稱」時,就不再是一行 db.query('SELECT * FROM users WHERE id=?'),而是一個 HTTP 請求:
GET http://user-service/internal/users/42
這代表:
- 過去是函式呼叫(毫秒級,一定成功)
- 現在是網路呼叫(可能慢、可能超時、可能失敗)
小明他們開始面對分散式系統的標準挑戰:Timeout 怎麼設?失敗了要不要 Retry?Retry 多少次? 這些問題在單體架構裡幾乎不存在,在微服務裡卻是每天都要面對的工程決策。
拆完後的架構長什麼樣子?
🏗️ 本篇結束後的架構 — 拆出第一個 Microservice,API Gateway 登場
注意這張圖的幾個重點:
- API Gateway 是唯一對外的入口,前端完全不需要知道後面有幾個服務。
- User Service 已經是獨立服務,有自己的 DB,以綠色標示。
- Course Service 和 Order Service 仍然住在舊單體裡,還在等待下一輪拆分。
- Course Service 和 Order Service 透過 HTTP API 向 User Service 查詢用戶資料,不再直接 join 資料表。
這是微服務遷移的典型中間態——不是一夕之間全部變微服務,而是一刀一刀慢慢切。
什麼時候「不應該」拆微服務?
既然說了這麼多拆的好處,也要說清楚:不是每個系統都適合拆。
| 情況 | 建議 |
|---|---|
| 團隊只有 3~5 人 | 維運多個服務的成本很可能超過好處,先好好做單體 |
| 業務邊界還不清楚 | 邊界不清就拆,會拆錯,之後要「合服務」比當初不拆更痛 |
| 還在快速驗證商業模式 | 不要被架構綁手綁腳,功能迭代速度遠比架構優雅重要 |
| 流量尚可承受 | 效能問題還沒到瓶頸時,先用快取、索引等「低成本手段」處理 |
微服務解決的是「大團隊的開發組織問題」,不是「小系統的效能問題」。
小結與預告
這篇我們做了幾件事:
- 學會劃分服務邊界:按業務領域、按變更頻率、按「誰的 Data 誰負責」。
- 理解 API Gateway 的角色:統一入口、路由、認證、限流、日誌,讓前端只需要打一個地方。
- 看到 Strangler Fig Pattern:漸進式遷移,不需要一次全部重寫。
- 認識微服務的新挑戰:函式呼叫變成網路呼叫,Timeout、Retry、錯誤處理都是新課題。
現在,使用者服務已經拆出來了。課程平台的架構開始往微服務的方向演進。
但小明很快就發現了新的問題:課程平台即將舉辦一場大型直播活動,預估會有十萬人同時湧入。API Gateway 擋得住嗎?後端服務撐得住嗎?如果 DB 快被壓垮,要怎麼讓系統在流量暴衝時優雅地活著,而不是直接掛掉?
下一篇,我們來談限流、排隊與降級——當洪水來臨,怎麼護住最核心的那道門。