【從一台 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_idrole)附在 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)**是最合適的候選人——它雖然被課程、訂單模組呼叫(取用者資訊),但它本身很少需要主動呼叫其他模組。把它切出去,對其他模組的影響最小。

實際執行步驟大概是這樣:

  1. 新建獨立的 Repo 和服務:User Service 有自己的程式碼庫、自己的 CI/CD 流程。
  2. 遷移資料表:把 users 相關的資料表從共用 DB 遷移到 User Service 專屬的 DB。
  3. 替換呼叫方式:課程服務和訂單服務原本直接 Join users 資料表,現在改成透過 HTTP API 向 User Service 查詢。
  4. 在舊單體裡刪除使用者邏輯:確認新服務穩定後,才把單體裡的對應程式碼移除。

這個策略有個名字,叫做 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 人維運多個服務的成本很可能超過好處,先好好做單體
業務邊界還不清楚邊界不清就拆,會拆錯,之後要「合服務」比當初不拆更痛
還在快速驗證商業模式不要被架構綁手綁腳,功能迭代速度遠比架構優雅重要
流量尚可承受效能問題還沒到瓶頸時,先用快取、索引等「低成本手段」處理

微服務解決的是「大團隊的開發組織問題」,不是「小系統的效能問題」。


小結與預告

這篇我們做了幾件事:

  1. 學會劃分服務邊界:按業務領域、按變更頻率、按「誰的 Data 誰負責」。
  2. 理解 API Gateway 的角色:統一入口、路由、認證、限流、日誌,讓前端只需要打一個地方。
  3. 看到 Strangler Fig Pattern:漸進式遷移,不需要一次全部重寫。
  4. 認識微服務的新挑戰:函式呼叫變成網路呼叫,Timeout、Retry、錯誤處理都是新課題。

現在,使用者服務已經拆出來了。課程平台的架構開始往微服務的方向演進。

但小明很快就發現了新的問題:課程平台即將舉辦一場大型直播活動,預估會有十萬人同時湧入。API Gateway 擋得住嗎?後端服務撐得住嗎?如果 DB 快被壓垮,要怎麼讓系統在流量暴衝時優雅地活著,而不是直接掛掉?

下一篇,我們來談限流、排隊與降級——當洪水來臨,怎麼護住最核心的那道門。