第十四章:微服務架構設計 (Microservices Architecture)
14.1 引言:單體 vs 微服務
在軟體架構的演進中,我們通常從 模組化單體 (Modular Monolith) 開始。這也是 ABP 官方推薦的起點。然而,當系統規模擴大到一定程度,或者團隊規模超過 20 人時,微服務架構就成為了必要的選擇。
1. 微服務的優勢
- 獨立部署:修改一個服務不需要重新部署整個系統。
- 技術多樣性:不同的服務可以使用不同的技術堆疊 (例如 .NET, Node.js, Python)。
- 彈性擴展:可以針對瓶頸服務單獨擴展 (Scale Out)。
- 故障隔離:一個服務掛掉不會拖垮整個系統 (前提是做好了隔離)。
2. 微服務的代價
- 運維複雜度:需要管理更多的伺服器、容器與網路設定。
- 資料一致性:沒有了全域的資料庫交易,資料一致性變得極其困難。
- 除錯困難:請求跨越多個服務,追蹤問題需要分散式追蹤工具。
14.2 服務邊界劃分 (Service Boundaries)
劃分微服務的邊界是一門藝術。
1. 依據業務能力 (Business Capabilities)
例如:IdentityService (身分認證), ProductService (商品管理), OrderingService (訂單處理), PaymentService (支付)。
2. 依據子領域 (Subdomains)
DDD 中的 Bounded Context (邊界上下文) 是劃分微服務的最佳依據。通常一個 Bounded Context 對應一個微服務。
3. 資料庫擁有權
黃金法則:每個微服務必須擁有自己的資料庫 (Database per Service)。
- 嚴禁跨服務直接存取資料庫。
- 若需要其他服務的資料,必須透過 API 或事件同步。
14.3 服務間通訊 (Inter-Service Communication)
1. 同步通訊 (Synchronous)
使用 HTTP (REST) 或 gRPC。
- 適用場景:查詢 (Query)、需要立即回應的操作。
- 缺點:服務間強耦合,若下游服務掛掉,上游也會受影響。
- ABP 支援:使用
HttpApi.Client代理,像呼叫本地方法一樣呼叫遠端服務。
public class OrderAppService : ApplicationService
{
private readonly IProductAppService _productAppService; // 遠端代理
public async Task CreateOrderAsync(CreateOrderDto input)
{
// 同步呼叫 ProductService
var product = await _productAppService.GetAsync(input.ProductId);
// ...
}
}2. 非同步通訊 (Asynchronous)
使用 Message Bus (RabbitMQ, Kafka)。
- 適用場景:命令 (Command)、狀態變更通知、長執行時間的任務。
- 優點:解耦、削峰填谷。
- ABP 支援:
IDistributedEventBus。
// OrderService
await _eventBus.PublishAsync(new OrderCreatedEto { OrderId = order.Id });
// InventoryService (訂閱者)
public class InventoryHandler : IDistributedEventHandler<OrderCreatedEto>
{
public async Task HandleEventAsync(OrderCreatedEto eventData)
{
// 扣減庫存
}
}3. 事件可靠性 (Event Reliability)
ABP 實作了 Outbox 與 Inbox 模式來確保事件不丟失。
- Outbox: 事件先寫入本地資料庫,再由背景工作發送。
- Inbox: 接收到的事件先寫入本地資料庫,再由背景工作處理。
- V10 更新: InboxProcessor 新增了 失敗重試策略,可配置重試次數與間隔,避免因暫時性錯誤導致事件遺失。
14.4 分散式交易 (Distributed Transactions)
在微服務中,我們無法使用 ACID 交易。我們必須依賴 Saga 模式 來實現最終一致性。
1. 什麼是 Saga?
Saga 是一系列本地交易的序列。如果其中一個失敗了,Saga 會執行一系列的 補償交易 (Compensating Transactions) 來復原變更。
2. 實作方式:基於事件的編排 (Choreography)
- 步驟 1:
OrderService建立訂單 (Pending),發布OrderCreated事件。 - 步驟 2:
InventoryService收到事件,扣減庫存。- 若成功:發布
InventoryReserved事件。 - 若失敗 (庫存不足):發布
InventoryFailed事件。
- 若成功:發布
- 步驟 3:
PaymentService收到InventoryReserved,進行扣款。- 若成功:發布
PaymentCompleted。 - 若失敗:發布
PaymentFailed。
- 若成功:發布
- 步驟 4:
OrderService根據收到的事件更新訂單狀態。- 收到
PaymentCompleted-> 訂單狀態改為Completed。 - 收到
InventoryFailed或PaymentFailed-> 訂單狀態改為Cancelled(並可能觸發退款)。
- 收到
14.5 API Gateway
API Gateway 是微服務系統的單一入口。它負責路由、認證、限流與聚合。
1. YARP (Yet Another Reverse Proxy)
微軟官方的高效能反向代理,ABP 微服務範本預設使用 YARP。
2. 配置範例 (appsettings.json)
"ReverseProxy": {
"Routes": {
"product-route": {
"ClusterId": "product-cluster",
"Match": { "Path": "/api/product-service/{**catch-all}" }
}
},
"Clusters": {
"product-cluster": {
"Destinations": {
"destination1": { "Address": "http://localhost:5001" }
}
}
}
}14.6 可觀測性 (Observability)
微服務若沒有監控就是黑盒子。
1. 分散式追蹤 (Distributed Tracing)
使用 OpenTelemetry 標準。
- 每個請求都會被標記一個
TraceId。 - 這個 ID 會隨著 HTTP Header 或 Message Bus 傳遞到所有服務。
- 工具:Jaeger, Zipkin, Aspire Dashboard (新)。
2. 集中式日誌 (Centralized Logging)
使用 Serilog 將日誌寫入 Elasticsearch 或 Seq。
- 透過
TraceId可以在日誌系統中串聯出完整的請求路徑。
14.7 實戰練習
練習 1:建立微服務方案
- 使用 ABP CLI 建立微服務方案 (注意:社群版沒有微服務範本,需手動建立多個
app專案並配置)。- 提示:建立
ProductService與OrderService兩個獨立專案。
- 提示:建立
- 配置 RabbitMQ 作為分散式事件匯流排。
練習 2:實作跨服務通訊
- 在
ProductService中建立商品。 - 在
OrderService中建立訂單時,透過HttpApi.Client同步檢查商品是否存在。 - 訂單建立後,發布事件通知
ProductService扣減庫存。
練習 3:配置 YARP Gateway
- 建立一個新的 ASP.NET Core 空專案作為 Gateway。
- 安裝
Yarp.ReverseProxy。 - 配置路由將
/api/products轉發到ProductService,將/api/orders轉發到OrderService。
14.8 總結
微服務架構強大但也複雜。
- 邊界劃分 是最關鍵的決策。
- 非同步通訊 是解耦的核心。
- Saga 模式 解決了一致性難題。
- 可觀測性 是運維的基石。
在下一章,我們將探討如何透過 模組化開發 (Modular Development) 來構建可重用的業務單元,這是微服務與單體架構都適用的重要技術。
參考資源: