From 5aa19ff5c3321e1f7b39de1a294e92da5432036c Mon Sep 17 00:00:00 2001 From: Junh-b Date: Thu, 5 Jun 2025 10:49:36 +0900 Subject: [PATCH 1/5] =?UTF-8?q?fix:=20=EC=A3=BC=EB=AC=B8=EC=83=9D=EC=84=B1?= =?UTF-8?q?=20=EC=9E=84=EC=8B=9C=20=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 반환 예외 타입 변경으로 인한 핸들링 로직 변경 --- .../coin/realitybot/service/OrderGenerateService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/cleanengine/coin/realitybot/service/OrderGenerateService.java b/src/main/java/com/cleanengine/coin/realitybot/service/OrderGenerateService.java index 849d769b..11d23596 100644 --- a/src/main/java/com/cleanengine/coin/realitybot/service/OrderGenerateService.java +++ b/src/main/java/com/cleanengine/coin/realitybot/service/OrderGenerateService.java @@ -1,9 +1,9 @@ package com.cleanengine.coin.realitybot.service; import com.cleanengine.coin.common.error.DomainValidationException; -import com.cleanengine.coin.order.application.OrderService; import com.cleanengine.coin.order.adapter.out.persistentce.account.OrderAccountRepository; import com.cleanengine.coin.order.adapter.out.persistentce.wallet.OrderWalletRepository; +import com.cleanengine.coin.order.application.OrderService; import com.cleanengine.coin.trade.entity.Trade; import com.cleanengine.coin.trade.repository.TradeRepository; import com.cleanengine.coin.user.domain.Account; @@ -170,7 +170,7 @@ private void createOrderWithFallback(String ticker,boolean isBuy, double volume, try { orderService.createOrderWithBot(ticker, isBuy, volume, price); - } catch (DomainValidationException e) { + } catch (IllegalArgumentException e) { log.debug("잔량 부족: {}", e.getMessage()); try { resetBot(ticker); From 0635421853c968d508af6768e6ee80fbff7534a7 Mon Sep 17 00:00:00 2001 From: Junh-b Date: Thu, 5 Jun 2025 10:51:55 +0900 Subject: [PATCH 2/5] =?UTF-8?q?fix:=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20?= =?UTF-8?q?=ED=95=B8=EB=93=A4=EB=9F=AC=20=EB=B3=84=EB=8F=84=20=EC=8A=A4?= =?UTF-8?q?=EB=A0=88=EB=93=9C=20=EB=8F=99=EC=9E=91=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 최근 반영사항으로 일시적으로 기존 싱글 스레드에서 처리되던 로직을 다시 별도 스레드 동작 방식으로 변경했습니다. --- src/main/java/com/cleanengine/coin/CoinApplication.java | 2 ++ .../coin/trade/application/TradeQueueManager.java | 7 +++++-- 2 files changed, 7 insertions(+), 2 deletions(-) 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/TradeQueueManager.java b/src/main/java/com/cleanengine/coin/trade/application/TradeQueueManager.java index 6dab0368..0f81de83 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 @Async + public void handleOrderInserted(OrderInsertedToQueue event) { try { tradeFlowService.execMatchAndTrade(event.order().getTicker()); } catch (Exception e) { From 0871699baf26bc84300771645e785d5b47faee85 Mon Sep 17 00:00:00 2001 From: caniro Date: Fri, 6 Jun 2025 14:19:22 +0900 Subject: [PATCH 3/5] =?UTF-8?q?refactor:=20=EC=A0=91=EA=B7=BC=20=EC=A0=9C?= =?UTF-8?q?=ED=95=9C=EC=9E=90=20=EB=B0=8F=20=EB=A6=AC=ED=84=B4=20=ED=83=80?= =?UTF-8?q?=EC=9E=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../coin/trade/application/TradeExecutor.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) 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); } From 9d4168741716460e94f198c6cd119408db694383 Mon Sep 17 00:00:00 2001 From: caniro Date: Fri, 6 Jun 2025 14:20:54 +0900 Subject: [PATCH 4/5] =?UTF-8?q?fix:=20=EC=B2=B4=EA=B2=B0=20=EB=A9=80?= =?UTF-8?q?=ED=8B=B0=EC=8A=A4=EB=A0=88=EB=93=9C=20->=20=EB=8B=A8=EC=9D=BC?= =?UTF-8?q?=EC=8A=A4=EB=A0=88=EB=93=9C=20=EB=B3=80=EA=B2=BD(=EC=B2=B4?= =?UTF-8?q?=EA=B2=B0=20=EC=95=88=EC=A0=95=ED=99=94=EB=A5=BC=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20=EC=9E=84=EC=8B=9C=20=EC=A1=B0=EC=B9=98)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cleanengine/coin/trade/application/TradeFlowService.java | 3 ++- .../cleanengine/coin/trade/application/TradeQueueManager.java | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) 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 0f81de83..46d84d26 100644 --- a/src/main/java/com/cleanengine/coin/trade/application/TradeQueueManager.java +++ b/src/main/java/com/cleanengine/coin/trade/application/TradeQueueManager.java @@ -18,7 +18,7 @@ public TradeQueueManager(TradeFlowService tradeFlowService) { this.tradeFlowService = tradeFlowService; } - @EventListener @Async + @EventListener public void handleOrderInserted(OrderInsertedToQueue event) { try { tradeFlowService.execMatchAndTrade(event.order().getTicker()); From 53ad317906d60f9e3b87258e323abd3715051547 Mon Sep 17 00:00:00 2001 From: caniro Date: Fri, 6 Jun 2025 14:21:38 +0900 Subject: [PATCH 5/5] =?UTF-8?q?chore:=20=EB=B6=88=ED=95=84=EC=9A=94=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20Disable=20=EC=A7=80=EC=A0=95(?= =?UTF-8?q?=EC=B6=94=ED=9B=84=20=EC=9E=AC=EC=9E=91=EC=84=B1=20=ED=95=84?= =?UTF-8?q?=EC=9A=94)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../coin/trade/application/TradeExecuteLoadTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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