文章二:10 Microservice Best Practices Every Developer Gets Wrong
1. 領域驅動設計:人人都跳過的地基
- 段落重點:團隊投入微服務時,常沿著技術邊界來拆分單體應用。他們會創建一個用戶服務、資料庫服務和 API 服務。這種方法完全忽略了領域驅動設計的重點。正確的微服務應該圍繞業務領域和子領域來組織。例如,一個電商平台可能有獨立的帳戶、支付、對帳、報表和客戶詳情等領域。每個子領域都包含自己的資料、邏輯和 API。
領域邊界減少了耦合,增加了內聚。支付領域的變更不會影響到帳戶管理。// 錯誤 - 技術邊界 class UserService { } class DatabaseService { } class ApiService { } // 正確 - 業務領域邊界 class AccountsService { // ... } class PaymentService { // ... } - 段落總結:許多開發者錯誤地以技術功能(如使用者、資料庫)來劃分微服務,這違背了 DDD 的初衷。正確的做法是應圍繞業務領域(如帳戶、支付)來組織服務,以實現高內聚、低耦合。
2. 獨立資料儲存:每個服務一個資料庫的模式
- 段落重點:共享資料庫會在服務之間產生緊密的耦合。當服務 A 和服務 B 都查詢同一個
users資料表時,資料庫結構的變更就成了協調的惡夢。一個團隊無法在不破壞另一個服務的情況下修改其結構。每個微服務都需要自己的資料儲存。服務 A 完全擁有其資料庫。服務 B 管理著獨立的資料表或完全不同的資料庫技術。
這種分離使團隊能為其特定需求選擇最佳的資料庫技術。# 服務 A - 使用 PostgreSQL class OrderService: def __init__(self): self.db = PostgresConnection("orders_db") # ... # 服務 B - 使用 MongoDB class InventoryService: def __init__(self): self.db = MongoConnection("inventory_db") # ... - 段落總結:開發者常犯的錯誤是讓多個微服務共享同一個資料庫,這會造成緊密耦合。最佳實踐是「每個服務一個資料庫」,賦予每個服務獨立選擇和演進其資料儲存技術的自由。
3. 獨立建置管線:獨立部署
- 段落重點:許多組織將所有微服務放在一個單體的 CI/CD 管線中一起建置。這違背了獨立服務的初衷。當一個服務建置失敗時,所有部署都會停止。每個微服務都需要自己的建置管線,具備獨立的版本控制和部署排程。
團隊可以每天部署服務 A 十次,而服務 B 每週更新一次。部署頻率應匹配業務需求,而非組織限制。# 服務 A 管線 name: orders-service-pipeline on: push: paths: - 'services/orders/**' # ... # 服務 B 管線 name: inventory-service-pipeline on: push: paths: - 'services/inventory/**' # ... - 段落總結:將所有微服務綁在同一個建置管線是常見錯誤,它破壞了獨立部署的優勢。每個微服務都應有自己獨立的 CI/CD 管線,使其能依自身節奏獨立發布和版本控制。
4. 程式碼成熟度等級:接受不同的標準
- 段落重點:組織常對所有服務強制執行統一的編碼標準。每個服務在部署前都必須達到相同的品質門檻。這種方法減慢了創新速度,並製造了不必要的摩擦。不同的服務需要不同的成熟度等級。一個關鍵的支付服務需要廣泛的測試、監控和文件。而一個內部報告工具則可以用基本的驗證就發布。
這種務實的方法在品質和速度之間取得平衡。// 關鍵服務 - 高成熟度等級 (Level 4) class PaymentService { // ... 包含完整的驗證、日誌、指標 } // 內部工具 - 較低成熟度等級 (Level 3) class ReportService { // ... 邏輯較為簡單 } - 段落總結:強制所有微服務遵循同樣嚴苛的品質標準是個錯誤,會扼殺效率。務實的做法是依據服務的重要性區分「成熟度等級」,關鍵服務要求高標準,而內部或非核心服務則可放寬標準以加速交付。
5. 容器化:跨環境的一致性
- 段落重點:直接在虛擬機器上運行微服務會造成環境不一致。依賴項會衝突。設定會在測試環境和生產環境之間漂移。擴展需要手動配置伺服器。容器將服務及其依賴項打包在一起,確保在所有環境中的行為都相同。
像 Kubernetes 這樣的容器編排平台會自動管理部署、擴展和網路。# 微服務的 Dockerfile FROM node:18-alpine WORKDIR /app COPY package*.json ./ RUN npm ci --only=production COPY . . CMD ["node", "server.js"] - 段落總結:開發者常忽略直接在 VM 上部署導致的環境不一致問題。最佳實踐是使用 Docker 等技術進行「容器化」,將應用程式與其環境打包,確保從開發到生產的一致性,並利用 Kubernetes 等工具進行自動化管理。
6. 無狀態服務:水平擴展
- 段落重點:將會話狀態儲存在記憶體中會妨礙水平擴展。當一個服務在本地儲存用戶會話時,請求必須被路由到同一個實例。這會產生瓶頸並使負載均衡複雜化。無狀態服務獨立對待每個請求。會話資料被移至像 Redis 或資料庫這樣的外部儲存中。
任何服務實例都可以處理任何請求。負載均衡器可以自由地將流量分配到所有可用的實例。# 有狀態服務 - 錯誤 class StatefulOrderService: def __init__(self): self.sessions = {} # 記憶體內狀態 # 無狀態服務 - 正確 class StatelessOrderService: def __init__(self): self.session_store = RedisClient() - 段落總結:在服務記憶體中儲存會話狀態是個常見錯誤,它會阻礙水平擴展。正確做法是設計「無狀態服務」,將所有狀態(如使用者會話)外部化到 Redis 或資料庫等共享儲存中,讓任何服務實例都能處理任何請求。
7. 單一職責:每個服務一項工作
- 段落重點:做得太多的服務會變成「迷你單體」。一個處理身份驗證、個人資料管理、通知和帳務的使用者服務就違反了單一職責原則。每個服務應該處理一個業務能力。身份驗證服務管理登入和權杖。個人資料服務處理使用者資料。通知服務發送電子郵件。
專注的服務更容易理解、測試和維護。// 職責過多 - 錯誤 class UserService { public void authenticate(Credentials creds) { } public void updateProfile(Profile profile) { } public void sendNotification(Notification notif) { } } // 單一職責 - 正確 class AuthenticationService { /* ... */ } class ProfileService { /* ... */ } - 段落總結:開發者常讓一個微服務承擔過多職責,使其變成難以維護的「迷你單體」。每個服務都應嚴格遵守「單一職責原則」,只專注於一項核心業務能力,例如將認證、個人資料管理拆分為不同的服務。
8. 微前端:UI 組成
- 段落重點:後端微服務常常搭配單體前端。一個 React 應用程式呼叫數十個後端服務。這會造成部署瓶頸,並將前端的發布與後端的變更耦合在一起。微前端將微服務原則擴展到 UI 層。每個團隊同時擁有後端服務和前端組件。
團隊可以一起部署前端和後端的變更。產品功能交付更快,因為協調開銷減少了。// API 閘道器將路由到不同的前端服務 // 付款團隊擁有自己的前端 app.use('/payments', proxy('http://payments-frontend:3000')); // 訂單團隊擁有自己的前端 app.use('/orders', proxy('http://orders-frontend:3000')); - 段落總結:僅有後端微服務而前端仍是單體應用,是個常見的架構瓶頸。最佳實踐是採用「微前端」,將前端也按業務能力拆分,讓團隊能獨立開發和部署從後端到前端的完整垂直功能。
9. 編排:管理複雜性
- 段落重點:在規模化時,手動跨伺服器運行微服務變得不可能。設定管理、服務發現、負載均衡和健康監控需要專門的平台。Kubernetes 為容器化的微服務提供編排。它會自動處理部署、擴展、網路和自我修復。
編排器會維持期望的狀態。如果容器崩潰,它會啟動替代品。如果流量增加,它會自動擴展服務。apiVersion: apps/v1 kind: Deployment metadata: name: order-service spec: replicas: 3 # ... - 段落總結:試圖手動管理大量微服務是行不通的。正確的做法是使用 Kubernetes 這類的「編排」平台,來自動化處理服務的部署、擴展、網路配置和健康監控,以應對規模化帶來的複雜性。
10. API 閘道器:單一入口點
- 段落重點:直接的服務對服務通訊會創建一個依賴網。客戶端必須知道每個服務的端點。身份驗證邏輯在各服務中重複。速率限制變得無法一致地執行。API 閘道器提供了一個單一入口點,將請求路由到適當的後端服務。
閘道器處理像身份驗證、速率限制、日誌記錄和請求轉換等橫切關注點。後端服務專注於業務邏輯。// API 閘道器設定 gateway() .load({ apiEndpoints: { payments: { host: 'api.example.com', paths: '/payments/*' }, orders: { host: 'api.example.com', paths: '/orders/*' } }, policies: ['rate-limit', 'oauth2', 'request-transformer'], // ... }) .run(); - 段落總結:讓客戶端直接與眾多微服務通訊會造成混亂和重複工作。最佳實踐是引入「API 閘道器」作為所有請求的統一入口,由它來處理認證、限流、路由等通用功能,讓後端服務能專注於核心業務。
文章二總結
這篇文章指出了開發者在實踐微服務架構時最常犯的十個錯誤,並提供了對應的最佳實踐,旨在幫助團隊避免將微服務的承諾變成營運的惡夢。
文章的核心觀點是,成功的微服務架構並非只是將單體應用拆分成小服務,而是需要遵循一系列關鍵原則才能實現其擴展性、靈活性和快速部署的優勢。開發者常犯的錯誤與對應的最佳實踐如下:
- 領域劃分:錯誤地按技術分層,應採用 DDD 按業務領域劃分。
- 資料管理:錯誤地共享資料庫,應採用 每個服務一個資料庫 的模式以實現獨立性。
- 部署流程:錯誤地使用單體 CI/CD,應為每個服務建立 獨立的建置管線。
- 品質標準:錯誤地一刀切,應依服務重要性採用 不同的程式碼成熟度等級。
- 環境一致性:錯誤地直接部署在 VM,應使用 容器化(如 Docker)確保環境一致。
- 擴展性:錯誤地設計有狀態服務,應設計 無狀態服務 並將狀態外部化。
- 服務職責:錯誤地讓服務職責過多,應遵循 單一職責原則。
- 前端架構:錯誤地保留單體前端,應考慮採用 微前端 將解耦延伸至 UI 層。
- 系統管理:錯誤地手動管理,應使用 編排工具(如 Kubernetes)自動化管理複雜性。
- 外部通訊:錯誤地讓客戶端直連服務,應引入 API 閘道器 作為統一入口點。
總結來說,要真正從微服務中獲益,團隊必須掌握這些基礎。從正確的領域設計開始,確保資料和部署的獨立性,並利用容器化、編排和 API 閘道器等現代工具來管理複雜性。只有打好這些地基,才能避免建造出一個分散式的單體,真正實現微服務的敏捷與彈性。