在上一篇中,我介紹了如何在 Order Service 發送一筆 OrderCreateEvent 到 RabbitMQ。本篇要延續這條事件流,帶你看看我如何在 Wallet Service 中接收這筆事件,並進行餘額驗證與資產鎖定。這是事件驅動架構中的第二步:訊息消費與處理。
消費端設定:監聽事件佇列
在 Wallet Service 中,我定義了一個 Listener 類別來處理來自 RabbitMQ 的訂單建立事件。這個類別使用 @Component 讓 Spring 自動註冊為 Bean,也透過 @RabbitListener 註解來接收指定佇列的事件:
@Slf4j
@Component
public class CreateOrderListener {
@Autowired
private WalletRepository walletRepository;
@Autowired
private RabbitTemplate rabbitTemplate;
@RabbitListener(queues = ORDER_CREATE_QUEUE)
public void onOrderCreate(OrderCreateEvent event) {
if (!isWalletEnough(event)) {
log.warn("訂單金額超過可用餘額: " + event.getUserId());
throw new ReturnException("訂單金額超過可用餘額: " + event.getUserId());
}
if (!isWalletEnoughForSell(event)) {
log.warn("訂單可用電量不足: " + event.getUserId());
throw new ReturnException("訂單可用電量不足: " + event.getUserId());
}
// 驗證通過後,進行資產鎖定
WalletEntity wallet = walletRepository.findByUserId(event.getUserId());
if ("BUY".equals(event.getOrderType())) {
int cost = event.getPrice() * event.getAmount();
wallet.setAvailableCurrency(wallet.getAvailableCurrency() - cost);
wallet.setLockedCurrency(wallet.getLockedCurrency() + cost);
} else if ("SELL".equals(event.getOrderType())) {
int quantity = event.getAmount();
wallet.setAvailableAmount(wallet.getAvailableAmount() - quantity);
wallet.setLockedAmount(wallet.getLockedAmount() + quantity);
}
walletRepository.save(wallet);
log.info(" 資產鎖定完成,用戶: {}", event.getUserId());
// 發送已鎖定資產的事件給 MatchEngine 等後續處理
OrderCreatedEvent orderCreatedEvent = OrderCreatedEvent.builder()
.orderId(event.getOrderId())
.userId(event.getUserId())
.price(event.getPrice())
.quantity(event.getAmount())
.type(event.getOrderType())
.createdAt(event.getCreatedAt())
.build();
rabbitTemplate.convertAndSend(ORDER_EXCHANGE, ORDER_CREATED_KEY, orderCreatedEvent);
}
private boolean isWalletEnough(OrderCreateEvent event) {
WalletEntity wallet = walletRepository.findByUserId(event.getUserId());
if (wallet == null) {
log.warn("找不到使用者錢包: " + event.getUserId());
return false;
}
return !"BUY".equals(event.getOrderType()) ||
event.getAmount() * event.getPrice() <= wallet.getAvailableCurrency();
}
private boolean isWalletEnoughForSell(OrderCreateEvent event) {
WalletEntity wallet = walletRepository.findByUserId(event.getUserId());
if (wallet == null) {
log.warn("找不到使用者錢包: " + event.getUserId());
return false;
}
return !"SELL".equals(event.getOrderType()) ||
event.getAmount() <= wallet.getAvailableAmount();
}
}
資產鎖定邏輯說明
當收到 OrderCreateEvent 並通過驗證後,我會立即從用戶的錢包中扣除對應的金額或電量,並將這些資產移至鎖定欄位,以避免後續重複下單或餘額不足的問題。
例如:
功能流程總結
下一步預告
下一篇我將介紹如何透過 Spring Cloud Contract 撰寫測試,模擬事件的進入與驗證 Wallet Service 的反應是否正確,實現事件驅動架構中「契約優先」的測試策略,讓微服務之間的溝通更安全、協作更穩固。