diff --git a/src/main/java/com/cleanengine/coin/CoinApplication.java b/src/main/java/com/cleanengine/coin/CoinApplication.java index 21ed9457..cf0d1fe1 100644 --- a/src/main/java/com/cleanengine/coin/CoinApplication.java +++ b/src/main/java/com/cleanengine/coin/CoinApplication.java @@ -3,12 +3,14 @@ import jakarta.annotation.PostConstruct; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableScheduling; import java.util.TimeZone; @EnableScheduling +@EnableAsync @SpringBootApplication public class CoinApplication { diff --git a/src/main/java/com/cleanengine/coin/trade/application/TradeExecutor.java b/src/main/java/com/cleanengine/coin/trade/application/TradeExecutor.java index b59e7c9f..1e96fbfa 100644 --- a/src/main/java/com/cleanengine/coin/trade/application/TradeExecutor.java +++ b/src/main/java/com/cleanengine/coin/trade/application/TradeExecutor.java @@ -2,7 +2,6 @@ import com.cleanengine.coin.common.error.BusinessException; import com.cleanengine.coin.common.response.ErrorStatus; -import com.cleanengine.coin.order.application.OrderService; import com.cleanengine.coin.order.domain.BuyOrder; import com.cleanengine.coin.order.domain.Order; import com.cleanengine.coin.order.domain.OrderStatus; @@ -89,12 +88,12 @@ public void executeTrade(WaitingOrders waitingOrders, TradePair tr tradeExecutedEventPublisher.publish(tradeExecutedEvent); } - public void increaseAccountCash(Order order, Double amount) { + private Account increaseAccountCash(Order order, Double amount) { Account account = accountService.findAccountByUserId(order.getUserId()).orElseThrow(); - accountService.save(account.increaseCash(amount)); + return accountService.save(account.increaseCash(amount)); } - public void updateWalletAfterTrade(Order order, String ticker, double tradedSize, double totalTradedPrice) { + private Wallet updateWalletAfterTrade(Order order, String ticker, double tradedSize, double totalTradedPrice) { if (order instanceof BuyOrder) { Wallet buyerWallet = walletService.findWalletByUserIdAndTicker(order.getUserId(), ticker); double updatedBuySize = buyerWallet.getSize() + tradedSize; @@ -103,17 +102,17 @@ public void updateWalletAfterTrade(Order order, String ticker, double tradedSize buyerWallet.setSize(updatedBuySize); buyerWallet.setBuyPrice(updatedBuyPrice); // TODO : ROI 계산 - walletService.save(buyerWallet); + return walletService.save(buyerWallet); } else if (order instanceof SellOrder) { // 매도 시에는 평단가 변동 없음 Wallet sellerWallet = walletService.findWalletByUserIdAndTicker(order.getUserId(), ticker); - walletService.save(sellerWallet); + return walletService.save(sellerWallet); } else { throw new BusinessException("Unsupported order type: " + order.getClass().getName(), ErrorStatus.INTERNAL_SERVER_ERROR); } } - public Trade insertNewTrade(String ticker, BuyOrder buyOrder, SellOrder sellOrder, double tradeSize, Double tradePrice) { + private Trade insertNewTrade(String ticker, BuyOrder buyOrder, SellOrder sellOrder, double tradeSize, Double tradePrice) { Trade newTrade = Trade.of(ticker, LocalDateTime.now(), buyOrder.getUserId(), sellOrder.getUserId(), tradePrice, tradeSize); return tradeService.save(newTrade); @@ -184,7 +183,7 @@ private void removeCompletedSellOrder(WaitingOrders waitingOrders, SellOrder ord } } - public void updateCompletedOrderStatus(Order order) { + private void updateCompletedOrderStatus(Order order) { order.setState(OrderStatus.DONE); } diff --git a/src/main/java/com/cleanengine/coin/trade/application/TradeFlowService.java b/src/main/java/com/cleanengine/coin/trade/application/TradeFlowService.java index 30f2415e..5a7cab22 100644 --- a/src/main/java/com/cleanengine/coin/trade/application/TradeFlowService.java +++ b/src/main/java/com/cleanengine/coin/trade/application/TradeFlowService.java @@ -5,19 +5,20 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import java.util.Optional; @Slf4j @RequiredArgsConstructor -@Transactional @Component public class TradeFlowService { private final TradeMatcher tradeMatcher; private final TradeExecutor tradeExecutor; + @Transactional(propagation = Propagation.REQUIRES_NEW) public void execMatchAndTrade(String ticker) { WaitingOrders waitingOrders = tradeMatcher.getWaitingOrders(ticker); // TODO : peek() 해온 Order 객체들을 lock -> 체결 도중 취소 방지 diff --git a/src/main/java/com/cleanengine/coin/trade/application/TradeQueueManager.java b/src/main/java/com/cleanengine/coin/trade/application/TradeQueueManager.java index 6dab0368..46d84d26 100644 --- a/src/main/java/com/cleanengine/coin/trade/application/TradeQueueManager.java +++ b/src/main/java/com/cleanengine/coin/trade/application/TradeQueueManager.java @@ -1,7 +1,10 @@ package com.cleanengine.coin.trade.application; import com.cleanengine.coin.order.application.event.OrderCreated; +import com.cleanengine.coin.order.application.event.OrderInsertedToQueue; import lombok.extern.slf4j.Slf4j; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; import org.springframework.transaction.event.TransactionalEventListener; @@ -15,8 +18,8 @@ public TradeQueueManager(TradeFlowService tradeFlowService) { this.tradeFlowService = tradeFlowService; } - @TransactionalEventListener - public void handleOrderInserted(OrderCreated event) { + @EventListener + public void handleOrderInserted(OrderInsertedToQueue event) { try { tradeFlowService.execMatchAndTrade(event.order().getTicker()); } catch (Exception e) { diff --git a/src/test/java/com/cleanengine/coin/trade/application/TradeExecuteLoadTest.java b/src/test/java/com/cleanengine/coin/trade/application/TradeExecuteLoadTest.java index dded4ca8..dd4aa933 100644 --- a/src/test/java/com/cleanengine/coin/trade/application/TradeExecuteLoadTest.java +++ b/src/test/java/com/cleanengine/coin/trade/application/TradeExecuteLoadTest.java @@ -3,7 +3,6 @@ import com.cleanengine.coin.common.domain.port.PriorityQueueStore; import com.cleanengine.coin.order.application.OrderService; import com.cleanengine.coin.order.application.dto.OrderCommand; -import com.cleanengine.coin.order.application.dto.OrderInfo; import com.cleanengine.coin.order.domain.BuyOrder; import com.cleanengine.coin.order.domain.OrderType; import com.cleanengine.coin.order.domain.SellOrder; @@ -11,6 +10,7 @@ import com.cleanengine.coin.order.domain.spi.WaitingOrdersManager; import com.cleanengine.coin.trade.repository.TradeRepository; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -20,6 +20,7 @@ import java.time.LocalDateTime; @SpringBootTest +@Disabled class TradeExecuteLoadTest { @Autowired