From 9d555ce3a2e3bd54363bbf13b937950363a325e1 Mon Sep 17 00:00:00 2001 From: BananMoon Date: Sun, 22 Sep 2024 22:37:18 +0900 Subject: [PATCH 1/5] =?UTF-8?q?delete:=20Discord=20=EB=A9=94=EC=8B=9C?= =?UTF-8?q?=EC=A7=80=20=EB=B0=9C=EC=86=A1=20=EA=B8=B0=EB=8A=A5=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EB=A1=9C=EC=A7=81,=20DTO=20=EC=A0=9C=EA=B1=B0=20(C?= =?UTF-8?q?onsumer=20Application=EC=97=90=20=EC=9D=B4=EB=8F=99)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/DiscordWebhookNotifyRequestDto.java | 36 ------ .../notification/dto/MessageContentDto.java | 120 ------------------ .../dto/MessageContentDtoTest.java | 110 ---------------- 3 files changed, 266 deletions(-) delete mode 100644 src/main/java/com/devspacehub/ast/domain/notification/dto/DiscordWebhookNotifyRequestDto.java delete mode 100644 src/main/java/com/devspacehub/ast/domain/notification/dto/MessageContentDto.java delete mode 100644 src/test/java/com/devspacehub/ast/domain/notification/dto/MessageContentDtoTest.java diff --git a/src/main/java/com/devspacehub/ast/domain/notification/dto/DiscordWebhookNotifyRequestDto.java b/src/main/java/com/devspacehub/ast/domain/notification/dto/DiscordWebhookNotifyRequestDto.java deleted file mode 100644 index bae578e..0000000 --- a/src/main/java/com/devspacehub/ast/domain/notification/dto/DiscordWebhookNotifyRequestDto.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - © 2023 devspacehub, Inc. All rights reserved. - - name : DiscordWebhookNotifyRequestDto - creation : 2024.1.27 - author : Yoonji Moon - */ -package com.devspacehub.ast.domain.notification.dto; - -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Builder; -import lombok.Getter; -import lombok.Setter; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; - -import java.util.function.Consumer; - -/** - * Discord Webhook 이용한 알림 요청 DTO. - */ -@Builder -@Getter -@Setter -public class DiscordWebhookNotifyRequestDto { - @JsonProperty(value = "username") - private String senderName; - @JsonProperty(value = "content") - private String message; - - public static Consumer setHeaders() { - HttpHeaders headers = new HttpHeaders(); - headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); - return httpHeaders -> httpHeaders.addAll(headers); - } -} diff --git a/src/main/java/com/devspacehub/ast/domain/notification/dto/MessageContentDto.java b/src/main/java/com/devspacehub/ast/domain/notification/dto/MessageContentDto.java deleted file mode 100644 index 03c72ed..0000000 --- a/src/main/java/com/devspacehub/ast/domain/notification/dto/MessageContentDto.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - © 2024 devspacehub, Inc. All rights reserved. - - name : MessageContentDto - creation : 2024.4.24 - author : Yoonji Moon - */ - -package com.devspacehub.ast.domain.notification.dto; - -import com.devspacehub.ast.common.constant.OpenApiType; -import com.devspacehub.ast.domain.orderTrading.OrderTrading; -import com.devspacehub.ast.domain.my.dto.orderConclusion.OrderConclusionDto; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -import java.math.BigDecimal; - -/** - * 디스코드 메시지 내용 DTO 추상 클래스. - */ -@Getter -@NoArgsConstructor -@SuperBuilder -@AllArgsConstructor -public abstract class MessageContentDto { - private String title; - private String accountStatusKor; - private String itemNameKor; - private String itemCode; - private OpenApiType openApiType; - private int orderQuantity; - private BigDecimal orderPrice; - private String orderNumber; - private String orderTime; - - - /** - * 알림 메시지 공통 내용 작성 - * @param content MessageContentDto 상속받는 메시지 내용 DTO - * @return 완성된 메시지 내용 - */ - public String createMessage(T content) { - StringBuilder sb = new StringBuilder(); - - sb.append("**[").append(content.getOrderTime()).append("] ").append(content.getTitle()).append("**"); - sb.append("\n계좌 상태 : ").append(content.getAccountStatusKor()); - sb.append("\n종목명 : ").append(content.getItemNameKor()); - sb.append(" (").append(content.getItemCode()).append(")"); - sb.append("\n매매구분 : ").append(content.getOpenApiType().getDiscription()); - sb.append("\n주문번호 : ").append(content.getOrderNumber()); - sb.append("\n주문수량 : ").append(content.getOrderQuantity()).append("주"); - sb.append("\n주문단가 : ").append(content.getOrderPrice()); - - return sb.toString(); - } - - /** - * 매수/매도 주문 결과 메세지 DTO - * MessageContentDto 상속 - */ - @Getter - @SuperBuilder - @AllArgsConstructor - public static class OrderResult extends MessageContentDto{ - - public static OrderResult fromOne(OpenApiType openApiType, String accountStatus, OrderTrading orderTrading) { - return OrderResult.builder() - .title("주문 완료") - .accountStatusKor(accountStatus) - .itemNameKor(orderTrading.getItemNameKor()) - .itemCode(orderTrading.getItemCode()) - .openApiType(openApiType) - .orderQuantity(orderTrading.getOrderQuantity()) - .orderPrice(orderTrading.getOrderPrice()) - .orderNumber(orderTrading.getOrderNumber()) - .orderTime(orderTrading.getOrderTime()) - .build(); - } - } - - /** - * 체결 결과 메세지 DTO - * MessageContentDto 상속 - */ - @Getter - @SuperBuilder - public static class ConclusionResult extends MessageContentDto { - private int concludedQuantity; - private BigDecimal concludedPrice; - - public static ConclusionResult fromOne(OpenApiType orderApiType, String accountStatus, OrderConclusionDto orderConclusion) { - return ConclusionResult.builder() - .title("체결 완료") - .accountStatusKor(accountStatus) - .itemNameKor(orderConclusion.getItemNameKor()) - .itemCode(orderConclusion.getItemCode()) - .openApiType(orderApiType) - .orderQuantity(orderConclusion.getOrderQuantity()) - .orderPrice(orderConclusion.getOrderPrice()) - .orderNumber(orderConclusion.getOrderNumber()) - .concludedQuantity(orderConclusion.getConcludedQuantity()) - .concludedPrice(orderConclusion.getConcludedPrice()) - .orderTime(orderConclusion.getOrderTime()) - .build(); - } - - @Override - public String createMessage(T content) { - ConclusionResult embodiedContent = (ConclusionResult) content; - - StringBuilder commonMessage = new StringBuilder(super.createMessage(content)); - commonMessage.append("\n체결수량 : ").append(embodiedContent.getConcludedQuantity()).append("주"); - commonMessage.append("\n체결금액 : ").append(embodiedContent.getConcludedPrice()); - return commonMessage.toString(); - } - } -} diff --git a/src/test/java/com/devspacehub/ast/domain/notification/dto/MessageContentDtoTest.java b/src/test/java/com/devspacehub/ast/domain/notification/dto/MessageContentDtoTest.java deleted file mode 100644 index 5148552..0000000 --- a/src/test/java/com/devspacehub/ast/domain/notification/dto/MessageContentDtoTest.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - © 2024 devspacehub, Inc. All rights reserved. - - name : MessageContentDtoTest - creation : 2024.4.25 - author : Yoonji Moon - */ - -package com.devspacehub.ast.domain.notification.dto; - -import com.devspacehub.ast.common.constant.OpenApiType; -import com.devspacehub.ast.common.constant.ProfileType; -import com.devspacehub.ast.domain.orderTrading.OrderTrading; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import java.math.BigDecimal; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.*; - -/** - * MessageContentDto 테스트 코드. - */ -class MessageContentDtoTest { - - @DisplayName("체결 결과 DTO를 이용해 디스코드 메시지 전송 DTO를 생성한다.") - @Test - void createMessageTest_conclusion() { - // given - MessageContentDto.ConclusionResult orderResult = MessageContentDto.ConclusionResult.builder() - .title("체결 완료") - .accountStatusKor("모의") - .itemNameKor("삼성전자") - .itemCode("005930") - .openApiType(OpenApiType.DOMESTIC_ORDER_CONCLUSION_FIND) - .orderQuantity(10) - .orderPrice(BigDecimal.valueOf(80000)) - .orderNumber("0123456") - .orderTime("090130") - .concludedQuantity(10) - .concludedPrice(BigDecimal.valueOf(80000)) - .build(); - // when - String message = orderResult.createMessage(orderResult); - // then - assertEquals(""" - **[090130] 체결 완료** - 계좌 상태 : 모의 - 종목명 : 삼성전자 (005930) - 매매구분 : 국내 주식 일별주문 체결 조회 - 주문번호 : 0123456 - 주문수량 : 10주 - 주문단가 : 80000 - 체결수량 : 10주 - 체결금액 : 80000""", message); - } - - @DisplayName("매수 주문 결과 DTO를 이용해 디스코드 메시지 전송 DTO를 생성한다.") - @Test - void createMessageTest_orderResult() { - // given - MessageContentDto.OrderResult orderResult = MessageContentDto.OrderResult.builder() - .title("주문 완료") - .accountStatusKor("실전") - .itemNameKor("삼성전자") - .itemCode("005930") - .openApiType(OpenApiType.DOMESTIC_STOCK_BUY_ORDER) - .orderQuantity(10) - .orderPrice(BigDecimal.valueOf(80000)) - .orderNumber("0123456") - .orderTime("090130") - .build(); - // when - String message = orderResult.createMessage(orderResult); - // then - assertEquals(""" - **[090130] 주문 완료** - 계좌 상태 : 실전 - 종목명 : 삼성전자 (005930) - 매매구분 : 국내 주식(현금)-매수 - 주문번호 : 0123456 - 주문수량 : 10주 - 주문단가 : 80000""", message); - } - @DisplayName("OpenApiType, 운영 상태 값, 주문 결과를 전달하여 디스코드 메세지 > 주문 결과 Dto를 생성한다.") - @Test - void orderResultTest_fromOne() { - // given - System.setProperty("spring.profiles.active", "test"); - OpenApiType givenOpenApiType = OpenApiType.OVERSEAS_STOCK_SELL_ORDER; - OrderTrading givenOrderTrading = OrderTrading.builder() - .itemCode("AAPL") - .itemNameKor("애플") - .orderPrice(new BigDecimal("134.02")) - .orderNumber("111111") - .orderQuantity(1) - .orderTime("093030") - .build(); - // when - MessageContentDto.OrderResult result = MessageContentDto.OrderResult.fromOne( - givenOpenApiType, ProfileType.getAccountStatus(), givenOrderTrading); - // then - assertThat(result.getTitle()).isEqualTo("주문 완료"); - assertThat(result.getItemNameKor()).isEqualTo("애플"); - assertThat(result.getOrderPrice()).isEqualTo(new BigDecimal("134.02")); - assertThat(result.getAccountStatusKor()).isEqualTo("모의"); - assertThat(result.getOpenApiType()).isEqualTo(givenOpenApiType); - } -} \ No newline at end of file From b465e4f9e372188003535c557c8dae3c425ff6c8 Mon Sep 17 00:00:00 2001 From: BananMoon Date: Tue, 24 Sep 2024 21:37:07 +0900 Subject: [PATCH 2/5] refactor: change method name, arguments --- .../ast/domain/mashup/service/MashupService.java | 4 ++-- .../service/OverseasReservationBuyOrderService.java | 8 ++++---- .../service/ReservationBuyOrderServiceImpl.java | 4 ++-- .../domain/orderTrading/service/BuyOrderServiceImpl.java | 4 ++-- .../domain/orderTrading/service/SellOrderServiceImpl.java | 4 ++-- .../service/overseas/OverseasBuyOrderServiceImpl.java | 4 ++-- .../service/overseas/OverseasSellOrderServiceImpl.java | 4 ++-- .../service/OverseasReservationBuyOrderServiceTest.java | 6 +++--- .../ReservationBuyOrderServiceIntegrationTest.java | 4 ++-- 9 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/main/java/com/devspacehub/ast/domain/mashup/service/MashupService.java b/src/main/java/com/devspacehub/ast/domain/mashup/service/MashupService.java index 0fc75f6..a2bea08 100644 --- a/src/main/java/com/devspacehub/ast/domain/mashup/service/MashupService.java +++ b/src/main/java/com/devspacehub/ast/domain/mashup/service/MashupService.java @@ -15,7 +15,7 @@ import com.devspacehub.ast.domain.my.service.MyService; import com.devspacehub.ast.domain.my.service.MyServiceFactory; import com.devspacehub.ast.domain.notification.Notificator; -import com.devspacehub.ast.domain.notification.dto.MessageContentDto; +import com.devspacehub.ast.domain.notification.dto.ItemConclusionResultDto; import com.devspacehub.ast.domain.oauth.service.OAuthService; import com.devspacehub.ast.domain.orderTrading.OrderTrading; import com.devspacehub.ast.domain.orderTrading.OrderTradingServiceFactory; @@ -78,7 +78,7 @@ public void startOrderConclusionResultProcess(OpenApiType openApiType) { myServiceImpl.updateMyReservationOrderUseYn(orderConclusion, today); // 체결된 종목들에 대해 디스코드 메시지 전달 - notificator.sendMessage(MessageContentDto.ConclusionResult.fromOne(openApiType, getAccountStatus(), orderConclusion)); + notificator.sendStockResultMessage(ItemConclusionResultDto.from(openApiType, getAccountStatus(), orderConclusion)); RequestUtil.timeDelay(); } } diff --git a/src/main/java/com/devspacehub/ast/domain/my/reservationOrderInfo/service/OverseasReservationBuyOrderService.java b/src/main/java/com/devspacehub/ast/domain/my/reservationOrderInfo/service/OverseasReservationBuyOrderService.java index 8a150d8..903e31a 100644 --- a/src/main/java/com/devspacehub/ast/domain/my/reservationOrderInfo/service/OverseasReservationBuyOrderService.java +++ b/src/main/java/com/devspacehub/ast/domain/my/reservationOrderInfo/service/OverseasReservationBuyOrderService.java @@ -21,7 +21,7 @@ import com.devspacehub.ast.domain.my.service.MyService; import com.devspacehub.ast.domain.my.service.MyServiceFactory; import com.devspacehub.ast.domain.notification.Notificator; -import com.devspacehub.ast.domain.notification.dto.MessageContentDto; +import com.devspacehub.ast.domain.notification.dto.DefaultItemInfoDto.ItemOrderResultDto; import com.devspacehub.ast.domain.orderTrading.OrderTrading; import com.devspacehub.ast.domain.orderTrading.OrderTradingRepository; import com.devspacehub.ast.domain.orderTrading.dto.OverseasStockOrderApiReqDto; @@ -134,7 +134,7 @@ private OrderTrading buy(ReservationStockItem reservationItem, OpenApiProperties /** * 예약 종목 데이터 validation check * @param reservation 예약 종목 데이터 - * @return 유효하면 True + * @throws InvalidValueException 값이 유효하지 않은 경우 */ protected void validateValue(ReservationStockItem reservation) { if (StringUtils.isBlank(reservation.getItemCode())) { @@ -197,7 +197,7 @@ private BigDecimal getMyDeposit(ReservationStockItem reservation) { /** * 예수금과 주문할 총 가격을 비교하여 주문 가능한지 체크한다. * @param reservation 해외 예약 종목 정보 Dto - * @return 주문 가능한지 여부 boolean 값 + * @throws InsufficientMoneyException 잔고가 부족한 경우 */ protected void sufficientDepositCheck(ReservationStockItem reservation, BigDecimal myDeposit) { StockItemDto overseasStockItem = reservation.getStockItem(); @@ -261,7 +261,7 @@ public List saveOrderInfos(List orderTradingInfos) { @Override public void orderApiResultProcess(OrderTrading orderTrading) { LogUtils.tradingOrderSuccess(OVERSEAS_STOCK_RESERVATION_BUY_ORDER, orderTrading.getItemNameKor()); - notificator.sendMessage(MessageContentDto.OrderResult.fromOne(OVERSEAS_STOCK_RESERVATION_BUY_ORDER, getAccountStatus(), orderTrading)); + notificator.sendStockResultMessage(ItemOrderResultDto.from(OVERSEAS_STOCK_RESERVATION_BUY_ORDER, getAccountStatus(), orderTrading)); } /** diff --git a/src/main/java/com/devspacehub/ast/domain/my/reservationOrderInfo/service/ReservationBuyOrderServiceImpl.java b/src/main/java/com/devspacehub/ast/domain/my/reservationOrderInfo/service/ReservationBuyOrderServiceImpl.java index cf93d5a..7aa5917 100644 --- a/src/main/java/com/devspacehub/ast/domain/my/reservationOrderInfo/service/ReservationBuyOrderServiceImpl.java +++ b/src/main/java/com/devspacehub/ast/domain/my/reservationOrderInfo/service/ReservationBuyOrderServiceImpl.java @@ -23,7 +23,7 @@ import com.devspacehub.ast.domain.my.service.MyService; import com.devspacehub.ast.domain.my.service.MyServiceFactory; import com.devspacehub.ast.domain.notification.Notificator; -import com.devspacehub.ast.domain.notification.dto.MessageContentDto; +import com.devspacehub.ast.domain.notification.dto.DefaultItemInfoDto.ItemOrderResultDto; import com.devspacehub.ast.domain.orderTrading.OrderTrading; import com.devspacehub.ast.domain.orderTrading.OrderTradingRepository; import com.devspacehub.ast.domain.orderTrading.dto.DomesticStockOrderExternalReqDto; @@ -216,6 +216,6 @@ public List saveOrderInfos(List orderTradingInfos) { @Override public void orderApiResultProcess(OrderTrading orderTrading) { LogUtils.tradingOrderSuccess(DOMESTIC_STOCK_RESERVATION_BUY_ORDER, orderTrading.getItemNameKor()); - notificator.sendMessage(MessageContentDto.OrderResult.fromOne(DOMESTIC_STOCK_RESERVATION_BUY_ORDER, getAccountStatus(), orderTrading)); + notificator.sendStockResultMessage(ItemOrderResultDto.from(DOMESTIC_STOCK_RESERVATION_BUY_ORDER, getAccountStatus(), orderTrading)); } } diff --git a/src/main/java/com/devspacehub/ast/domain/orderTrading/service/BuyOrderServiceImpl.java b/src/main/java/com/devspacehub/ast/domain/orderTrading/service/BuyOrderServiceImpl.java index 48fdd01..aeab398 100644 --- a/src/main/java/com/devspacehub/ast/domain/orderTrading/service/BuyOrderServiceImpl.java +++ b/src/main/java/com/devspacehub/ast/domain/orderTrading/service/BuyOrderServiceImpl.java @@ -23,7 +23,6 @@ import com.devspacehub.ast.domain.my.service.MyService; import com.devspacehub.ast.domain.my.service.MyServiceFactory; import com.devspacehub.ast.domain.notification.Notificator; -import com.devspacehub.ast.domain.notification.dto.MessageContentDto; import com.devspacehub.ast.domain.orderTrading.OrderTrading; import com.devspacehub.ast.domain.orderTrading.OrderTradingRepository; import com.devspacehub.ast.domain.orderTrading.dto.DomesticStockOrderExternalReqDto; @@ -53,6 +52,7 @@ import static com.devspacehub.ast.common.constant.ProfileType.*; import static com.devspacehub.ast.common.constant.YesNoStatus.YES; import static com.devspacehub.ast.domain.marketStatus.dto.DomStockTradingVolumeRankingExternalResDto.*; +import static com.devspacehub.ast.domain.notification.dto.DefaultItemInfoDto.*; /** * 국내 주식 주문 서비스 구현체 - 매수 @@ -295,7 +295,7 @@ public List saveOrderInfos(List orderTradingInfos) { @Override public void orderApiResultProcess(OrderTrading orderTrading) { LogUtils.tradingOrderSuccess(DOMESTIC_STOCK_BUY_ORDER, orderTrading.getItemNameKor()); - notificator.sendMessage(MessageContentDto.OrderResult.fromOne(DOMESTIC_STOCK_BUY_ORDER, getAccountStatus(), orderTrading)); + notificator.sendStockResultMessage(ItemOrderResultDto.from(DOMESTIC_STOCK_BUY_ORDER, getAccountStatus(), orderTrading)); } /** diff --git a/src/main/java/com/devspacehub/ast/domain/orderTrading/service/SellOrderServiceImpl.java b/src/main/java/com/devspacehub/ast/domain/orderTrading/service/SellOrderServiceImpl.java index dd4fb15..5cc599f 100644 --- a/src/main/java/com/devspacehub/ast/domain/orderTrading/service/SellOrderServiceImpl.java +++ b/src/main/java/com/devspacehub/ast/domain/orderTrading/service/SellOrderServiceImpl.java @@ -17,7 +17,6 @@ import com.devspacehub.ast.domain.my.stockBalance.dto.response.StockBalanceApiResDto; import com.devspacehub.ast.domain.my.service.MyService; import com.devspacehub.ast.domain.notification.Notificator; -import com.devspacehub.ast.domain.notification.dto.MessageContentDto; import com.devspacehub.ast.domain.orderTrading.OrderTrading; import com.devspacehub.ast.domain.orderTrading.OrderTradingRepository; import com.devspacehub.ast.domain.orderTrading.dto.DomesticStockOrderExternalReqDto; @@ -41,6 +40,7 @@ import static com.devspacehub.ast.common.constant.CommonConstants.*; import static com.devspacehub.ast.common.constant.OpenApiType.DOMESTIC_STOCK_SELL_ORDER; import static com.devspacehub.ast.common.constant.ProfileType.getAccountStatus; +import static com.devspacehub.ast.domain.notification.dto.DefaultItemInfoDto.*; /** * 국내 주식 주문 서비스 구현체 - 매도 @@ -185,7 +185,7 @@ public List saveOrderInfos(List orderTradingInfos) { @Override public void orderApiResultProcess(OrderTrading orderTrading) { LogUtils.tradingOrderSuccess(DOMESTIC_STOCK_SELL_ORDER, orderTrading.getItemNameKor()); - notificator.sendMessage(MessageContentDto.OrderResult.fromOne(DOMESTIC_STOCK_SELL_ORDER, getAccountStatus(), orderTrading)); + notificator.sendStockResultMessage(ItemOrderResultDto.from(DOMESTIC_STOCK_SELL_ORDER, getAccountStatus(), orderTrading)); } /** diff --git a/src/main/java/com/devspacehub/ast/domain/orderTrading/service/overseas/OverseasBuyOrderServiceImpl.java b/src/main/java/com/devspacehub/ast/domain/orderTrading/service/overseas/OverseasBuyOrderServiceImpl.java index 6d06d4b..18c2a1d 100644 --- a/src/main/java/com/devspacehub/ast/domain/orderTrading/service/overseas/OverseasBuyOrderServiceImpl.java +++ b/src/main/java/com/devspacehub/ast/domain/orderTrading/service/overseas/OverseasBuyOrderServiceImpl.java @@ -21,7 +21,6 @@ import com.devspacehub.ast.domain.marketStatus.service.OverseasMarketStatusService; import com.devspacehub.ast.domain.my.dto.MyServiceRequestDto; import com.devspacehub.ast.domain.my.service.MyService; -import com.devspacehub.ast.domain.notification.dto.MessageContentDto; import com.devspacehub.ast.domain.orderTrading.OrderTrading; import com.devspacehub.ast.domain.orderTrading.service.TradingService; import com.devspacehub.ast.exception.error.BusinessException; @@ -46,6 +45,7 @@ import static com.devspacehub.ast.common.constant.OpenApiType.OVERSEAS_STOCK_BUY_ORDER; import static com.devspacehub.ast.common.constant.ProfileType.getAccountStatus; import static com.devspacehub.ast.domain.marketStatus.dto.OverseasStockConditionSearchResDto.*; +import static com.devspacehub.ast.domain.notification.dto.DefaultItemInfoDto.*; /** * 해외 주식 주문 서비스 구현체 - 매수 @@ -218,7 +218,7 @@ public List saveOrderInfos(List orderTradingInfos) { */ public void orderApiResultProcess(OrderTrading orderTrading) { LogUtils.tradingOrderSuccess(OVERSEAS_STOCK_BUY_ORDER, orderTrading.getItemNameKor()); - notificator.sendMessage(MessageContentDto.OrderResult.fromOne(OVERSEAS_STOCK_BUY_ORDER, getAccountStatus(), orderTrading)); + notificator.sendStockResultMessage(ItemOrderResultDto.from(OVERSEAS_STOCK_BUY_ORDER, getAccountStatus(), orderTrading)); } diff --git a/src/main/java/com/devspacehub/ast/domain/orderTrading/service/overseas/OverseasSellOrderServiceImpl.java b/src/main/java/com/devspacehub/ast/domain/orderTrading/service/overseas/OverseasSellOrderServiceImpl.java index 5ed6141..057b1c2 100644 --- a/src/main/java/com/devspacehub/ast/domain/orderTrading/service/overseas/OverseasSellOrderServiceImpl.java +++ b/src/main/java/com/devspacehub/ast/domain/orderTrading/service/overseas/OverseasSellOrderServiceImpl.java @@ -17,7 +17,6 @@ import com.devspacehub.ast.domain.my.service.MyServiceFactory; import com.devspacehub.ast.domain.my.stockBalance.dto.response.overseas.OverseasStockBalanceApiResDto; import com.devspacehub.ast.domain.notification.Notificator; -import com.devspacehub.ast.domain.notification.dto.MessageContentDto; import com.devspacehub.ast.domain.orderTrading.OrderTrading; import com.devspacehub.ast.domain.orderTrading.OrderTradingRepository; import com.devspacehub.ast.domain.orderTrading.dto.OverseasStockOrderApiReqDto; @@ -42,6 +41,7 @@ import static com.devspacehub.ast.common.constant.CommonConstants.*; import static com.devspacehub.ast.common.constant.OpenApiType.OVERSEAS_STOCK_SELL_ORDER; import static com.devspacehub.ast.common.constant.ProfileType.getAccountStatus; +import static com.devspacehub.ast.domain.notification.dto.DefaultItemInfoDto.*; /** * 해외 주식 주문 서비스 구현체 - 매도 @@ -190,7 +190,7 @@ public List saveOrderInfos(List orderTradingInfos) { @Override public void orderApiResultProcess(OrderTrading orderTrading) { LogUtils.tradingOrderSuccess(OVERSEAS_STOCK_SELL_ORDER, orderTrading.getItemNameKor()); - notificator.sendMessage(MessageContentDto.OrderResult.fromOne(OVERSEAS_STOCK_SELL_ORDER, getAccountStatus(), orderTrading)); + notificator.sendStockResultMessage(ItemOrderResultDto.from(OVERSEAS_STOCK_SELL_ORDER, getAccountStatus(), orderTrading)); } private MyService myServiceImpl() { diff --git a/src/test/java/com/devspacehub/ast/domain/my/reservationOrderInfo/service/OverseasReservationBuyOrderServiceTest.java b/src/test/java/com/devspacehub/ast/domain/my/reservationOrderInfo/service/OverseasReservationBuyOrderServiceTest.java index 5ecc5ba..3e709d6 100644 --- a/src/test/java/com/devspacehub/ast/domain/my/reservationOrderInfo/service/OverseasReservationBuyOrderServiceTest.java +++ b/src/test/java/com/devspacehub/ast/domain/my/reservationOrderInfo/service/OverseasReservationBuyOrderServiceTest.java @@ -16,7 +16,7 @@ import com.devspacehub.ast.domain.my.reservationOrderInfo.ReservationOrderInfoRepository; import com.devspacehub.ast.domain.my.reservationOrderInfo.dto.ReservationStockItem; import com.devspacehub.ast.domain.notification.Notificator; -import com.devspacehub.ast.domain.notification.dto.MessageContentDto; +import com.devspacehub.ast.domain.notification.dto.DefaultItemInfoDto; import com.devspacehub.ast.domain.orderTrading.OrderTrading; import com.devspacehub.ast.domain.orderTrading.OrderTradingRepository; import com.devspacehub.ast.domain.orderTrading.dto.OverseasStockOrderApiReqDto; @@ -197,11 +197,11 @@ void throw_InsufficientMoneyException_when_myDeposit_is_lessThan_totalOrderPrice void call_notificator_when_orderResult_is_success() { OrderTrading orderTrading = OrderTrading.builder().itemCode("AAPL").itemNameKor("애플").orderQuantity(1).orderPrice(new BigDecimal("29.000")) .orderNumber("193284932").orderTime("090018").build(); - willDoNothing().given(notificator).sendMessage(any(MessageContentDto.OrderResult.class)); + willDoNothing().given(notificator).sendStockResultMessage(any(DefaultItemInfoDto.ItemOrderResultDto.class)); overseasReservationBuyOrderService.orderApiResultProcess(orderTrading); - verify(notificator).sendMessage(any(MessageContentDto.OrderResult.class)); + verify(notificator).sendStockResultMessage(any(DefaultItemInfoDto.ItemOrderResultDto.class)); } @DisplayName("주문가가 0원일 때 InvalidValueException이 발생한다.") diff --git a/src/test/java/com/devspacehub/ast/domain/my/reservationOrderInfo/service/ReservationBuyOrderServiceIntegrationTest.java b/src/test/java/com/devspacehub/ast/domain/my/reservationOrderInfo/service/ReservationBuyOrderServiceIntegrationTest.java index 8faa651..625c981 100644 --- a/src/test/java/com/devspacehub/ast/domain/my/reservationOrderInfo/service/ReservationBuyOrderServiceIntegrationTest.java +++ b/src/test/java/com/devspacehub/ast/domain/my/reservationOrderInfo/service/ReservationBuyOrderServiceIntegrationTest.java @@ -21,12 +21,12 @@ import com.devspacehub.ast.domain.my.service.MyServiceFactory; import com.devspacehub.ast.domain.my.service.MyServiceImpl; import com.devspacehub.ast.domain.notification.Notificator; -import com.devspacehub.ast.domain.notification.dto.MessageContentDto; import com.devspacehub.ast.domain.orderTrading.OrderTrading; import com.devspacehub.ast.domain.orderTrading.OrderTradingRepository; import com.devspacehub.ast.domain.orderTrading.dto.DomesticStockOrderExternalReqDto; import com.devspacehub.ast.domain.orderTrading.dto.StockOrderApiResDto; import com.devspacehub.ast.domain.orderTrading.service.CurrentStockPriceInfoBuilder; +import com.devspacehub.ast.kafka.dto.MessageDto; import com.devspacehub.ast.util.OpenApiRequest; import jakarta.persistence.EntityNotFoundException; import org.junit.jupiter.api.DisplayName; @@ -118,7 +118,7 @@ void order() { given(myService.getBuyOrderPossibleCash(any(MyServiceRequestDto.Domestic.class))).willReturn(BigDecimal.valueOf(10000)); given(marketStatusService.getCurrentStockPrice(givenEntity.getItemCode())).willReturn(currentStockPriceResponseOutput); - doNothing().when(notificator).sendMessage(any(MessageContentDto.class)); + doNothing().when(notificator).sendStockResultMessage(any(MessageDto.class)); // when reservationBuyOrderService.order(openApiProperties, openApiType); From f1594e00a275425453fc795c93841a7ad380f978 Mon Sep 17 00:00:00 2001 From: BananMoon Date: Wed, 25 Sep 2024 22:33:20 +0900 Subject: [PATCH 3/5] =?UTF-8?q?feat:=20=EB=94=94=EC=8A=A4=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=95=8C=EB=A6=BC=20=EB=A9=94=EC=8B=9C=EC=A7=80=20?= =?UTF-8?q?=EB=B0=9C=EC=86=A1=20=EA=B8=B0=EB=8A=A5=EC=9D=84=20Kafka=20Mess?= =?UTF-8?q?ageProducer=EB=A1=9C=20=EA=B0=9C=EB=B0=9C=20-=20spring-kafka=20?= =?UTF-8?q?=EB=9D=BC=EC=9D=B4=EB=B8=8C=EB=9F=AC=EB=A6=AC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20-=20Config=20=EC=A0=95=EB=B3=B4,=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EC=B6=94=EA=B0=80=20-=20=EA=B3=B5?= =?UTF-8?q?=ED=86=B5=20=EB=A9=94=EC=84=B8=EC=A7=80,=20=EA=B5=AC=EC=B2=B4?= =?UTF-8?q?=EC=A0=81=EC=9D=B8=20=EB=A9=94=EC=84=B8=EC=A7=80=20Dto=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20-=20Notificator:=20MessageProducer=20?= =?UTF-8?q?=ED=98=B8=EC=B6=9C=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=8C=80?= =?UTF-8?q?=EC=B2=B4=20-=20MessageProducer:=20=EB=A9=94=EC=84=B8=EC=A7=80?= =?UTF-8?q?=20=EB=B0=9C=ED=96=89=20=ED=94=84=EB=A1=9C=EB=93=80=EC=84=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 4 + .../ast/domain/notification/Notificator.java | 78 ++++--------------- .../notification/dto/DefaultItemInfoDto.java | 65 ++++++++++++++++ .../dto/ItemConclusionResultDto.java | 51 ++++++++++++ .../kafka/config/KafkaProducerConfig.java | 58 ++++++++++++++ .../infra/kafka/config/KafkaTopicType.java | 34 ++++++++ .../ast/infra/kafka/dto/MessageDto.java | 23 ++++++ .../infra/kafka/producer/MessageProducer.java | 54 +++++++++++++ .../domain/notification/NotificatorTest.java | 55 +++++++++++++ .../MessageProducerIntegrationTest.java | 45 +++++++++++ 10 files changed, 406 insertions(+), 61 deletions(-) create mode 100644 src/main/java/com/devspacehub/ast/domain/notification/dto/DefaultItemInfoDto.java create mode 100644 src/main/java/com/devspacehub/ast/domain/notification/dto/ItemConclusionResultDto.java create mode 100644 src/main/java/com/devspacehub/ast/infra/kafka/config/KafkaProducerConfig.java create mode 100644 src/main/java/com/devspacehub/ast/infra/kafka/config/KafkaTopicType.java create mode 100644 src/main/java/com/devspacehub/ast/infra/kafka/dto/MessageDto.java create mode 100644 src/main/java/com/devspacehub/ast/infra/kafka/producer/MessageProducer.java create mode 100644 src/test/java/com/devspacehub/ast/domain/notification/NotificatorTest.java create mode 100644 src/test/java/com/devspacehub/ast/kafka/producer/MessageProducerIntegrationTest.java diff --git a/build.gradle b/build.gradle index 414423c..16340e9 100644 --- a/build.gradle +++ b/build.gradle @@ -97,6 +97,10 @@ dependencies { annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta" annotationProcessor 'jakarta.annotation:jakarta.annotation-api' annotationProcessor 'jakarta.persistence:jakarta.persistence-api' + + // kafka + implementation 'org.springframework.kafka:spring-kafka:3.1.0' + } tasks.named('test') { diff --git a/src/main/java/com/devspacehub/ast/domain/notification/Notificator.java b/src/main/java/com/devspacehub/ast/domain/notification/Notificator.java index a04449c..dac310f 100644 --- a/src/main/java/com/devspacehub/ast/domain/notification/Notificator.java +++ b/src/main/java/com/devspacehub/ast/domain/notification/Notificator.java @@ -7,80 +7,36 @@ */ package com.devspacehub.ast.domain.notification; -import com.devspacehub.ast.common.constant.OpenApiType; -import com.devspacehub.ast.common.constant.ResultCode; -import com.devspacehub.ast.domain.notification.dto.DiscordWebhookNotifyRequestDto; -import com.devspacehub.ast.domain.notification.dto.MessageContentDto; -import com.devspacehub.ast.exception.error.NotificationException; -import com.devspacehub.ast.exception.error.InvalidValueException; +import com.devspacehub.ast.common.constant.ProfileType; +import com.devspacehub.ast.infra.kafka.config.KafkaTopicType; +import com.devspacehub.ast.infra.kafka.dto.MessageDto; +import com.devspacehub.ast.infra.kafka.producer.MessageProducer; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; -import org.springframework.web.reactive.function.client.WebClient; -import org.springframework.web.reactive.function.client.WebClientResponseException; -import java.util.function.Consumer; - /** - * Discord Webhook 이용한 알림 서비스 + * 알림 클래스 */ @Slf4j @Service +@RequiredArgsConstructor public class Notificator { - @Value("${notify.discord-webhook.url}") - private String discordWebhookUrl; - private static final String DOMESTIC_ORDER_NOTI_SENDER_NAME = "국내 주문 봇"; - private static final String DOMESTIC_CONCLUSION_NOTI_SENDER_NAME = "국내 체결 봇"; - - private static final String OVERSEAS_ORDER_NOTI_SENDER_NAME = "해외 주문 봇"; - private static final String OVERSEAS_CONCLUSION_NOTI_SENDER_NAME = "해외 체결 봇"; + private final MessageProducer messageProducer; /** - * 단일 메시지 발송 요청 - * @param content 메세지 내용 DTO + * 주식 결과 알림 메세지 발송 요청한다. + * @param content MessageDto 상속하는 메세지 Dto 클래스 */ - public void sendMessage(T content) { - String senderName = getSenderName(content.getOpenApiType()); - String msg = content.createMessage(content); - Consumer headers = DiscordWebhookNotifyRequestDto.setHeaders(); - DiscordWebhookNotifyRequestDto requestBody = DiscordWebhookNotifyRequestDto.builder() - .senderName(senderName) - .message(msg) - .build(); - - try { - WebClient.builder() - .baseUrl(discordWebhookUrl) - .build() - .post() - .headers(headers) - .bodyValue(requestBody) - .retrieve() - .toBodilessEntity() - .block(); - - } catch (Exception ex) { - if (ex instanceof WebClientResponseException webClientResponseException && HttpStatus.NO_CONTENT.equals(webClientResponseException.getStatusCode())) { - return; - } - throw new NotificationException(ex.getMessage()); - } + public void sendStockResultMessage(T content) { + messageProducer.produce(this.createTopic(KafkaTopicType.STOCK_RESULT_NOTI_TOPIC), content); } + /** - * 알림 봇 이름 지정 - * @param openApiType OpenApiType - * @return 알림 봇 이름 + * 환경 별 토픽을 생성한다. + * @return 토픽 */ - private String getSenderName(OpenApiType openApiType) { - return switch (openApiType) { - case DOMESTIC_STOCK_BUY_ORDER, DOMESTIC_STOCK_SELL_ORDER, DOMESTIC_STOCK_RESERVATION_BUY_ORDER -> DOMESTIC_ORDER_NOTI_SENDER_NAME; - case DOMESTIC_ORDER_CONCLUSION_FIND -> DOMESTIC_CONCLUSION_NOTI_SENDER_NAME; - case OVERSEAS_STOCK_BUY_ORDER, OVERSEAS_STOCK_SELL_ORDER, OVERSEAS_STOCK_RESERVATION_BUY_ORDER -> OVERSEAS_ORDER_NOTI_SENDER_NAME; - case OVERSEAS_ORDER_CONCLUSION_FIND -> OVERSEAS_CONCLUSION_NOTI_SENDER_NAME; - default -> throw new InvalidValueException(ResultCode.INVALID_OPENAPI_TYPE_ERROR); - }; + private String createTopic(KafkaTopicType topicType) { + return topicType.getValue(ProfileType.getActiveProfileType()); } - } diff --git a/src/main/java/com/devspacehub/ast/domain/notification/dto/DefaultItemInfoDto.java b/src/main/java/com/devspacehub/ast/domain/notification/dto/DefaultItemInfoDto.java new file mode 100644 index 0000000..b97ea1d --- /dev/null +++ b/src/main/java/com/devspacehub/ast/domain/notification/dto/DefaultItemInfoDto.java @@ -0,0 +1,65 @@ +/* + © 2024 devspacehub, Inc. All rights reserved. + + name : DefaultItemInfoDto + creation : 2024.9.18 + author : Yoonji Moon + */ + +package com.devspacehub.ast.domain.notification.dto; + +import com.devspacehub.ast.common.constant.OpenApiType; +import com.devspacehub.ast.domain.orderTrading.OrderTrading; +import com.devspacehub.ast.infra.kafka.dto.MessageDto; +import lombok.Getter; +import lombok.ToString; +import lombok.experimental.SuperBuilder; + +import java.math.BigDecimal; + +/** + * 메세지 발송 시 생성되는 주식 관련 정보 Dto + */ +@Getter +@SuperBuilder +public abstract class DefaultItemInfoDto extends MessageDto { + private String accountStatusKor; + private String itemNameKor; + private String itemCode; + private OpenApiType openApiType; + private int orderQuantity; + private BigDecimal orderPrice; + private String orderNumber; + private String orderTime; + + /** + * 주문 결과 Dto. + * DefaultItemInfoDto를 상속한다. + */ + @ToString + @SuperBuilder + public static class ItemOrderResultDto extends DefaultItemInfoDto { + + /** + * 전달받는 인자로 주문 결과 Dto 인스턴스를 반환한다. + * @param openApiType OpenApi Type + * @param accountStatus 계좌 상태 + * @param orderTrading 주문 결과 Dto + * @return ItemOrderResultDto + */ + public static ItemOrderResultDto from(OpenApiType openApiType, String accountStatus, OrderTrading orderTrading) { + return ItemOrderResultDto.builder() + .title("주문 완료") + .accountStatusKor(accountStatus) + .itemNameKor(orderTrading.getItemNameKor()) + .itemCode(orderTrading.getItemCode()) + .openApiType(openApiType) + .orderQuantity(orderTrading.getOrderQuantity()) + .orderPrice(orderTrading.getOrderPrice()) + .orderNumber(orderTrading.getOrderNumber()) + .orderTime(orderTrading.getOrderTime()) + .build(); + } + } + +} diff --git a/src/main/java/com/devspacehub/ast/domain/notification/dto/ItemConclusionResultDto.java b/src/main/java/com/devspacehub/ast/domain/notification/dto/ItemConclusionResultDto.java new file mode 100644 index 0000000..63a672b --- /dev/null +++ b/src/main/java/com/devspacehub/ast/domain/notification/dto/ItemConclusionResultDto.java @@ -0,0 +1,51 @@ +/* + © 2024 devspacehub, Inc. All rights reserved. + + name : ItemConclusionResultDto + creation : 2024.9.18 + author : Yoonji Moon + */ + +package com.devspacehub.ast.domain.notification.dto; + +import com.devspacehub.ast.common.constant.OpenApiType; +import com.devspacehub.ast.domain.my.dto.orderConclusion.OrderConclusionDto; +import lombok.ToString; +import lombok.experimental.SuperBuilder; + +import java.math.BigDecimal; + +/** + * 주식 체결 결과 Dto + * DefaultItemInfoDto 상속 + */ +@ToString +@SuperBuilder +public class ItemConclusionResultDto extends DefaultItemInfoDto { + private int concludedQuantity; + private BigDecimal concludedPrice; + + + /** + * 전달받는 인자로 주식 체결 결과 Dto 인스턴스 생성하여 반환한다. + * @param orderApiType OpenApi Type + * @param accountStatus 계좌 상태 + * @param orderConclusion 주문 결과 정보 + * @return ItemConclusionResultDto + */ + public static ItemConclusionResultDto from(OpenApiType orderApiType, String accountStatus, OrderConclusionDto orderConclusion) { + return ItemConclusionResultDto.builder() + .title("체결 완료") + .accountStatusKor(accountStatus) + .itemNameKor(orderConclusion.getItemNameKor()) + .itemCode(orderConclusion.getItemCode()) + .openApiType(orderApiType) + .orderQuantity(orderConclusion.getOrderQuantity()) + .orderPrice(orderConclusion.getOrderPrice()) + .orderNumber(orderConclusion.getOrderNumber()) + .concludedQuantity(orderConclusion.getConcludedQuantity()) + .concludedPrice(orderConclusion.getConcludedPrice()) + .orderTime(orderConclusion.getOrderTime()) + .build(); + } +} diff --git a/src/main/java/com/devspacehub/ast/infra/kafka/config/KafkaProducerConfig.java b/src/main/java/com/devspacehub/ast/infra/kafka/config/KafkaProducerConfig.java new file mode 100644 index 0000000..5a94fe5 --- /dev/null +++ b/src/main/java/com/devspacehub/ast/infra/kafka/config/KafkaProducerConfig.java @@ -0,0 +1,58 @@ +/* + © 2024 devspacehub, Inc. All rights reserved. + + name : KafkaProducerConfig + creation : 2024.9.15 + author : Yoonji Moon + */ + +package com.devspacehub.ast.infra.kafka.config; + +import com.devspacehub.ast.infra.kafka.dto.MessageDto; +import org.apache.kafka.clients.producer.ProducerConfig; +import org.apache.kafka.common.serialization.StringSerializer; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.kafka.core.DefaultKafkaProducerFactory; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.kafka.core.ProducerFactory; +import org.springframework.kafka.support.serializer.JsonSerializer; + +import java.util.HashMap; +import java.util.Map; + +/** + * Kafka Producer Configuration + */ +@Configuration +public class KafkaProducerConfig { + + @Value("${kafka.producer.bootstrap-server.host}") + private String bootstrapServerHost; + @Value("${kafka.producer.bootstrap-server.port}") + private int bootstrapServerPort; + + /** + * Kafka Producer 설정 프로퍼티 세팅하여 반환한다. + * @return ProducerFactory + */ + @Bean + public ProducerFactory kafkaProducerFactory() { + Map props = new HashMap<>(); + props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); + props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class); + props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, String.format("%s:%d", bootstrapServerHost, bootstrapServerPort)); + + return new DefaultKafkaProducerFactory<>(props); + } + + /** + * Kafka Producer Bean + * @return KafkaTemplate + */ + @Bean + public KafkaTemplate kafkaTemplate(){ + return new KafkaTemplate<>(kafkaProducerFactory()); + } +} diff --git a/src/main/java/com/devspacehub/ast/infra/kafka/config/KafkaTopicType.java b/src/main/java/com/devspacehub/ast/infra/kafka/config/KafkaTopicType.java new file mode 100644 index 0000000..b285edb --- /dev/null +++ b/src/main/java/com/devspacehub/ast/infra/kafka/config/KafkaTopicType.java @@ -0,0 +1,34 @@ +/* + © 2024 devspacehub, Inc. All rights reserved. + + name : KafkaTopicType + creation : 2024.9.18 + author : Yoonji Moon + */ + +package com.devspacehub.ast.infra.kafka.config; + +import com.devspacehub.ast.common.constant.ProfileType; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * Kafka Topic Type Enum + */ +@Getter +@RequiredArgsConstructor +public enum KafkaTopicType { + STOCK_RESULT_NOTI_TOPIC("Stock-Result-Notification", "Stock-Result-Notification_TEST"); + + private final String prodValue; + private final String testValue; + + /** + * 환경 별 토픽 값 반환한다. + * @param profileType 동작 중인 프로파일 타입 + * @return kafka 토픽 + */ + public String getValue(ProfileType profileType) { + return ProfileType.PROD.equals(profileType) ? this.prodValue : this.testValue; + } +} diff --git a/src/main/java/com/devspacehub/ast/infra/kafka/dto/MessageDto.java b/src/main/java/com/devspacehub/ast/infra/kafka/dto/MessageDto.java new file mode 100644 index 0000000..e604941 --- /dev/null +++ b/src/main/java/com/devspacehub/ast/infra/kafka/dto/MessageDto.java @@ -0,0 +1,23 @@ +/* + © 2024 devspacehub, Inc. All rights reserved. + + name : MessageDto + creation : 2024.8.30 + author : Yoonji Moon + */ + +package com.devspacehub.ast.infra.kafka.dto; + +import lombok.Getter; +import lombok.ToString; +import lombok.experimental.SuperBuilder; + +/** + * Kafka Producer를 통해 발행하는 공통 Dto 클래스 + */ +@Getter +@ToString +@SuperBuilder +public abstract class MessageDto { + protected String title; +} diff --git a/src/main/java/com/devspacehub/ast/infra/kafka/producer/MessageProducer.java b/src/main/java/com/devspacehub/ast/infra/kafka/producer/MessageProducer.java new file mode 100644 index 0000000..3549176 --- /dev/null +++ b/src/main/java/com/devspacehub/ast/infra/kafka/producer/MessageProducer.java @@ -0,0 +1,54 @@ +/* + © 2024 devspacehub, Inc. All rights reserved. + + name : MessageProducer + creation : 2024.8.30 + author : Yoonji Moon + */ + +package com.devspacehub.ast.infra.kafka.producer; + +import com.devspacehub.ast.infra.kafka.dto.MessageDto; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.kafka.clients.producer.ProducerRecord; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.stereotype.Component; + +/** + * 주식 결과 (주문 결과, 체결 결과 등) 메시지 발행하는 Producer + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class MessageProducer { + private final KafkaTemplate template; + + /** + * 메세지를 발행한다. + * @param topic 데이터의 토픽 + * @param message MessageDto 상속하는 메세지 Dto 클래스 + */ + public void produce(String topic, T message) { + try { + template.send(this.createRecord(topic, message)); + } catch (Exception e) { + log.error(String.format("cause: %s, result: failed to produce '%s' message.", e.getMessage(), message.getTitle()), e); + } finally { + template.flush(); + } + + } + + /** + * 메세지 발행 위해 ProducerRecord 생성한다. + * @param topic 데이터의 토픽 + * @param message MessageDto 상속하는 메세지 Dto 클래스 + * @return ProducerRecord + */ + private ProducerRecord createRecord(String topic, T message) { + log.info("produce message={}, topic={}", message, topic); + + return new ProducerRecord<>(topic, message); + } +} \ No newline at end of file diff --git a/src/test/java/com/devspacehub/ast/domain/notification/NotificatorTest.java b/src/test/java/com/devspacehub/ast/domain/notification/NotificatorTest.java new file mode 100644 index 0000000..97cfad2 --- /dev/null +++ b/src/test/java/com/devspacehub/ast/domain/notification/NotificatorTest.java @@ -0,0 +1,55 @@ +/* + © 2024 devspacehub, Inc. All rights reserved. + + name : NotificatorTest + creation : 2024.9.22 + author : Yoonji Moon + */ + +package com.devspacehub.ast.domain.notification; + +import com.devspacehub.ast.common.constant.OpenApiType; +import com.devspacehub.ast.domain.notification.dto.ItemConclusionResultDto; +import com.devspacehub.ast.infra.kafka.producer.MessageProducer; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.math.BigDecimal; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.BDDMockito.willDoNothing; +import static org.mockito.Mockito.only; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +class NotificatorTest { + @InjectMocks + Notificator notificator; + @Mock + MessageProducer messageProducer; + + @DisplayName("환경에 따른 토픽명을 지정하여 메세지 발송을 요청한다.") + @Test + void sendStockResultMessage() { + System.setProperty("spring.profiles.active", "test"); + willDoNothing().given(messageProducer).produce(anyString(), any(ItemConclusionResultDto.class)); + ItemConclusionResultDto given = ItemConclusionResultDto.builder() + .openApiType(OpenApiType.OVERSEAS_ORDER_CONCLUSION_FIND) + .itemCode("AAPL") + .orderTime("105010") + .orderPrice(BigDecimal.valueOf(228.20)) + .orderQuantity(2) + .concludedPrice(BigDecimal.valueOf(228.20)) + .concludedQuantity(2) + .build(); + + notificator.sendStockResultMessage(given); + + verify(messageProducer, only()).produce("Stock Result Notification_TEST", given); + } +} \ No newline at end of file diff --git a/src/test/java/com/devspacehub/ast/kafka/producer/MessageProducerIntegrationTest.java b/src/test/java/com/devspacehub/ast/kafka/producer/MessageProducerIntegrationTest.java new file mode 100644 index 0000000..add6f8e --- /dev/null +++ b/src/test/java/com/devspacehub/ast/kafka/producer/MessageProducerIntegrationTest.java @@ -0,0 +1,45 @@ +/* + © 2024 devspacehub, Inc. All rights reserved. + + name : MessageProducerTest + creation : 2024.9.20 + author : Yoonji Moon + */ + +package com.devspacehub.ast.kafka.producer; + +import com.devspacehub.ast.common.constant.OpenApiType; +import com.devspacehub.ast.domain.notification.dto.DefaultItemInfoDto; +import com.devspacehub.ast.infra.kafka.dto.MessageDto; +import com.devspacehub.ast.infra.kafka.producer.MessageProducer; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import java.math.BigDecimal; + +@SpringBootTest +@ActiveProfiles("local") +class MessageProducerIntegrationTest { + @Autowired + MessageProducer messageProducer; + + // TODO 발행된 데이터를 확인하는 방법 있을지 확인 필요 + @DisplayName("주문 결과 데이터를 발행한다.") + @Test + void produce() { + MessageDto given = DefaultItemInfoDto.ItemOrderResultDto.ItemOrderResultDto.builder() + .title("주문 결과 테스트") + .openApiType(OpenApiType.DOMESTIC_STOCK_BUY_ORDER) + .itemCode("005930") + .orderTime("090100") + .orderPrice(BigDecimal.valueOf(63000)) + .orderQuantity(2) + .build(); + + messageProducer.produce("Stock Result Notification_TEST", given); + } + +} \ No newline at end of file From 7d060210042afd11c76a0008cf3b92dfc0361e1e Mon Sep 17 00:00:00 2001 From: BananMoon Date: Wed, 25 Sep 2024 22:41:49 +0900 Subject: [PATCH 4/5] =?UTF-8?q?refactor&test:=20=ED=8C=A8=ED=82=A4?= =?UTF-8?q?=EC=A7=80=20=EC=9D=B4=EB=8F=99,=20JavaDoc=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=20chore:=20application.yml=20=EC=A0=95=EB=A6=AC=20=EB=B0=8F=20?= =?UTF-8?q?=EC=B9=B4=ED=94=84=EC=B9=B4=20=EC=A0=95=EB=B3=B4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ast/common/constant/ProfileType.java | 15 ++++++++-- .../infra/kafka/config/KafkaTopicType.java | 2 -- src/main/resources/application.yml | 30 +++++++------------ .../ast/common/constant/ProfileTypeTest.java | 7 ++--- ...rvationBuyOrderServiceIntegrationTest.java | 2 +- .../kafka/config/KafkaTopicTypeTest.java | 26 ++++++++++++++++ 6 files changed, 53 insertions(+), 29 deletions(-) create mode 100644 src/test/java/com/devspacehub/ast/infra/kafka/config/KafkaTopicTypeTest.java diff --git a/src/main/java/com/devspacehub/ast/common/constant/ProfileType.java b/src/main/java/com/devspacehub/ast/common/constant/ProfileType.java index 1301005..281c688 100644 --- a/src/main/java/com/devspacehub/ast/common/constant/ProfileType.java +++ b/src/main/java/com/devspacehub/ast/common/constant/ProfileType.java @@ -24,11 +24,22 @@ public enum ProfileType { private final String code; private final String accountStatus; - + /** + * 실행 환경 별 계좌 상태를 반환한다. + * @return 계좌 상태 + */ public static String getAccountStatus() { return PROD.code.equals(getActiveProfile()) ? PROD.accountStatus : TEST.accountStatus; } + /** + * 실행 환경 별 코드를 반환한다. + * @return 코드 + */ + public static ProfileType getActiveProfileType() { + return PROD.code.equals(getActiveProfile()) ? PROD : TEST; + } + /** * 동작 중인 애플리케이션이 'Prod'이면 True를 반환한다. 반대는 False. * @return 'Prod' 여부 Boolean 값. @@ -44,7 +55,7 @@ public static Boolean isProdActive() { * 현재 동작 중인 애플리케이션의 Profile을 반환한다. * @return 애플리케이션의 profile */ - static String getActiveProfile() { + private static String getActiveProfile() { return System.getProperty("spring.profiles.active"); } } diff --git a/src/main/java/com/devspacehub/ast/infra/kafka/config/KafkaTopicType.java b/src/main/java/com/devspacehub/ast/infra/kafka/config/KafkaTopicType.java index b285edb..694fce0 100644 --- a/src/main/java/com/devspacehub/ast/infra/kafka/config/KafkaTopicType.java +++ b/src/main/java/com/devspacehub/ast/infra/kafka/config/KafkaTopicType.java @@ -9,13 +9,11 @@ package com.devspacehub.ast.infra.kafka.config; import com.devspacehub.ast.common.constant.ProfileType; -import lombok.Getter; import lombok.RequiredArgsConstructor; /** * Kafka Topic Type Enum */ -@Getter @RequiredArgsConstructor public enum KafkaTopicType { STOCK_RESULT_NOTI_TOPIC("Stock-Result-Notification", "Stock-Result-Notification_TEST"); diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index da8e901..9672c83 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -11,6 +11,9 @@ spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver + url: ${DB_URL} + username: ${DB_USERNAME} + password: ${DB_PASSWORD} server: port: 8081 @@ -22,17 +25,18 @@ notify: discord-webhook: url: https://discord.com/api/webhooks/1200783808279101572/58SyrsR6ZnWmbPCBDll6ONWAqY7AMZIV15V5L3g2Grb_KQtNkkJrZSaTuNqOBWLlUv3d +kafka: + producer: + bootstrap-server: + host: localhost + port: 9092 + --- spring: config: activate: on-profile: local - datasource: - url: ${DB_URL} - username: ${DB_USERNAME} - password: ${DB_PASSWORD} - properties: hibernate: format_sql: true @@ -59,11 +63,6 @@ spring: activate: on-profile: beta - datasource: - url: ${DB_URL} - username: ${DB_USERNAME} - password: ${DB_PASSWORD} - properties: hibernate: format_sql: true @@ -85,11 +84,6 @@ spring: activate: on-profile: prod - datasource: - url: ${DB_URL} - username: ${DB_USERNAME} - password: ${DB_PASSWORD} - properties: hibernate: show_sql: false @@ -105,8 +99,4 @@ server: logging: level: org.hibernate: - SQL: error - -notify: - discord-webhook: - url: https://discord.com/api/webhooks/1207310185216081960/kMoNygVLXY-0Gth4gz1LP8Nobkt9aa50z0CENUCHKw9jDEFNZmJXev8VVYwUzibfbRmQ \ No newline at end of file + SQL: error \ No newline at end of file diff --git a/src/test/java/com/devspacehub/ast/common/constant/ProfileTypeTest.java b/src/test/java/com/devspacehub/ast/common/constant/ProfileTypeTest.java index 3db88e4..c039494 100644 --- a/src/test/java/com/devspacehub/ast/common/constant/ProfileTypeTest.java +++ b/src/test/java/com/devspacehub/ast/common/constant/ProfileTypeTest.java @@ -12,18 +12,17 @@ import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.*; class ProfileTypeTest { @DisplayName("시스템 프로퍼티로 설정한 값을 가져와서 어떤 환경인지 확인한다.") @Test void getActiveProfile() { //given - System.setProperty("spring.profiles.active", "local"); + System.setProperty("spring.profiles.active", "test"); // when - String activeProfile = ProfileType.getActiveProfile(); + ProfileType result = ProfileType.getActiveProfileType(); // then - assertThat(activeProfile).isEqualTo("local"); + assertThat(result).isEqualTo(ProfileType.TEST); } diff --git a/src/test/java/com/devspacehub/ast/domain/my/reservationOrderInfo/service/ReservationBuyOrderServiceIntegrationTest.java b/src/test/java/com/devspacehub/ast/domain/my/reservationOrderInfo/service/ReservationBuyOrderServiceIntegrationTest.java index 625c981..fd2c124 100644 --- a/src/test/java/com/devspacehub/ast/domain/my/reservationOrderInfo/service/ReservationBuyOrderServiceIntegrationTest.java +++ b/src/test/java/com/devspacehub/ast/domain/my/reservationOrderInfo/service/ReservationBuyOrderServiceIntegrationTest.java @@ -26,7 +26,7 @@ import com.devspacehub.ast.domain.orderTrading.dto.DomesticStockOrderExternalReqDto; import com.devspacehub.ast.domain.orderTrading.dto.StockOrderApiResDto; import com.devspacehub.ast.domain.orderTrading.service.CurrentStockPriceInfoBuilder; -import com.devspacehub.ast.kafka.dto.MessageDto; +import com.devspacehub.ast.infra.kafka.dto.MessageDto; import com.devspacehub.ast.util.OpenApiRequest; import jakarta.persistence.EntityNotFoundException; import org.junit.jupiter.api.DisplayName; diff --git a/src/test/java/com/devspacehub/ast/infra/kafka/config/KafkaTopicTypeTest.java b/src/test/java/com/devspacehub/ast/infra/kafka/config/KafkaTopicTypeTest.java new file mode 100644 index 0000000..04b6d30 --- /dev/null +++ b/src/test/java/com/devspacehub/ast/infra/kafka/config/KafkaTopicTypeTest.java @@ -0,0 +1,26 @@ +/* + © 2024 devspacehub, Inc. All rights reserved. + + name : KafkaTopicTypeTest + creation : 2024.9.25 + author : Yoonji Moon + */ + +package com.devspacehub.ast.infra.kafka.config; + +import com.devspacehub.ast.common.constant.ProfileType; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class KafkaTopicTypeTest { + + @DisplayName("환경 ProfileType에 따라 적절한 topic 이름을 반환한다.") + @Test + void return_topicName_by_profileType() { + String result = KafkaTopicType.STOCK_RESULT_NOTI_TOPIC.getValue(ProfileType.TEST); + + assertThat(result).isEqualTo("Stock-Result-Notification_TEST"); + } +} \ No newline at end of file From cd44fbe417db1ccef78a5cd6354eb4f93708fb80 Mon Sep 17 00:00:00 2001 From: BananMoon Date: Wed, 2 Oct 2024 23:12:20 +0900 Subject: [PATCH 5/5] =?UTF-8?q?test:=20=EB=8F=99=EC=9D=BC=ED=95=9C=20?= =?UTF-8?q?=EB=A7=A4=EA=B0=9C=EB=B3=80=EC=88=98=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95,=20=ED=8C=A8?= =?UTF-8?q?=ED=82=A4=EC=A7=80=20=EC=9D=B4=EB=8F=99,=20Copyright=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ast/domain/notification/NotificatorTest.java | 8 ++------ .../kafka/producer/MessageProducerIntegrationTest.java | 5 ++--- 2 files changed, 4 insertions(+), 9 deletions(-) rename src/test/java/com/devspacehub/ast/{ => infra}/kafka/producer/MessageProducerIntegrationTest.java (90%) diff --git a/src/test/java/com/devspacehub/ast/domain/notification/NotificatorTest.java b/src/test/java/com/devspacehub/ast/domain/notification/NotificatorTest.java index 97cfad2..52e3225 100644 --- a/src/test/java/com/devspacehub/ast/domain/notification/NotificatorTest.java +++ b/src/test/java/com/devspacehub/ast/domain/notification/NotificatorTest.java @@ -20,9 +20,6 @@ import java.math.BigDecimal; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.BDDMockito.willDoNothing; import static org.mockito.Mockito.only; import static org.mockito.Mockito.verify; @@ -33,11 +30,10 @@ class NotificatorTest { @Mock MessageProducer messageProducer; - @DisplayName("환경에 따른 토픽명을 지정하여 메세지 발송을 요청한다.") + @DisplayName("토픽명과 메세지 데이터를 전달하여 메세지 발송을 요청한다.") @Test void sendStockResultMessage() { System.setProperty("spring.profiles.active", "test"); - willDoNothing().given(messageProducer).produce(anyString(), any(ItemConclusionResultDto.class)); ItemConclusionResultDto given = ItemConclusionResultDto.builder() .openApiType(OpenApiType.OVERSEAS_ORDER_CONCLUSION_FIND) .itemCode("AAPL") @@ -50,6 +46,6 @@ void sendStockResultMessage() { notificator.sendStockResultMessage(given); - verify(messageProducer, only()).produce("Stock Result Notification_TEST", given); + verify(messageProducer, only()).produce("Stock-Result-Notification_TEST", given); } } \ No newline at end of file diff --git a/src/test/java/com/devspacehub/ast/kafka/producer/MessageProducerIntegrationTest.java b/src/test/java/com/devspacehub/ast/infra/kafka/producer/MessageProducerIntegrationTest.java similarity index 90% rename from src/test/java/com/devspacehub/ast/kafka/producer/MessageProducerIntegrationTest.java rename to src/test/java/com/devspacehub/ast/infra/kafka/producer/MessageProducerIntegrationTest.java index add6f8e..a1bd636 100644 --- a/src/test/java/com/devspacehub/ast/kafka/producer/MessageProducerIntegrationTest.java +++ b/src/test/java/com/devspacehub/ast/infra/kafka/producer/MessageProducerIntegrationTest.java @@ -1,17 +1,16 @@ /* © 2024 devspacehub, Inc. All rights reserved. - name : MessageProducerTest + name : MessageProducerIntegrationTest creation : 2024.9.20 author : Yoonji Moon */ -package com.devspacehub.ast.kafka.producer; +package com.devspacehub.ast.infra.kafka.producer; import com.devspacehub.ast.common.constant.OpenApiType; import com.devspacehub.ast.domain.notification.dto.DefaultItemInfoDto; import com.devspacehub.ast.infra.kafka.dto.MessageDto; -import com.devspacehub.ast.infra.kafka.producer.MessageProducer; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired;