iT邦幫忙

2025 iThome 鐵人賽

DAY 5
0
Software Development

事件驅動電力交易平台:Spring Boot 實戰系列 第 5

Day 5|為什麼選擇事件驅動 Wallet 核定,而不是 API 呼叫?以及我如何追蹤訂單狀態

  • 分享至 

  • xImage
  •  

在交易系統裡,一筆訂單成立的關鍵步驟是 Wallet 的資產核定。
這裡我選擇使用 事件驅動 的方式完成核定,而不是讓 Order Service 直接呼叫 Wallet API。這個決策來自兩個考量:

  1. 高併發效能
  2. 訂單狀態追蹤設計

  1. 為什麼不用 API 呼叫?
    如果 Order Service 每次掛單都同步呼叫 Wallet API 驗證與凍結資產,會遇到幾個問題:
    • 同步阻塞:Order Service 需要等待 Wallet 的回應,交易高峰期(例如幾百或幾千筆掛單同時進來)容易卡住。
    • 單點瓶頸:Wallet API 可能被瞬間打爆,造成服務延遲甚至掛掉。
    • 失敗傳遞複雜:當 Wallet 出現暫時錯誤時,Order 要處理重試與超時,程式邏輯會變得非常複雜。

  1. 事件驅動的優勢
    相對地,透過 RabbitMQ 事件驅動:
    • 非同步解耦:Order Service 只要發送 OrderCreateEvent,不必等待 Wallet 的回覆。
    • 高擴展性:Wallet Service 可以水平擴展 Consumer,平行消費訂單事件。
    • 可靠性:事件可持久化,Wallet 掛掉重啟後仍能繼續處理。
    • 流量削峰:事件進入 Queue,自然排隊處理,不會瞬間壓垮 Wallet。
    這是高併發交易系統常見的設計模式。

  1. 那我要怎麼追蹤訂單狀態?
    事件驅動的挑戰是:Order Service 沒有即時知道 Wallet 核定結果。
    為了解決這個問題,我在 Order Service 中設計了一個 記憶體內的 Map 來追蹤訂單狀態。
    流程如下:
  2. Order Service 接收到掛單請求 → 建立 OrderCreateEvent → 把訂單狀態先放進一個本地 ConcurrentHashMap,狀態為 PENDING。
  3. Wallet Service 驗證成功後,發送 OrderCreatedEvent。
  4. Order Service 消費 OrderCreatedEvent → 更新 Map 中對應的訂單狀態為 CREATED,並附上核定時間與鎖定資產資訊。
  5. 如果後續 MatchEngine 發送 OrderMatchedEvent,再更新為 MATCHED。
    程式碼摘要:
@Component
public class OrderStatusTracker {
    private final Map<UUID, OrderStatus> statusMap = new ConcurrentHashMap<>();

    public void markPending(UUID orderId) {
        statusMap.put(orderId, OrderStatus.PENDING);
    }

    @RabbitListener(queues = ORDER_CREATED_QUEUE)
    public void onOrderCreated(OrderCreatedEvent event) {
        statusMap.put(event.getOrderId(), OrderStatus.CREATED);
    }

    @RabbitListener(queues = ORDER_MATCHED_QUEUE)
    public void onOrderMatched(OrderMatchedEvent event) {
        statusMap.put(event.getOrderId(), OrderStatus.MATCHED);
    }

    public OrderStatus getStatus(UUID orderId) {
        return statusMap.get(orderId);
    }
}

這樣,前端如果要查詢訂單狀態,只要呼叫 Order Service 的查詢 API,就能直接讀取這個 Map,不需要即時查 Wallet 或 MatchEngine。


  1. 為什麼用記憶體 Map?
    這種做法有幾個優點:
    • 快速:查詢幾乎是 O(1),適合即時交易平台。
    • 簡單:不需要額外儲存層,適合 MVP 或 Demo 階段。
    • 低延遲:事件一進來就能即時更新狀態,前端查詢到的資訊非常新鮮。

  1. 還有哪些其他選擇?
    雖然記憶體 Map 很快,但它也有侷限:
    • 服務重啟後,Map 會清空,訂單狀態需要重新建立。
    • 多個 Order Service 節點下,狀態不會自動同步。
    因此,在規模更大時,可以考慮:
    Redis
    • 適合高併發讀寫
    • 可做訂單狀態的快取中心
    • 支援 pub/sub,天然適合事件驅動系統
    • 缺點:需要額外運維 Redis,並設計 TTL 與資料一致性策略
    MongoDB
    • 適合歷史狀態追蹤
    • 能儲存訂單的全量紀錄(狀態變更 log)
    • 適合事後分析或審計需求
    • 缺點:查詢效能略慢於 Redis,不一定適合即時查詢
    混合模式(最佳實務)
    • 即時狀態放 Redis,給前端快速查詢
    • 歷史紀錄放 MongoDB 或 SQL,保留完整交易 log
    • Order Service 記憶體 Map → 作為第一層快取,適合單機 demo 或小規模環境

  1. 小結
    我在目前版本選擇用 記憶體 Map + 事件回傳更新 來追蹤訂單狀態,因為它簡單、快速,足以支援 MVP 階段。
    未來如果要支援 多節點部署或更大流量,我會改用 Redis 作為訂單狀態中心,並將完成的歷史訂單存入postgreSql資料庫中
    這樣設計能同時兼顧:
    • 即時性(Map / Redis)
    • 可靠性(事件持久化)
    • 可追溯性(MongoDB / SQL)

上一篇
Day 4|事件驅動實作:Wallet Service 接收訂單事件並執行資產鎖定
下一篇
Day 6|事件契約測試:使用 Spring Cloud Contract 驗證 Wallet Service 發送事件
系列文
事件驅動電力交易平台:Spring Boot 實戰9
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言