個人 Side Project ‧ 架構設計與開發 · 2024
電商微服務可觀測性專案
訂單、付款、物流三個微服務組成的電商練習專案,以 DDD 戰略/戰術設計 + Hexagonal Architecture 實作 Event Sourcing,並完整導入 OpenTelemetry 可觀測性堆疊與 Grafana/Tempo MCP 查詢工具。

專案概述
一個以訂單(Order)、付款(Payment)、物流(Shipping)三個微服務組成的電商練 習專案,目的不是做出完整電商功能,而是紮實地練習 DDD 戰略與戰術設計、 Event Sourcing 與可觀測性(Observability) 在 .NET 微服務中的落地方 式。每個服務都各自擁有獨立資料庫,並透過領域事件串接彼此的業務流程。
DDD 戰略設計(Strategic Design)
三個服務對應三個獨立的 Bounded Context,各自有自己的資料庫、程式碼庫與通 用語言(Ubiquitous Language),彼此不共用資料表、也不直接呼叫對方的 API:
| Bounded Context | 核心聚合 | 負責的通用語言 |
|---|---|---|
| OrderService | Order | 建立訂單、確認付款、揀貨、完成訂單、取消訂單 |
| PaymentService | Payment | 請款、付款完成、付款失敗、取消付款 |
| ShippingService | Shipment | 請求出貨、已派送、已送達 |
Context 之間只透過 Kafka 上的領域事件溝通,不共享資料庫、不同步呼叫對
方,屬於以「Published Language(事件即契約)」串接的 Context Mapping:
OrderService 發布 OrderCreatedEvent 後即結束自己的職責,完全不知道
PaymentService 內部如何處理請款;PaymentService 處理完也只發布
PaymentCompletedEvent / PaymentFailedEvent,同樣不知道 OrderService
收到後會做什麼。這種鬆耦合讓三個服務可以獨立開發、獨立部署、獨立擴展。
事件流程示意圖
下圖是訂單成立、付款、出貨(以及付款失敗導致取消訂單)的完整事件流:

- 成功路徑:
User → 建立訂單 → OrderCreated → 發起支付請求 → PaymentRequested → 支付完成 → PaymentCompleted → 確認付款 → OrderPaid → 發起配送請求 → ShippingRequested - 失敗路徑:
支付失敗 → PaymentFailed → 取消訂單 → OrderCancelled
整個流程是典型的事件編舞(Choreography):沒有一個中控服務下指令,每個 服務只是「訂閱自己關心的事件、做完事、發布下一個事件」,服務間的協作關係完全 從事件的訂閱/發布關係中浮現出來。
DDD 戰術設計(Tactical Design)
在單一 Bounded Context 內部,則落實了完整的戰術設計工具箱:
- Aggregate Root + Event Sourcing:
Order、Payment、Shipment都繼承 共用的AggregateRoot<TId>。狀態不能被直接賦值修改,只能透過Apply(DomainEvent)產生事件、在When()中依事件重建狀態,並在EnsureValidState()中檢查不變條件(Invariant),修改狀態與驗證規則被綁在 一起,不會有「改了忘記驗證」的問題public void ConfirmPayment() { Apply(new OrderPaidEvent(Id, ShipmentAddress)); } protected override void When(DomainEvent domainEvent) { switch (domainEvent) { case OrderCreatedEvent e: Status = Status.PendingPayment; break; case OrderPaidEvent: Status = Status.Paid; break; // ... } } protected override void EnsureValidState() { if (Status == Status.Cancelled && string.IsNullOrEmpty(CancellationReason)) throw new OrderDomainException("Cancellation reason is required."); } - 強型別 ID(Value Object):
OrderId、PaymentId、ShipmentId、MemberId都是獨立的 Value Object,而不是裸的Guid,避免把訂單 ID 誤傳 成會員 ID 這種編譯期抓不到的錯誤 - 狀態機驅動:
Order從PendingPayment開始,依序經過Paid、PickingInProgress,最終到達Completed,或在任何節點被Cancelled;狀 態轉換完全由事件決定,畫面上看到的狀態永遠是「套用過哪些事件」的結果 - 事件即事實,而非指令:領域事件一律用過去式命名
(
OrderCreatedEvent、PaymentCompletedEvent),代表「已經發生的事 實」,而不是「請你做什麼」的命令,這也是它能安全地被多個服務訂閱、不需要協 調誰先誰後的原因 - Null Object Pattern:以
INullObject/Order.Null取代null判 斷,减少呼叫端重複的防禦性檢查 - 事件溯源基礎設施:
AggregateRoot內建Version、GetUncommittedEvents()、Load(history),聚合可以完全由事件歷史重建,而 非只依賴目前快照
系統架構
三個服務都採用 Hexagonal Architecture(Ports & Adapters),並拆成獨立的 專案分層:
Entities:聚合根與領域事件,對應各服務的狀態機UseCase:應用層服務與 In/Out Port 介面(如CreateOrderService、RequestPaymentService、DispatchOrderService)Adapter.Out:以 EF Core 實作的資料庫存取層與 MigrationWebApi:Minimal API 端點與事件訂閱者(Event Handler)MainComponent:各服務的組裝根(DI 註冊)
共用套件則抽成獨立專案:E-Commerce.SeedWork(AggregateRoot、DomainEvent 等
DDD 基礎建構)、E-Commerce.EventBus(以 Wolverine 包裝 Kafka 的領域事件匯流
排)、E-Commerce.Diagnostics(Tracing/Metrics/AOP)。並以 .NET Aspire
作為本機開發時的服務編排(AppHost)。
可觀測性亮點
這個專案真正想練習的重點:
- 以 .NET 內建
ActivitySource在三個服務中發送 Trace,並將 TraceId 寫入 領域事件本身,讓一筆訂單從建立、付款到出貨的完整流程,能在分散式追蹤中被 串成同一條 Trace - 自訂
MethodTracingAttribute(AOP)快速為方法加上追蹤埋點,不需手動包using activity - 透過
IMeterFactory建立自訂 Metrics(計數器、直方圖、浮動計數器),例如 訂單金額分佈、待處理付款數量 - 以 OpenTelemetry Collector 統一收集 Trace/Metric/Log,轉送至 Tempo、 Prometheus、Loki,並用 Grafana 儀表板呈現
- 整合 Grafana MCP 與 Tempo MCP,讓開發時能直接用 TraceQL/PromQL/LogQL 在編輯器內查詢追蹤與指標,不必切換到 Grafana 介面除錯
使用技術
- 應用層:.NET 8、ASP.NET Core Minimal API、Wolverine、Kafka
- 架構:Domain-Driven Design(戰略+戰術設計)、Event Sourcing、 Hexagonal Architecture
- 資料層:EF Core、Code First、MSSQL
- 可觀測性:OpenTelemetry、Grafana、Tempo、Loki、Prometheus
- 開發體驗:.NET Aspire、xUnit、Docker Compose
目前狀態
專案仍在持續補充中,目前已完整展示三服務間的事件編舞(Choreography)與可觀測 性串接;後續規劃導入 SAGA Pattern 讓跨服務交易一致性有明確的補償流程可 循,並在服務前加上 API Gateway。