From c5c48507b0218fc1026a738578ca656286ebac3b Mon Sep 17 00:00:00 2001 From: koreanMike513 Date: Tue, 28 Jan 2025 23:55:30 +0000 Subject: [PATCH 01/24] refactor: Refactored core Kafka Configs integrating DeadLetter strategy --- .../kafka/config/KafkaConsumerConfig.java | 44 ++++++++++++++----- .../kafka/config/KafkaProducerConfig.java | 2 +- core/src/main/resources/application.yml | 2 +- foods/src/main/resources/application.yml | 40 ++++++++++------- orders/src/main/resources/application.yml | 17 +++---- 5 files changed, 67 insertions(+), 38 deletions(-) diff --git a/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/config/KafkaConsumerConfig.java b/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/config/KafkaConsumerConfig.java index 89acefb..d4b3290 100644 --- a/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/config/KafkaConsumerConfig.java +++ b/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/config/KafkaConsumerConfig.java @@ -1,45 +1,67 @@ package com.f_lab.joyeuse_planete.core.kafka.config; +import com.f_lab.joyeuse_planete.core.exceptions.JoyeusePlaneteApplicationException; import com.f_lab.joyeuse_planete.core.kafka.exceptions.NonRetryableException; import com.f_lab.joyeuse_planete.core.kafka.exceptions.RetryableException; +import com.f_lab.joyeuse_planete.core.kafka.util.ExceptionUtil; +import lombok.extern.slf4j.Slf4j; +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.apache.kafka.common.TopicPartition; import org.springframework.beans.factory.annotation.Value; import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; import org.springframework.kafka.core.ConsumerFactory; import org.springframework.kafka.core.DefaultKafkaConsumerFactory; import org.springframework.kafka.listener.DeadLetterPublishingRecoverer; import org.springframework.kafka.listener.DefaultErrorHandler; -import org.springframework.util.backoff.FixedBackOff; +import org.springframework.kafka.support.ExponentialBackOffWithMaxRetries; +import org.springframework.kafka.support.KafkaHeaders; +import org.springframework.util.backoff.BackOff; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.Map; +import java.util.function.BiFunction; +@Slf4j public abstract class KafkaConsumerConfig { - @Value("${spring.kafka.bootstrap-servers}") + @Value("${spring.kafka.bootstrap-servers:localhost:9092}") protected String BOOTSTRAP_SERVERS; - @Value("${spring.kafka.consumer.enable-auto-commit}") + @Value("${spring.kafka.consumer.enable-auto-commit:false}") protected boolean AUTO_COMMIT; - @Value("${kafka.container.concurrency}") + @Value("${kafka.container.concurrency:3}") protected int CONCURRENCY; - @Value("${spring.kafka.consumer.properties.spring.json.trusted.packages}") + @Value("${spring.kafka.consumer.properties.spring.json.trusted.packages:com.f_lab.joyeuse_planete.core.*}") protected String TRUSTED_PACKAGES; - @Value("${spring.kafka.consumer.isolation-level}") + @Value("${spring.kafka.consumer.isolation-level:read_committed}") protected String ISOLATION_LEVEL; - abstract public ConcurrentKafkaListenerContainerFactory kafkaListenerContainerFactory(); - abstract protected Map consumerConfig(); + abstract protected String deadLetterTopicName(); + protected BiFunction, Exception, TopicPartition> deadLetterTopicStrategy() { + return defaultDeadLetterTopicStrategy(deadLetterTopicName()); + } + protected BiFunction, Exception, TopicPartition> defaultDeadLetterTopicStrategy(String deadLetterTopic) { + return (record, ex) -> { + ex = ExceptionUtil.unwrap(ex); + record.headers().add(KafkaHeaders.EXCEPTION_MESSAGE, ex.getMessage().getBytes(StandardCharsets.UTF_8)); + record.headers().add(KafkaHeaders.ORIGINAL_TOPIC, record.topic().getBytes(StandardCharsets.UTF_8)); + + return new TopicPartition(deadLetterTopic, -1); + }; + } protected DeadLetterPublishingRecoverer deadLetterPublishingRecoverer() { return null; } - public FixedBackOff defaultBackOffStrategy() { - return new FixedBackOff(1500L, 10); + public BackOff defaultBackOffStrategy() { + return new ExponentialBackOffWithMaxRetries(5); } public DefaultErrorHandler defaultErrorHandler() { @@ -47,7 +69,7 @@ public DefaultErrorHandler defaultErrorHandler() { deadLetterPublishingRecoverer(), defaultBackOffStrategy()); - errorHandler.addNotRetryableExceptions(NonRetryableException.class); + errorHandler.addNotRetryableExceptions(NonRetryableException.class, JoyeusePlaneteApplicationException.class); errorHandler.addRetryableExceptions(RetryableException.class); return errorHandler; diff --git a/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/config/KafkaProducerConfig.java b/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/config/KafkaProducerConfig.java index 00e6016..7ced09c 100644 --- a/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/config/KafkaProducerConfig.java +++ b/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/config/KafkaProducerConfig.java @@ -12,7 +12,7 @@ public abstract class KafkaProducerConfig { - @Value("${spring.kafka.bootstrap-servers}") + @Value("${spring.kafka.bootstrap-servers:localhost:9092}") protected String BOOTSTRAP_SERVERS; @Value("${spring.kafka.producer.ack:all}") diff --git a/core/src/main/resources/application.yml b/core/src/main/resources/application.yml index bfe5433..a9a2d14 100644 --- a/core/src/main/resources/application.yml +++ b/core/src/main/resources/application.yml @@ -1,6 +1,6 @@ spring: kafka: - bootstrap-servers: localhost:9094 + bootstrap-servers: localhost:9092 listener: ack-mode: manual producer: diff --git a/foods/src/main/resources/application.yml b/foods/src/main/resources/application.yml index c9fa72e..9a4e0de 100644 --- a/foods/src/main/resources/application.yml +++ b/foods/src/main/resources/application.yml @@ -4,19 +4,26 @@ spring: application: name: foods - datasource: - url: jdbc:h2:tcp://localhost/~/Desktop/software/h2/h2-datafile/kafka - username: sa - password: - driver-class-name: org.h2.Driver +# datasource: +# url: jdbc:h2:tcp://localhost/~/Desktop/software/h2/h2-datafile/kafka +# username: sa +# password: +# driver-class-name: org.h2.Driver jpa: - database-platform: org.hibernate.dialect.H2Dialect - hibernate: - ddl-auto: validate +# database-platform: org.hibernate.dialect.H2Dialect +# hibernate: +# ddl-auto: validate properties: hibernate: show_sql: true + kafka: + producer: + transaction-id-prefix: foods-tx + + consumer: + group-id: foods + orders: events: topic: @@ -28,10 +35,8 @@ foods: name: foods.order-created-event fail: foods.foods-reservation-fail-event -payment: - events: - topic: - name: payments.payment-process-command + dead-letter-topic: + name: foods.dead-letter-topic kafka: topic: @@ -40,9 +45,14 @@ kafka: container: concurrency: 3 +non-retryable-errors: + - "상품이 존재하지 않습니다." + - "상품의 수량이 부족합니다" + - "현재 너무 많은 요청을 처리하고 있습니다. 다시 시도해주세요 (락)" + logging: level: org.hibernate.sql: TRACE - -# org.springframework.kafka: DEBUG -# org.apache.kafka: DEBUG \ No newline at end of file +# +# org.springframework.kafka: TRACE +# org.apache.kafka: TRACE \ No newline at end of file diff --git a/orders/src/main/resources/application.yml b/orders/src/main/resources/application.yml index 433a202..2731888 100644 --- a/orders/src/main/resources/application.yml +++ b/orders/src/main/resources/application.yml @@ -3,18 +3,11 @@ server: spring: kafka: - bootstrap-servers: localhost:9094 producer: - acks: all - enable: - idempotence: true + transaction-id-prefix: orders-tx consumer: group-id: orders - enable-auto-commit: false - properties: - spring.json.trusted.packages: com.f_lab.joyeuse_planete.core.* - isolation-level: read_committed orders: events: @@ -22,6 +15,9 @@ orders: name: orders.order-created-event fail: orders.orders-creation-failed-event + dead-letter-topic: + name: orders.dead-letter-topic + kafka: topic: partitions: 3 @@ -34,7 +30,8 @@ logging: level: org: apache: - kafka: error +# kafka: TRACE orm: jpa: - JpaTransactionManager: trace + JpaTransactionManager: TRACE + hibernate.sql: TRACE From bd8fda13d75631497e2ec5604a5838f2c7bdcc2f Mon Sep 17 00:00:00 2001 From: koreanMike513 Date: Thu, 30 Jan 2025 14:39:43 +0000 Subject: [PATCH 02/24] fix: Fixed configs and yml files --- .../core/kafka/config/KafkaConsumerConfig.java | 2 +- .../core/kafka/config/KafkaProducerConfig.java | 2 +- foods/src/main/resources/application.yml | 9 +-------- orders/src/main/resources/application.yml | 1 + payment/src/main/resources/application.yml | 10 ++-------- 5 files changed, 6 insertions(+), 18 deletions(-) diff --git a/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/config/KafkaConsumerConfig.java b/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/config/KafkaConsumerConfig.java index d4b3290..291bd71 100644 --- a/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/config/KafkaConsumerConfig.java +++ b/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/config/KafkaConsumerConfig.java @@ -25,7 +25,7 @@ @Slf4j public abstract class KafkaConsumerConfig { - @Value("${spring.kafka.bootstrap-servers:localhost:9092}") + @Value("${spring.kafka.bootstrap-servers}") protected String BOOTSTRAP_SERVERS; @Value("${spring.kafka.consumer.enable-auto-commit:false}") diff --git a/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/config/KafkaProducerConfig.java b/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/config/KafkaProducerConfig.java index 7ced09c..00e6016 100644 --- a/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/config/KafkaProducerConfig.java +++ b/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/config/KafkaProducerConfig.java @@ -12,7 +12,7 @@ public abstract class KafkaProducerConfig { - @Value("${spring.kafka.bootstrap-servers:localhost:9092}") + @Value("${spring.kafka.bootstrap-servers}") protected String BOOTSTRAP_SERVERS; @Value("${spring.kafka.producer.ack:all}") diff --git a/foods/src/main/resources/application.yml b/foods/src/main/resources/application.yml index 9a4e0de..bd34d89 100644 --- a/foods/src/main/resources/application.yml +++ b/foods/src/main/resources/application.yml @@ -4,20 +4,13 @@ spring: application: name: foods -# datasource: -# url: jdbc:h2:tcp://localhost/~/Desktop/software/h2/h2-datafile/kafka -# username: sa -# password: -# driver-class-name: org.h2.Driver jpa: -# database-platform: org.hibernate.dialect.H2Dialect -# hibernate: -# ddl-auto: validate properties: hibernate: show_sql: true kafka: + bootstrap-servers: localhost:9092 producer: transaction-id-prefix: foods-tx diff --git a/orders/src/main/resources/application.yml b/orders/src/main/resources/application.yml index 2731888..83139c4 100644 --- a/orders/src/main/resources/application.yml +++ b/orders/src/main/resources/application.yml @@ -3,6 +3,7 @@ server: spring: kafka: + bootstrap-servers: localhost:9092 producer: transaction-id-prefix: orders-tx diff --git a/payment/src/main/resources/application.yml b/payment/src/main/resources/application.yml index 4096f9c..10ca6f0 100644 --- a/payment/src/main/resources/application.yml +++ b/payment/src/main/resources/application.yml @@ -5,18 +5,12 @@ spring: name: payments kafka: + bootstrap-servers: localhost:9092 producer: - bootstrap-servers: localhost:9092 - acks: all - enable: - idempotence: true + transaction-id-prefix: payments-tx consumer: - bootstrap-servers: localhost:9092 group-id: payments - enable-auto-commit: false - properties: - spring.json.trusted.packages: com.f-lab.joyeuse_planete.core.* payments: events: From 8863821257d02e7b473814d160a9b2ec53f6a031 Mon Sep 17 00:00:00 2001 From: koreanMike513 Date: Wed, 29 Jan 2025 00:03:13 +0000 Subject: [PATCH 03/24] refactor: Refactored entities and util classes --- .../joyeuse_planete/core/domain/Food.java | 4 ++- .../joyeuse_planete/core/domain/Order.java | 13 +++++---- .../core/exceptions/ErrorCode.java | 5 ++-- .../core/kafka/util/ExceptionUtil.java | 29 +++++++++++++++++++ .../core/kafka/util/LogUtil.java | 15 ++++++++++ 5 files changed, 56 insertions(+), 10 deletions(-) create mode 100644 core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/util/ExceptionUtil.java create mode 100644 core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/util/LogUtil.java diff --git a/core/src/main/java/com/f_lab/joyeuse_planete/core/domain/Food.java b/core/src/main/java/com/f_lab/joyeuse_planete/core/domain/Food.java index 1a2f2c2..09a87d8 100644 --- a/core/src/main/java/com/f_lab/joyeuse_planete/core/domain/Food.java +++ b/core/src/main/java/com/f_lab/joyeuse_planete/core/domain/Food.java @@ -1,6 +1,8 @@ package com.f_lab.joyeuse_planete.core.domain; import com.f_lab.joyeuse_planete.core.domain.base.BaseEntity; +import com.f_lab.joyeuse_planete.core.exceptions.ErrorCode; +import com.f_lab.joyeuse_planete.core.exceptions.JoyeusePlaneteApplicationException; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; @@ -51,7 +53,7 @@ public BigDecimal calculateCost(int quantity) { public void minusQuantity(int quantity) { if (totalQuantity - quantity < 0) - throw new IllegalStateException("수량이 부족합니다. 따라서 구매가 진행될 수 없습니다."); + throw new JoyeusePlaneteApplicationException(ErrorCode.FOOD_NOT_ENOUGH_STOCK); totalQuantity -= quantity; } diff --git a/core/src/main/java/com/f_lab/joyeuse_planete/core/domain/Order.java b/core/src/main/java/com/f_lab/joyeuse_planete/core/domain/Order.java index 88cd6bc..8829f2e 100644 --- a/core/src/main/java/com/f_lab/joyeuse_planete/core/domain/Order.java +++ b/core/src/main/java/com/f_lab/joyeuse_planete/core/domain/Order.java @@ -2,6 +2,7 @@ import com.f_lab.joyeuse_planete.core.domain.base.BaseEntity; +import com.f_lab.joyeuse_planete.core.domain.base.BaseTimeEntity; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; @@ -11,13 +12,10 @@ import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToOne; import jakarta.persistence.Table; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; +import lombok.*; import java.math.BigDecimal; +import java.time.LocalDateTime; import static jakarta.persistence.FetchType.LAZY; import static jakarta.persistence.GenerationType.IDENTITY; @@ -25,10 +23,11 @@ @Entity @Builder @Getter @Setter +@ToString(exclude = { "food", "payment", "voucher" }) @AllArgsConstructor @NoArgsConstructor @Table(name = "orders") -public class Order extends BaseEntity { +public class Order extends BaseTimeEntity { @Id @GeneratedValue(strategy = IDENTITY) @@ -53,6 +52,8 @@ public class Order extends BaseEntity { @JoinColumn(name = "voucher_id") private Voucher voucher; + private LocalDateTime collectionTime; + public BigDecimal calculateTotalCost() { return (voucher != null) ? voucher.apply(food.calculateCost(quantity), food.getCurrency()) diff --git a/core/src/main/java/com/f_lab/joyeuse_planete/core/exceptions/ErrorCode.java b/core/src/main/java/com/f_lab/joyeuse_planete/core/exceptions/ErrorCode.java index 75f37d8..e2dff59 100644 --- a/core/src/main/java/com/f_lab/joyeuse_planete/core/exceptions/ErrorCode.java +++ b/core/src/main/java/com/f_lab/joyeuse_planete/core/exceptions/ErrorCode.java @@ -9,11 +9,10 @@ @AllArgsConstructor(access = PRIVATE) public enum ErrorCode { FOOD_NOT_EXIST_EXCEPTION("상품이 존재하지 않습니다.", 400), - - + FOOD_NOT_ENOUGH_STOCK("상품의 수량이 부족합니다", 409), // LOCK - LOCK_ACQUISITION_FAIL_EXCEPTION("현재 너무 많은 요청을 처리하고 있습니다. 다시 시도해주세요",503), + LOCK_ACQUISITION_FAIL_EXCEPTION("현재 너무 많은 요청을 처리하고 있습니다. 다시 시도해주세요 (락)",503), // KAFKA KAFKA_RETRY_FAIL_EXCEPTION("오류 발생! 잠시 후 다시 시도해주세요.", 503), diff --git a/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/util/ExceptionUtil.java b/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/util/ExceptionUtil.java new file mode 100644 index 0000000..454fe2d --- /dev/null +++ b/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/util/ExceptionUtil.java @@ -0,0 +1,29 @@ +package com.f_lab.joyeuse_planete.core.kafka.util; + +import com.f_lab.joyeuse_planete.core.exceptions.ErrorCode; + +import org.springframework.kafka.listener.ListenerExecutionFailedException; + +import java.util.List; + +public class ExceptionUtil { + private static final List> unWrapList = List.of( + ListenerExecutionFailedException.class + ); + + private static final List nonRequeueList = List.of( + ErrorCode.FOOD_NOT_ENOUGH_STOCK.getDescription(), + ErrorCode.FOOD_NOT_EXIST_EXCEPTION.getDescription() + ); + + public static Exception unwrap(Exception e) { + while (e.getCause() != null && unWrapList.contains(e.getClass())) { + e = (Exception) e.getCause(); + } + return e; + } + + public static boolean checkRequeue(String message) { + return !nonRequeueList.contains(message); + } +} diff --git a/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/util/LogUtil.java b/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/util/LogUtil.java new file mode 100644 index 0000000..473fdd5 --- /dev/null +++ b/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/util/LogUtil.java @@ -0,0 +1,15 @@ +package com.f_lab.joyeuse_planete.core.kafka.util; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class LogUtil { + + public static void logException(Exception e) { + + } + + public static void logRetryException() { + + } +} From fa78a5fe0ada9b62e9a15a9fb77415833b176ee0 Mon Sep 17 00:00:00 2001 From: koreanMike513 Date: Wed, 29 Jan 2025 00:06:54 +0000 Subject: [PATCH 04/24] refactor: Refactored projects' kafka config --- .../core/events/OrderCreatedEvent.java | 2 ++ .../foods/config/KafkaConfig.java | 21 ++++++++++++---- .../orders/config/KafkaConfig.java | 25 ++++++++++++------- .../payment/config/KafkaConfig.java | 13 ++++++---- payment/src/main/resources/application.yml | 3 +++ 5 files changed, 45 insertions(+), 19 deletions(-) diff --git a/core/src/main/java/com/f_lab/joyeuse_planete/core/events/OrderCreatedEvent.java b/core/src/main/java/com/f_lab/joyeuse_planete/core/events/OrderCreatedEvent.java index ec6237b..ba0591d 100644 --- a/core/src/main/java/com/f_lab/joyeuse_planete/core/events/OrderCreatedEvent.java +++ b/core/src/main/java/com/f_lab/joyeuse_planete/core/events/OrderCreatedEvent.java @@ -14,6 +14,8 @@ @AllArgsConstructor public class OrderCreatedEvent { + private Long orderId; + private Long foodId; private String foodName; diff --git a/foods/src/main/java/com/f_lab/joyeuse_planete/foods/config/KafkaConfig.java b/foods/src/main/java/com/f_lab/joyeuse_planete/foods/config/KafkaConfig.java index da9b2c5..a26d7ba 100644 --- a/foods/src/main/java/com/f_lab/joyeuse_planete/foods/config/KafkaConfig.java +++ b/foods/src/main/java/com/f_lab/joyeuse_planete/foods/config/KafkaConfig.java @@ -6,12 +6,15 @@ import com.f_lab.joyeuse_planete.core.kafka.config.KafkaConsumerConfig; import com.f_lab.joyeuse_planete.core.kafka.config.KafkaProducerConfig; import com.f_lab.joyeuse_planete.core.kafka.service.KafkaService; +import com.f_lab.joyeuse_planete.core.kafka.util.ExceptionUtil; import jakarta.persistence.EntityManagerFactory; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.kafka.clients.admin.NewTopic; import org.apache.kafka.clients.consumer.ConsumerConfig; +import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.producer.ProducerConfig; +import org.apache.kafka.clients.producer.ProducerRecord; import org.apache.kafka.common.TopicPartition; import org.apache.kafka.common.serialization.StringDeserializer; import org.apache.kafka.common.serialization.StringSerializer; @@ -24,13 +27,17 @@ import org.springframework.kafka.config.TopicBuilder; import org.springframework.kafka.core.KafkaTemplate; import org.springframework.kafka.listener.DeadLetterPublishingRecoverer; +import org.springframework.kafka.support.KafkaHeaders; import org.springframework.kafka.support.serializer.JsonDeserializer; import org.springframework.kafka.support.serializer.JsonSerializer; import org.springframework.kafka.transaction.KafkaTransactionManager; import org.springframework.orm.jpa.JpaTransactionManager; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import java.util.function.BiFunction; @Slf4j @Configuration @@ -98,10 +105,10 @@ public KafkaTransactionManager kafkaTransactionManager() { @RequiredArgsConstructor static class CustomKafkaConsumerConfig extends KafkaConsumerConfig { - private final KafkaTemplate kafkaTemplate; + @Value("${foods.dead-letter-topic.name}") + private String DEAD_LETTER_TOPIC; - @Value("${kafka.dead-letter-topic.suffix:-dlt}") - private String DLT_SUFFIX; + private final KafkaTemplate kafkaTemplate; @Override protected Map consumerConfig() { @@ -117,10 +124,14 @@ protected Map consumerConfig() { return config; } + @Override + protected String deadLetterTopicName() { + return DEAD_LETTER_TOPIC; + } + @Override protected DeadLetterPublishingRecoverer deadLetterPublishingRecoverer() { - return new DeadLetterPublishingRecoverer(kafkaTemplate, - (record, ex) -> new TopicPartition(record.topic() + DLT_SUFFIX, record.partition())); + return new DeadLetterPublishingRecoverer(kafkaTemplate, deadLetterTopicStrategy()); } @Override diff --git a/orders/src/main/java/com/f_lab/joyeuse_planete/orders/config/KafkaConfig.java b/orders/src/main/java/com/f_lab/joyeuse_planete/orders/config/KafkaConfig.java index c836e3e..f13acef 100644 --- a/orders/src/main/java/com/f_lab/joyeuse_planete/orders/config/KafkaConfig.java +++ b/orders/src/main/java/com/f_lab/joyeuse_planete/orders/config/KafkaConfig.java @@ -34,9 +34,14 @@ @Configuration public class KafkaConfig { - @Value("${kafka.topic.partitions:3}") int TOPIC_PARTITIONS; - @Value("${orders.events.topic.name}") String ORDER_CREATED_EVENT; - @Value("${orders.events.topic.fail}") String ORDER_CREATION_FAILED_EVENT; + @Value("${kafka.topic.partitions:3}") + int TOPIC_PARTITIONS; + + @Value("${orders.events.topic.name}") + String ORDER_CREATED_EVENT; + + @Value("${orders.events.topic.fail}") + String ORDER_CREATION_FAILED_EVENT; @Bean public KafkaService kafkaService(KafkaTemplate kafkaTemplate) { @@ -59,8 +64,6 @@ public NewTopic orderCreationFailedEvent() { .build(); } -// @Bean -// public NewTopic @Primary @Bean(name = { "transactionManager", "jpaTransactionManager" }) @@ -107,8 +110,8 @@ static class CustomKafkaConsumerConfig extends KafkaConsumerConfig { private final KafkaTemplate kafkaTemplate; - @Value("${kafka.dead-letter-topic.suffix:-dlt}") - private String DLT_SUFFIX; + @Value("${orders.dead-letter-topic.name}") + private String DEAD_LETTER_TOPIC; @Override protected Map consumerConfig() { @@ -124,10 +127,14 @@ protected Map consumerConfig() { return config; } + @Override + protected String deadLetterTopicName() { + return DEAD_LETTER_TOPIC; + } + @Override protected DeadLetterPublishingRecoverer deadLetterPublishingRecoverer() { - return new DeadLetterPublishingRecoverer(kafkaTemplate, - (record, ex) -> new TopicPartition(record.topic() + DLT_SUFFIX, record.partition())); + return new DeadLetterPublishingRecoverer(kafkaTemplate, deadLetterTopicStrategy()); } @Override diff --git a/payment/src/main/java/com/f_lab/joyeuse_planete/payment/config/KafkaConfig.java b/payment/src/main/java/com/f_lab/joyeuse_planete/payment/config/KafkaConfig.java index dde5992..bb72a63 100644 --- a/payment/src/main/java/com/f_lab/joyeuse_planete/payment/config/KafkaConfig.java +++ b/payment/src/main/java/com/f_lab/joyeuse_planete/payment/config/KafkaConfig.java @@ -10,7 +10,6 @@ import org.apache.kafka.clients.admin.NewTopic; import org.apache.kafka.clients.consumer.ConsumerConfig; import org.apache.kafka.clients.producer.ProducerConfig; -import org.apache.kafka.common.TopicPartition; import org.apache.kafka.common.serialization.StringDeserializer; import org.apache.kafka.common.serialization.StringSerializer; import org.springframework.beans.factory.annotation.Value; @@ -108,8 +107,8 @@ static class CustomKafkaConsumerConfig extends KafkaConsumerConfig { private final KafkaTemplate kafkaTemplate; - @Value("${kafka.dead-letter-topic.suffix:-dlt}") - private String DLT_SUFFIX; + @Value("${payments.dead-letter-topic.name}") + private String DEAD_LETTER_TOPIC; @Override protected Map consumerConfig() { @@ -125,10 +124,14 @@ protected Map consumerConfig() { return config; } + @Override + protected String deadLetterTopicName() { + return DEAD_LETTER_TOPIC; + } + @Override protected DeadLetterPublishingRecoverer deadLetterPublishingRecoverer() { - return new DeadLetterPublishingRecoverer(kafkaTemplate, - (record, ex) -> new TopicPartition(record.topic() + DLT_SUFFIX, record.partition())); + return new DeadLetterPublishingRecoverer(kafkaTemplate, deadLetterTopicStrategy()); } @Override diff --git a/payment/src/main/resources/application.yml b/payment/src/main/resources/application.yml index 10ca6f0..16a5b67 100644 --- a/payment/src/main/resources/application.yml +++ b/payment/src/main/resources/application.yml @@ -18,6 +18,9 @@ payments: name: payments.order-created-event fail: payments.order-creation-failed-event + dead-letter-topic: + name: payments.dead-letter-topic + kafka: topic: partitions: 3 From d7fdb8d0940ac00840d995d8b77d9eaba799de56 Mon Sep 17 00:00:00 2001 From: koreanMike513 Date: Wed, 29 Jan 2025 00:18:15 +0000 Subject: [PATCH 05/24] feat: Added foods dead letter topic and gradle configs --- .../foods/service/FoodService.java | 1 - .../OrderCreatedDeadLetterTopicHandler.java | 53 +++++++++++++++++++ gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 5 +- gradlew.bat | 2 - 5 files changed, 55 insertions(+), 8 deletions(-) create mode 100644 foods/src/main/java/com/f_lab/joyeuse_planete/foods/service/handler/OrderCreatedDeadLetterTopicHandler.java diff --git a/foods/src/main/java/com/f_lab/joyeuse_planete/foods/service/FoodService.java b/foods/src/main/java/com/f_lab/joyeuse_planete/foods/service/FoodService.java index 10fd418..3546200 100644 --- a/foods/src/main/java/com/f_lab/joyeuse_planete/foods/service/FoodService.java +++ b/foods/src/main/java/com/f_lab/joyeuse_planete/foods/service/FoodService.java @@ -18,7 +18,6 @@ public class FoodService { private final FoodRepository foodRepository; - @Transactional public void reserve(Long foodId, int quantity) { Food food = findFoodWithLock(foodId); diff --git a/foods/src/main/java/com/f_lab/joyeuse_planete/foods/service/handler/OrderCreatedDeadLetterTopicHandler.java b/foods/src/main/java/com/f_lab/joyeuse_planete/foods/service/handler/OrderCreatedDeadLetterTopicHandler.java new file mode 100644 index 0000000..71305c7 --- /dev/null +++ b/foods/src/main/java/com/f_lab/joyeuse_planete/foods/service/handler/OrderCreatedDeadLetterTopicHandler.java @@ -0,0 +1,53 @@ +package com.f_lab.joyeuse_planete.foods.service.handler; + +import com.f_lab.joyeuse_planete.core.events.OrderCreatedEvent; +import com.f_lab.joyeuse_planete.core.events.OrderCreationFailedEvent; +import com.f_lab.joyeuse_planete.core.kafka.exceptions.NonRetryableException; +import com.f_lab.joyeuse_planete.core.kafka.exceptions.RetryableException; +import com.f_lab.joyeuse_planete.core.kafka.service.KafkaService; +import com.f_lab.joyeuse_planete.core.kafka.util.ExceptionUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.kafka.annotation.KafkaHandler; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.kafka.support.KafkaHeaders; +import org.springframework.messaging.handler.annotation.Header; +import org.springframework.messaging.handler.annotation.Payload; +import org.springframework.stereotype.Component; + +import java.util.Objects; + +import static com.f_lab.joyeuse_planete.core.util.time.TimeConstantsString.FIVE_SECONDS; + + +@Slf4j +@Component +@RequiredArgsConstructor +@KafkaListener(topics = "${foods.dead-letter-topic.name}", groupId = "${spring.kafka.consumer.group-id}") +public class OrderCreatedDeadLetterTopicHandler { + + private final KafkaService kafkaService; + + @KafkaHandler + public void processDeadLetterTopic( + @Payload OrderCreatedEvent orderCreatedEvent, + @Header(value = KafkaHeaders.EXCEPTION_MESSAGE, required = false) String exceptionMessage, + @Header(value = KafkaHeaders.ORIGINAL_TOPIC, required = false) String originalTopic) { + + log.info("PAYLOAD = {} MESSAGE = {}, TOPIC = {}", orderCreatedEvent, exceptionMessage, originalTopic); + + if (Objects.isNull(exceptionMessage) || + Objects.isNull(originalTopic) || + !ExceptionUtil.checkRequeue(exceptionMessage) + ) { + return; + } + + try { + Thread.sleep(Integer.parseInt(FIVE_SECONDS)); + kafkaService.sendKafkaEvent(originalTopic, orderCreatedEvent); + } catch (InterruptedException e) { + throw new RetryableException(); + } + } +} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e2847c8..a441313 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index f5feea6..b740cf1 100755 --- a/gradlew +++ b/gradlew @@ -15,8 +15,6 @@ # See the License for the specific language governing permissions and # limitations under the License. # -# SPDX-License-Identifier: Apache-2.0 -# ############################################################################## # @@ -86,8 +84,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s -' "$PWD" ) || exit +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/gradlew.bat b/gradlew.bat index 9d21a21..25da30d 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,8 +13,6 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem -@rem SPDX-License-Identifier: Apache-2.0 -@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## From 408bcc379a8af21f09eac410e1eb6e321b6d9c99 Mon Sep 17 00:00:00 2001 From: koreanMike513 Date: Wed, 29 Jan 2025 00:22:10 +0000 Subject: [PATCH 06/24] feat: Added querydsl package and DTOs --- build.gradle | 6 ++ orders/build.gradle | 1 + .../orders/domain/OrderSearchCondition.java | 25 +++++++ .../dto/request/OrderCreateRequestDTO.java | 25 +++++-- .../orders/dto/response/OrderDTO.java | 75 +++++++++++++++++++ 5 files changed, 127 insertions(+), 5 deletions(-) create mode 100644 orders/src/main/java/com/f_lab/joyeuse_planete/orders/domain/OrderSearchCondition.java create mode 100644 orders/src/main/java/com/f_lab/joyeuse_planete/orders/dto/response/OrderDTO.java diff --git a/build.gradle b/build.gradle index c330629..4a59b15 100644 --- a/build.gradle +++ b/build.gradle @@ -40,6 +40,12 @@ subprojects { // lombok 테스트에서도 사용 testCompileOnly 'org.projectlombok:lombok' testAnnotationProcessor 'org.projectlombok:lombok' + + // Querydsl 추가 + implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' + annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta" + annotationProcessor "jakarta.annotation:jakarta.annotation-api" + annotationProcessor "jakarta.persistence:jakarta.persistence-api" } tasks.named('test') { diff --git a/orders/build.gradle b/orders/build.gradle index e9f5add..3eecb0b 100644 --- a/orders/build.gradle +++ b/orders/build.gradle @@ -14,4 +14,5 @@ dependencies { implementation 'org.springframework.kafka:spring-kafka' testImplementation 'org.springframework.kafka:spring-kafka' + testImplementation 'org.springframework.kafka:spring-kafka-test' } diff --git a/orders/src/main/java/com/f_lab/joyeuse_planete/orders/domain/OrderSearchCondition.java b/orders/src/main/java/com/f_lab/joyeuse_planete/orders/domain/OrderSearchCondition.java new file mode 100644 index 0000000..5780e7f --- /dev/null +++ b/orders/src/main/java/com/f_lab/joyeuse_planete/orders/domain/OrderSearchCondition.java @@ -0,0 +1,25 @@ +package com.f_lab.joyeuse_planete.orders.domain; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.time.Month; +import java.util.List; + +@Data +@NoArgsConstructor +public class OrderSearchCondition { + + //TODO Java Bean Validation 추가 + + private BigDecimal minCost = BigDecimal.ZERO; // Default value + private BigDecimal maxCost; + private String status = "ALL"; + private LocalDateTime startDate = LocalDateTime.of(2024, Month.JANUARY, 1, 0, 0); + private LocalDateTime endDate; + private List sortBy = List.of("DATE_NEW"); + private int page = 0; // Default page + private int size = 10; // Default page size +} diff --git a/orders/src/main/java/com/f_lab/joyeuse_planete/orders/dto/request/OrderCreateRequestDTO.java b/orders/src/main/java/com/f_lab/joyeuse_planete/orders/dto/request/OrderCreateRequestDTO.java index e3aff0a..8f6c596 100644 --- a/orders/src/main/java/com/f_lab/joyeuse_planete/orders/dto/request/OrderCreateRequestDTO.java +++ b/orders/src/main/java/com/f_lab/joyeuse_planete/orders/dto/request/OrderCreateRequestDTO.java @@ -1,6 +1,8 @@ package com.f_lab.joyeuse_planete.orders.dto.request; +import com.f_lab.joyeuse_planete.core.domain.Order; +import com.f_lab.joyeuse_planete.core.domain.OrderStatus; import com.f_lab.joyeuse_planete.core.events.OrderCreatedEvent; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AllArgsConstructor; @@ -19,11 +21,14 @@ public class OrderCreateRequestDTO { @JsonProperty("food_id") private Long foodId; + @JsonProperty("food_name") + private String foodName; + @JsonProperty("store_id") private Long storeId; - @JsonProperty("total_amount") - private BigDecimal totalAmount; + @JsonProperty("total_cost") + private BigDecimal totalCost; @JsonProperty("quantity") private int quantity; @@ -35,12 +40,22 @@ public class OrderCreateRequestDTO { private PaymentInformation paymentInformation; - public OrderCreatedEvent toEvent() { + public Order toEntity() { + return Order.builder() + .status(OrderStatus.READY) + .totalCost(totalCost) + .quantity(quantity) + .build(); + } + + public OrderCreatedEvent toEvent(Long orderId) { return OrderCreatedEvent.builder() + .orderId(orderId) .foodId(foodId) + .foodName(foodName) .storeId(storeId) .quantity(quantity) - .totalAmount(totalAmount) + .totalAmount(totalCost) .voucherId(voucherId) .paymentInformation(paymentInformation.toPaymentInformation()) .build(); @@ -58,7 +73,7 @@ static class PaymentInformation { @JsonProperty("card_holdername") private String cardHolderName; - @JsonProperty("expiery_date") + @JsonProperty("expiry_date") private String expiryDate; @JsonProperty("cvc") diff --git a/orders/src/main/java/com/f_lab/joyeuse_planete/orders/dto/response/OrderDTO.java b/orders/src/main/java/com/f_lab/joyeuse_planete/orders/dto/response/OrderDTO.java new file mode 100644 index 0000000..38b4076 --- /dev/null +++ b/orders/src/main/java/com/f_lab/joyeuse_planete/orders/dto/response/OrderDTO.java @@ -0,0 +1,75 @@ +package com.f_lab.joyeuse_planete.orders.dto.response; + +import com.f_lab.joyeuse_planete.core.domain.*; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.querydsl.core.annotations.QueryProjection; +import jakarta.persistence.JoinColumn; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + + +@Getter +@Builder +@NoArgsConstructor +public class OrderDTO { + + @JsonProperty("order_id") + private Long orderId; + + @JsonProperty("food_name") + private String foodName; + + @JsonProperty("total_cost") + private BigDecimal totalCost; + + @JsonProperty("currency_code") + private String currencyCode; + + @JsonProperty("currency_symbol") + private String currencySymbol; + + @JsonProperty("quantity") + private int quantity; + + @JsonProperty("status") + private String status; + + @JoinColumn(name = "payment_id") + private Long payment; + + @JoinColumn(name = "voucher_id") + private Long voucher; + + @JoinColumn(name = "collection_time") + private LocalDateTime collectionTime; + + @QueryProjection + public OrderDTO( + Long orderId, + String foodName, + BigDecimal totalCost, + String currencyCode, + String currencySymbol, + int quantity, + String status, + Long payment, + Long voucher, + LocalDateTime collectionTime + ) { + + this.orderId = orderId; + this.foodName = foodName; + this.totalCost = totalCost; + this.currencyCode = currencyCode; + this.currencySymbol = currencySymbol; + this.quantity = quantity; + this.status = status; + this.payment = payment; + this.voucher = voucher; + this.collectionTime = collectionTime; + } +} From d5ba45ba3438971a3480fedc21de1055cca40507 Mon Sep 17 00:00:00 2001 From: koreanMike513 Date: Wed, 29 Jan 2025 00:25:34 +0000 Subject: [PATCH 07/24] feat: Added OrderRepositoryCustom --- .../orders/repository/OrderRepository.java | 6 +- .../repository/OrderRepositoryCustom.java | 13 ++ .../repository/OrderRepositoryCustomImpl.java | 129 ++++++++++++++++++ .../orders/service/OrderService.java | 46 +++---- .../repository/OrderRepositoryTest.java | 68 +++++++++ 5 files changed, 237 insertions(+), 25 deletions(-) create mode 100644 orders/src/main/java/com/f_lab/joyeuse_planete/orders/repository/OrderRepositoryCustom.java create mode 100644 orders/src/main/java/com/f_lab/joyeuse_planete/orders/repository/OrderRepositoryCustomImpl.java create mode 100644 orders/src/test/java/com/f_lab/joyeuse_planete/orders/repository/OrderRepositoryTest.java diff --git a/orders/src/main/java/com/f_lab/joyeuse_planete/orders/repository/OrderRepository.java b/orders/src/main/java/com/f_lab/joyeuse_planete/orders/repository/OrderRepository.java index 2ca278f..5273f80 100644 --- a/orders/src/main/java/com/f_lab/joyeuse_planete/orders/repository/OrderRepository.java +++ b/orders/src/main/java/com/f_lab/joyeuse_planete/orders/repository/OrderRepository.java @@ -4,5 +4,9 @@ import com.f_lab.joyeuse_planete.core.domain.Order; import org.springframework.data.jpa.repository.JpaRepository; -public interface OrderRepository extends JpaRepository { +import java.util.List; +import java.util.Optional; + +public interface OrderRepository extends JpaRepository, OrderRepositoryCustom { + } diff --git a/orders/src/main/java/com/f_lab/joyeuse_planete/orders/repository/OrderRepositoryCustom.java b/orders/src/main/java/com/f_lab/joyeuse_planete/orders/repository/OrderRepositoryCustom.java new file mode 100644 index 0000000..312b57b --- /dev/null +++ b/orders/src/main/java/com/f_lab/joyeuse_planete/orders/repository/OrderRepositoryCustom.java @@ -0,0 +1,13 @@ +package com.f_lab.joyeuse_planete.orders.repository; + +import com.f_lab.joyeuse_planete.orders.domain.OrderSearchCondition; +import com.f_lab.joyeuse_planete.orders.dto.response.OrderDTO; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + + +public interface OrderRepositoryCustom { + +// Page findOrders(Long memberId, OrderSearchCondition condition, Pageable pageable); + Page findOrders(OrderSearchCondition condition, Pageable pageable); +} diff --git a/orders/src/main/java/com/f_lab/joyeuse_planete/orders/repository/OrderRepositoryCustomImpl.java b/orders/src/main/java/com/f_lab/joyeuse_planete/orders/repository/OrderRepositoryCustomImpl.java new file mode 100644 index 0000000..c1fc9af --- /dev/null +++ b/orders/src/main/java/com/f_lab/joyeuse_planete/orders/repository/OrderRepositoryCustomImpl.java @@ -0,0 +1,129 @@ +package com.f_lab.joyeuse_planete.orders.repository; + +import com.f_lab.joyeuse_planete.core.domain.OrderStatus; +import com.f_lab.joyeuse_planete.orders.domain.OrderSearchCondition; +import com.f_lab.joyeuse_planete.orders.dto.response.OrderDTO; +import com.f_lab.joyeuse_planete.orders.dto.response.QOrderDTO; +import com.querydsl.core.types.OrderSpecifier; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.annotation.PostConstruct; +import jakarta.persistence.EntityManager; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; + + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static com.f_lab.joyeuse_planete.core.domain.QFood.food; +import static com.f_lab.joyeuse_planete.core.domain.QOrder.order; +import static com.f_lab.joyeuse_planete.core.domain.QPayment.payment; + + +public class OrderRepositoryCustomImpl implements OrderRepositoryCustom { + + private Map sortByMap = new HashMap<>(); + private static final List defaultSortBy = List.of("DATE_NEW"); + + @PostConstruct + void init() { + sortByMap.put("PRICE_LOW", order.totalCost.asc()); + sortByMap.put("PRICE_HIGH", order.totalCost.desc()); + sortByMap.put("DATE_NEW", order.createdAt.desc()); + sortByMap.put("DATE_OLD", order.createdAt.asc()); + } + + private final JPAQueryFactory queryFactory; + + public OrderRepositoryCustomImpl(EntityManager em) { + this.queryFactory = new JPAQueryFactory(em); + } + + @Override + public Page findOrders(OrderSearchCondition condition, Pageable pageable) { + List results = queryFactory + .select(new QOrderDTO( + order.id.as("orderId"), + food.foodName, + order.totalCost, + food.currency.currencyCode, + food.currency.currencySymbol, + order.quantity, + order.status.stringValue(), + order.payment.id.as("paymentId"), + order.voucher.id.as("voucherId"), + order.collectionTime + )) + .from(order) + .leftJoin(order.payment, payment) + .leftJoin(order.food, food) + .where( + eqStatus(condition.getStatus()), + dateGoe(condition.getStartDate()), + dateLoe(condition.getEndDate()), + totalCostGoe(condition.getMinCost()), + totalCostLoe(condition.getMaxCost()) + ) + .orderBy(getOrders(condition.getSortBy())) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + + long count = queryFactory + .select(order.count()) + .from(order) + .where( + eqStatus(condition.getStatus()), + dateGoe(condition.getStartDate()), + dateLoe(condition.getEndDate()), + totalCostGoe(condition.getMinCost()), + totalCostLoe(condition.getMaxCost()) + ) + .fetch() + .get(0); + + return new PageImpl<>(results, pageable, count); + } + + private BooleanExpression eqStatus(String status) { + return (status != null && !status.equals("ALL")) + ? order.status.eq(OrderStatus.valueOf(status)) + : null; + } + + private BooleanExpression dateGoe(LocalDateTime date) { + return date != null ? order.createdAt.goe(date) : null; + } + + private BooleanExpression dateLoe(LocalDateTime date) { + return date != null ? order.createdAt.loe(date) : null; + } + + private BooleanExpression totalCostGoe(BigDecimal cost) { + return cost != null ? order.totalCost.goe(cost) : null; + } + + private BooleanExpression totalCostLoe(BigDecimal cost) { + return cost != null ? order.totalCost.loe(cost) : null; + } + + private OrderSpecifier[] getOrders(List sortBy) { + List list = new ArrayList<>(); + + if (sortBy == null) + sortBy = defaultSortBy; + + for (String sort : sortBy) { + if (sortByMap.containsKey(sort)) + list.add(sortByMap.get(sort)); + } + + return list.toArray(OrderSpecifier[]::new); + } +} diff --git a/orders/src/main/java/com/f_lab/joyeuse_planete/orders/service/OrderService.java b/orders/src/main/java/com/f_lab/joyeuse_planete/orders/service/OrderService.java index d4537bc..40e86dc 100644 --- a/orders/src/main/java/com/f_lab/joyeuse_planete/orders/service/OrderService.java +++ b/orders/src/main/java/com/f_lab/joyeuse_planete/orders/service/OrderService.java @@ -2,13 +2,18 @@ +import com.f_lab.joyeuse_planete.core.domain.Order; +import com.f_lab.joyeuse_planete.core.kafka.service.KafkaService; +import com.f_lab.joyeuse_planete.orders.domain.OrderSearchCondition; +import com.f_lab.joyeuse_planete.orders.dto.response.OrderDTO; import com.f_lab.joyeuse_planete.orders.dto.request.OrderCreateRequestDTO; import com.f_lab.joyeuse_planete.orders.dto.response.OrderCreateResponseDTO; import com.f_lab.joyeuse_planete.orders.repository.OrderRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; -import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -19,43 +24,36 @@ public class OrderService { private final OrderRepository orderRepository; - - private final KafkaTemplate kafkaTemplate; + private final KafkaService kafkaService; @Value("${orders.events.topic.name}") String ORDER_CREATED_EVENT; + public Page findOrders(OrderSearchCondition condition, Pageable pageable) { + return orderRepository.findOrders(condition, pageable); + } + @Transactional public OrderCreateResponseDTO createFoodOrder(OrderCreateRequestDTO request) { - log.info("request={}", request); + Order order = request.toEntity(); try { - kafkaTemplate.send(ORDER_CREATED_EVENT, request.toEvent()); + orderRepository.save(order); } catch (Exception e) { - throw new OrderCreatedFailureException(e); - } - - return new OrderCreateResponseDTO("CREATED"); - } - + log.error("오류가 발생하였습니다. message = {}", e.getMessage(), e); - static class OrderCreatedFailureException extends RuntimeException { - public OrderCreatedFailureException() { } - public OrderCreatedFailureException(String message) { - super(message); - } + sendKafkaOrderCreatedEvent(request, order); - public OrderCreatedFailureException(String message, Throwable cause) { - super(message, cause); - } + return new OrderCreateResponseDTO("PROCESSING"); + } - public OrderCreatedFailureException(Throwable cause) { - super(cause); - } - public OrderCreatedFailureException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { - super(message, cause, enableSuppression, writableStackTrace); + public void sendKafkaOrderCreatedEvent(OrderCreateRequestDTO request, Order order) { + try { + kafkaService.sendKafkaEvent(ORDER_CREATED_EVENT, request.toEvent(order.getId())); + } catch (Exception e) { + throw new RuntimeException(e); } } } diff --git a/orders/src/test/java/com/f_lab/joyeuse_planete/orders/repository/OrderRepositoryTest.java b/orders/src/test/java/com/f_lab/joyeuse_planete/orders/repository/OrderRepositoryTest.java new file mode 100644 index 0000000..70ffaa8 --- /dev/null +++ b/orders/src/test/java/com/f_lab/joyeuse_planete/orders/repository/OrderRepositoryTest.java @@ -0,0 +1,68 @@ +package com.f_lab.joyeuse_planete.orders.repository; + +import com.f_lab.joyeuse_planete.core.domain.Order; +import com.f_lab.joyeuse_planete.core.domain.OrderStatus; +import com.f_lab.joyeuse_planete.orders.domain.OrderSearchCondition; +import com.f_lab.joyeuse_planete.orders.dto.response.OrderDTO; +import org.junit.jupiter.api.BeforeEach; +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.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.kafka.test.context.EmbeddedKafka; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + + +@EmbeddedKafka +@SpringBootTest +class OrderRepositoryTest { + + @Autowired OrderRepository orderRepository; + + @BeforeEach + void beforeEach() { + orderRepository.saveAll(getOrderList()); + } + + @Test + @DisplayName("AAA") + void test() { + // given + OrderSearchCondition condition = new OrderSearchCondition(); + Pageable pageable = PageRequest.of(condition.getPage(), condition.getSize()); + + // when + Page result = orderRepository.findOrders(condition, pageable); + + for (Order o : orderRepository.findAll()) { + System.out.println("ACtuAL = " + o); + + } + // then + System.out.println("RESULT " + result); + } + + private List getOrderList() { + List orders = new ArrayList<>(); + + for (int i = 1; i <= 20; i++) { + OrderStatus status = (i % 3 == 0) ? OrderStatus.READY : (i % 2 == 0 ? OrderStatus.IN_PROGRESS : OrderStatus.DONE); + + orders.add(Order.builder() + .totalCost(BigDecimal.ONE) + .quantity(i % 5 + 1) + .status(status) + .collectionTime(LocalDateTime.now().plusDays(i)) + .build()); + } + + return orders; + } +} \ No newline at end of file From cf6b31938587fd9a309e6f3a4dba63ea9b66ba5a Mon Sep 17 00:00:00 2001 From: koreanMike513 Date: Wed, 29 Jan 2025 00:29:52 +0000 Subject: [PATCH 08/24] feat: Added OrderController and tests --- .../orders/controller/OrderController.java | 26 +++++++++-- .../orders/OrdersApplicationTests.java | 3 +- .../orders/service/OrderServiceTest.java | 46 +++++++++++++++++++ orders/src/test/resources/application.yml | 20 ++++++-- 4 files changed, 85 insertions(+), 10 deletions(-) create mode 100644 orders/src/test/java/com/f_lab/joyeuse_planete/orders/service/OrderServiceTest.java diff --git a/orders/src/main/java/com/f_lab/joyeuse_planete/orders/controller/OrderController.java b/orders/src/main/java/com/f_lab/joyeuse_planete/orders/controller/OrderController.java index 5f3baaa..538aaf3 100644 --- a/orders/src/main/java/com/f_lab/joyeuse_planete/orders/controller/OrderController.java +++ b/orders/src/main/java/com/f_lab/joyeuse_planete/orders/controller/OrderController.java @@ -1,17 +1,24 @@ package com.f_lab.joyeuse_planete.orders.controller; +import com.f_lab.joyeuse_planete.orders.domain.OrderSearchCondition; import com.f_lab.joyeuse_planete.orders.dto.request.OrderCreateRequestDTO; import com.f_lab.joyeuse_planete.orders.dto.response.OrderCreateResponseDTO; +import com.f_lab.joyeuse_planete.orders.dto.response.OrderDTO; import com.f_lab.joyeuse_planete.orders.service.OrderService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; @Slf4j @RestController @@ -21,9 +28,18 @@ public class OrderController { private final OrderService orderService; + @GetMapping + @ResponseStatus(HttpStatus.OK) + public Page findOrders(@ModelAttribute OrderSearchCondition condition) { + PageRequest pageRequest = PageRequest.of(condition.getPage(), condition.getSize()); + + return orderService.findOrders(condition, pageRequest); + } + @PostMapping("/foods") public ResponseEntity createFoodOrder( - @RequestBody OrderCreateRequestDTO orderCreateRequestDTO) { + @RequestBody OrderCreateRequestDTO orderCreateRequestDTO + ) { return ResponseEntity .status(HttpStatus.CREATED) diff --git a/orders/src/test/java/com/f_lab/joyeuse_planete/orders/OrdersApplicationTests.java b/orders/src/test/java/com/f_lab/joyeuse_planete/orders/OrdersApplicationTests.java index 44c3846..b08733c 100644 --- a/orders/src/test/java/com/f_lab/joyeuse_planete/orders/OrdersApplicationTests.java +++ b/orders/src/test/java/com/f_lab/joyeuse_planete/orders/OrdersApplicationTests.java @@ -2,12 +2,13 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.kafka.test.context.EmbeddedKafka; +@EmbeddedKafka @SpringBootTest class OrdersApplicationTests { @Test void contextLoads() { } - } diff --git a/orders/src/test/java/com/f_lab/joyeuse_planete/orders/service/OrderServiceTest.java b/orders/src/test/java/com/f_lab/joyeuse_planete/orders/service/OrderServiceTest.java new file mode 100644 index 0000000..bd98c54 --- /dev/null +++ b/orders/src/test/java/com/f_lab/joyeuse_planete/orders/service/OrderServiceTest.java @@ -0,0 +1,46 @@ +package com.f_lab.joyeuse_planete.orders.service; + +import com.f_lab.joyeuse_planete.orders.domain.OrderSearchCondition; +import com.f_lab.joyeuse_planete.orders.dto.response.OrderDTO; +import com.f_lab.joyeuse_planete.orders.repository.OrderRepository; +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 org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class OrderServiceTest { + + @InjectMocks + OrderService orderService; + @Mock + OrderRepository orderRepository; + + @Test + @DisplayName("orderService 가 올바로 orderRepository를 호출하고 Page를 return 하는 것을 확인") + void testCallingOnOrderRepositoryAndReturnPageOrderDTO() { + // given + Page expected = Page.empty(); + OrderSearchCondition condition = new OrderSearchCondition(); + Pageable pageable = PageRequest.of(0, 10); + + // when + when(orderRepository.findOrders(any(), any())).thenReturn(expected); + Page result = orderService.findOrders(condition, pageable); + + // then + assertThat(result).isEqualTo(expected); + verify(orderRepository, times(1)).findOrders(condition, pageable); + } +} \ No newline at end of file diff --git a/orders/src/test/resources/application.yml b/orders/src/test/resources/application.yml index 9330456..3f56b85 100644 --- a/orders/src/test/resources/application.yml +++ b/orders/src/test/resources/application.yml @@ -1,7 +1,19 @@ spring: - profiles: - active: test - + datasource: + url: jdbc:h2:mem:test;DB_CLOSE_DELAY=-1 + username: sa + password: + driver-class-name: org.h2.Driver jpa: hibernate: - ddl-auto: create-drop \ No newline at end of file + ddl-auto: create-drop + + kafka: + producer: + transaction-id-prefix: test-tx + +orders: + events: + topic: + name: orders.test + fail: orders.fail.test \ No newline at end of file From 3186a6a17a15dd24dd15dcde14de45205b077bfa Mon Sep 17 00:00:00 2001 From: koreanMike513 Date: Wed, 29 Jan 2025 00:31:43 +0000 Subject: [PATCH 09/24] feat: Added tests --- .../joyeuse_planete/core/domain/FoodTest.java | 3 +- .../foods/FoodsApplicationTests.java | 18 +- .../foods/aspect/LockRetryAspectTest.java | 224 +++++++++--------- .../handler/PaymentProcessHandler.java | 36 --- 4 files changed, 123 insertions(+), 158 deletions(-) delete mode 100644 payment/src/main/java/com/f_lab/joyeuse_planete/payment/service/handler/PaymentProcessHandler.java diff --git a/core/src/test/java/com/f_lab/joyeuse_planete/core/domain/FoodTest.java b/core/src/test/java/com/f_lab/joyeuse_planete/core/domain/FoodTest.java index 1b61f50..dd9980c 100644 --- a/core/src/test/java/com/f_lab/joyeuse_planete/core/domain/FoodTest.java +++ b/core/src/test/java/com/f_lab/joyeuse_planete/core/domain/FoodTest.java @@ -1,5 +1,6 @@ package com.f_lab.joyeuse_planete.core.domain; +import com.f_lab.joyeuse_planete.core.exceptions.JoyeusePlaneteApplicationException; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -35,7 +36,7 @@ void test_minus_food_quantity_fail() { // then assertThatThrownBy(() -> food.minusQuantity(11)) - .isInstanceOf(IllegalStateException.class); + .isInstanceOf(JoyeusePlaneteApplicationException.class); } private Food createFood(BigDecimal price, int quantity) { diff --git a/foods/src/test/java/com/f_lab/joyeuse_planete/foods/FoodsApplicationTests.java b/foods/src/test/java/com/f_lab/joyeuse_planete/foods/FoodsApplicationTests.java index c69d562..9ba72ca 100644 --- a/foods/src/test/java/com/f_lab/joyeuse_planete/foods/FoodsApplicationTests.java +++ b/foods/src/test/java/com/f_lab/joyeuse_planete/foods/FoodsApplicationTests.java @@ -2,12 +2,12 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class FoodsApplicationTests { - - @Test - void contextLoads() { - } - -} +// +//@SpringBootTest +//class FoodsApplicationTests { +// +// @Test +// void contextLoads() { +// } +// +//} diff --git a/foods/src/test/java/com/f_lab/joyeuse_planete/foods/aspect/LockRetryAspectTest.java b/foods/src/test/java/com/f_lab/joyeuse_planete/foods/aspect/LockRetryAspectTest.java index 6993792..3679a0e 100644 --- a/foods/src/test/java/com/f_lab/joyeuse_planete/foods/aspect/LockRetryAspectTest.java +++ b/foods/src/test/java/com/f_lab/joyeuse_planete/foods/aspect/LockRetryAspectTest.java @@ -1,112 +1,112 @@ -package com.f_lab.joyeuse_planete.foods.aspect; - -import com.f_lab.joyeuse_planete.core.aspect.RetryOnLockFailure; -import com.f_lab.joyeuse_planete.core.domain.Food; -import com.f_lab.joyeuse_planete.foods.repository.FoodRepository; -import jakarta.persistence.LockTimeoutException; -import jakarta.persistence.PessimisticLockException; -import lombok.RequiredArgsConstructor; -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.boot.test.context.TestConfiguration; -import org.springframework.context.annotation.Bean; -import org.springframework.test.context.bean.override.mockito.MockitoBean; - -import java.util.Optional; - -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -@SpringBootTest -class LockRetryAspectTest { - - @MockitoBean - private FoodRepository foodRepository; - - @Autowired - private FoodTestService foodTestService; - - @TestConfiguration - static class LockRetryAspectTestConfig { - @Bean - public FoodTestService foodTestService(FoodRepository foodRepository) { - return new FoodTestService(foodRepository); - } - } - - @Test - @DisplayName("락 없이 첫 시도에 성공") - void test_find_food_lock_and_retry_success() { - // given - Long foodId = 1L; - Food expectedFood = createFood(foodId); - - // when - when(foodRepository.findFoodByFoodIdWithPessimisticLock(foodId)) - .thenReturn(Optional.ofNullable(expectedFood)); - - Food foundFood = foodTestService.findFood(foodId).get(); - - // then - assertThat(foundFood.getId()).isEqualTo(foodId); - } - - @Test - @DisplayName("첫 번째 시도는 실패하고 두 번째 시도에 성공") - void test_find_food_lock_and_retry_fail_on_first_then_success() { - // given - Long foodId = 1L; - Food expectedFood = createFood(foodId); - - // when - when(foodRepository.findFoodByFoodIdWithPessimisticLock(anyLong())) - .thenThrow(new PessimisticLockException()) - .thenReturn(Optional.ofNullable(expectedFood)); - - Food foundFood = foodTestService.findFood(foodId).get(); - - // then - assertThat(foundFood.getId()).isEqualTo(foodId); - verify(foodRepository, times(2)).findFoodByFoodIdWithPessimisticLock(foodId); - } - - @Test - @DisplayName("락 타임아웃으로 최대 재시도 후 실패") - void test_find_food_lock_and_retry_fail() { - // given - Long foodId = 1L; - - // when - when(foodRepository.findFoodByFoodIdWithPessimisticLock(anyLong())) - .thenThrow(new PessimisticLockException()) - .thenThrow(new LockTimeoutException()) - .thenThrow(new LockTimeoutException()); - - // then - assertThatThrownBy(() -> foodTestService.findFood(foodId)) - .isInstanceOf(RuntimeException.class) - .hasMessage("현재 너무 많은 요청을 처리하고 있습니다. 다시 시도해주세요"); - } - - private Food createFood(Long foodId) { - return Food.builder() - .id(foodId) - .build(); - } - - @RequiredArgsConstructor - static class FoodTestService { - private final FoodRepository foodRepository; - - @RetryOnLockFailure - public Optional findFood(Long foodId) { - return foodRepository.findFoodByFoodIdWithPessimisticLock(foodId); - } - } -} \ No newline at end of file +//package com.f_lab.joyeuse_planete.foods.aspect; +// +//import com.f_lab.joyeuse_planete.core.aspect.RetryOnLockFailure; +//import com.f_lab.joyeuse_planete.core.domain.Food; +//import com.f_lab.joyeuse_planete.foods.repository.FoodRepository; +//import jakarta.persistence.LockTimeoutException; +//import jakarta.persistence.PessimisticLockException; +//import lombok.RequiredArgsConstructor; +//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.boot.test.context.TestConfiguration; +//import org.springframework.context.annotation.Bean; +//import org.springframework.test.context.bean.override.mockito.MockitoBean; +// +//import java.util.Optional; +// +//import static org.assertj.core.api.Assertions.assertThatThrownBy; +//import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; +//import static org.mockito.ArgumentMatchers.anyLong; +//import static org.mockito.Mockito.times; +//import static org.mockito.Mockito.verify; +//import static org.mockito.Mockito.when; +// +//@SpringBootTest +//class LockRetryAspectTest { +// +// @MockitoBean +// private FoodRepository foodRepository; +// +// @Autowired +// private FoodTestService foodTestService; +// +// @TestConfiguration +// static class LockRetryAspectTestConfig { +// @Bean +// public FoodTestService foodTestService(FoodRepository foodRepository) { +// return new FoodTestService(foodRepository); +// } +// } +// +// @Test +// @DisplayName("락 없이 첫 시도에 성공") +// void test_find_food_lock_and_retry_success() { +// // given +// Long foodId = 1L; +// Food expectedFood = createFood(foodId); +// +// // when +// when(foodRepository.findFoodByFoodIdWithPessimisticLock(foodId)) +// .thenReturn(Optional.ofNullable(expectedFood)); +// +// Food foundFood = foodTestService.findFood(foodId).get(); +// +// // then +// assertThat(foundFood.getId()).isEqualTo(foodId); +// } +// +// @Test +// @DisplayName("첫 번째 시도는 실패하고 두 번째 시도에 성공") +// void test_find_food_lock_and_retry_fail_on_first_then_success() { +// // given +// Long foodId = 1L; +// Food expectedFood = createFood(foodId); +// +// // when +// when(foodRepository.findFoodByFoodIdWithPessimisticLock(anyLong())) +// .thenThrow(new PessimisticLockException()) +// .thenReturn(Optional.ofNullable(expectedFood)); +// +// Food foundFood = foodTestService.findFood(foodId).get(); +// +// // then +// assertThat(foundFood.getId()).isEqualTo(foodId); +// verify(foodRepository, times(2)).findFoodByFoodIdWithPessimisticLock(foodId); +// } +// +// @Test +// @DisplayName("락 타임아웃으로 최대 재시도 후 실패") +// void test_find_food_lock_and_retry_fail() { +// // given +// Long foodId = 1L; +// +// // when +// when(foodRepository.findFoodByFoodIdWithPessimisticLock(anyLong())) +// .thenThrow(new PessimisticLockException()) +// .thenThrow(new LockTimeoutException()) +// .thenThrow(new LockTimeoutException()); +// +// // then +// assertThatThrownBy(() -> foodTestService.findFood(foodId)) +// .isInstanceOf(RuntimeException.class) +// .hasMessage("현재 너무 많은 요청을 처리하고 있습니다. 다시 시도해주세요"); +// } +// +// private Food createFood(Long foodId) { +// return Food.builder() +// .id(foodId) +// .build(); +// } +// +// @RequiredArgsConstructor +// static class FoodTestService { +// private final FoodRepository foodRepository; +// +// @RetryOnLockFailure +// public Optional findFood(Long foodId) { +// return foodRepository.findFoodByFoodIdWithPessimisticLock(foodId); +// } +// } +//} \ No newline at end of file diff --git a/payment/src/main/java/com/f_lab/joyeuse_planete/payment/service/handler/PaymentProcessHandler.java b/payment/src/main/java/com/f_lab/joyeuse_planete/payment/service/handler/PaymentProcessHandler.java deleted file mode 100644 index 9db2d48..0000000 --- a/payment/src/main/java/com/f_lab/joyeuse_planete/payment/service/handler/PaymentProcessHandler.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.f_lab.joyeuse_planete.payment.service.handler; - - -import com.f_lab.joyeuse_planete.core.commands.PaymentProcessCommand; -import com.f_lab.joyeuse_planete.payment.service.PaymentService; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.kafka.annotation.KafkaHandler; -import org.springframework.kafka.annotation.KafkaListener; -import org.springframework.messaging.handler.annotation.Payload; -import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Transactional; - -@Slf4j -@Component -@Transactional -@RequiredArgsConstructor -@KafkaListener( - topics = { "${payments.commands.topic.name}" }, - groupId = "${spring.kafka.consumer.group-id}" -) -public class PaymentProcessHandler { - - private final PaymentService paymentService; - - @KafkaHandler - public void handlePaymentProcessCommand(@Payload PaymentProcessCommand paymentProcessCommand) { - log.info("PaymentProcessCommand={}", paymentProcessCommand); - - try { - - } catch (Exception e) { - - } - } -} From 278405ac11133e3d5601f0dd190edfd071cc2190 Mon Sep 17 00:00:00 2001 From: koreanMike513 Date: Wed, 29 Jan 2025 16:55:51 +0000 Subject: [PATCH 10/24] refactor: Refactored LogUtil class --- .../core/aspect/LockRetryAspect.java | 7 +++---- .../core/kafka/aspect/KafkaRetryAspect.java | 9 +++++---- .../joyeuse_planete/core/kafka/util/LogUtil.java | 15 --------------- .../joyeuse_planete/core/util/log/LogUtil.java | 14 ++++++++++++++ .../service/handler/OrderCreatedEventHandler.java | 5 +++-- 5 files changed, 25 insertions(+), 25 deletions(-) delete mode 100644 core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/util/LogUtil.java create mode 100644 core/src/main/java/com/f_lab/joyeuse_planete/core/util/log/LogUtil.java diff --git a/core/src/main/java/com/f_lab/joyeuse_planete/core/aspect/LockRetryAspect.java b/core/src/main/java/com/f_lab/joyeuse_planete/core/aspect/LockRetryAspect.java index 7319c12..fc90c1c 100644 --- a/core/src/main/java/com/f_lab/joyeuse_planete/core/aspect/LockRetryAspect.java +++ b/core/src/main/java/com/f_lab/joyeuse_planete/core/aspect/LockRetryAspect.java @@ -2,6 +2,7 @@ import com.f_lab.joyeuse_planete.core.exceptions.JoyeusePlaneteApplicationException; import com.f_lab.joyeuse_planete.core.exceptions.ErrorCode; +import com.f_lab.joyeuse_planete.core.util.log.LogUtil; import com.f_lab.joyeuse_planete.core.util.time.TimeConstantsString; import jakarta.persistence.LockTimeoutException; import jakarta.persistence.PessimisticLockException; @@ -30,9 +31,7 @@ public Object lockRetry(ProceedingJoinPoint joinPoint) { try { return joinPoint.proceed(); } catch (PessimisticLockException | LockTimeoutException e) { - attempts++; - log.warn("시도 횟수={}, 다시 메서드={} 락을 얻기를 시도합니다", - attempts, joinPoint.getSignature()); + LogUtil.retry(++attempts, joinPoint.getSignature().toString()); // 재시도 전 잠시 멈추고 다시 시작 // 각 시도 마다 WAIT_INTERVAL 이 MULTIPLIER 에 상응하는 값을 지수적으로 늘어납니다 (backoff) @@ -48,7 +47,7 @@ public Object lockRetry(ProceedingJoinPoint joinPoint) { } } catch (Throwable e) { - log.error("예상치 못한 오류가 발생하였습니다. 다시 시도해 주세요", e); + LogUtil.exception(joinPoint.getSignature().toString(), e); throw new JoyeusePlaneteApplicationException(ErrorCode.LOCK_ACQUISITION_FAIL_EXCEPTION, e); } } diff --git a/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/aspect/KafkaRetryAspect.java b/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/aspect/KafkaRetryAspect.java index 72696a0..233b7a9 100644 --- a/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/aspect/KafkaRetryAspect.java +++ b/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/aspect/KafkaRetryAspect.java @@ -2,6 +2,7 @@ import com.f_lab.joyeuse_planete.core.exceptions.JoyeusePlaneteApplicationException; import com.f_lab.joyeuse_planete.core.exceptions.ErrorCode; +import com.f_lab.joyeuse_planete.core.util.log.LogUtil; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; @@ -20,18 +21,18 @@ public class KafkaRetryAspect { @Around("@annotation(com.f_lab.joyeuse_planete.core.kafka.aspect.KafkaRetry)") public Object kafkaRetry(ProceedingJoinPoint joinPoint) { - int attempt = 0; + int attempts = 0; - while(attempt < MAX_RETRY) { + while (attempts < MAX_RETRY) { try { return joinPoint.proceed(); } catch (Throwable e) { - log.warn("시도 횟수={}, 메서드={}", attempt, joinPoint.getSignature()); - attempt++; + LogUtil.retry(++attempts, joinPoint.getSignature().toString()); try { Thread.sleep(STOP_INTERVAL); } catch (InterruptedException ex) { + LogUtil.exception(joinPoint.getSignature().toString(), ex); throw new JoyeusePlaneteApplicationException(ErrorCode.KAFKA_RETRY_FAIL_EXCEPTION, ex); } } diff --git a/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/util/LogUtil.java b/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/util/LogUtil.java deleted file mode 100644 index 473fdd5..0000000 --- a/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/util/LogUtil.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.f_lab.joyeuse_planete.core.kafka.util; - -import lombok.extern.slf4j.Slf4j; - -@Slf4j -public class LogUtil { - - public static void logException(Exception e) { - - } - - public static void logRetryException() { - - } -} diff --git a/core/src/main/java/com/f_lab/joyeuse_planete/core/util/log/LogUtil.java b/core/src/main/java/com/f_lab/joyeuse_planete/core/util/log/LogUtil.java new file mode 100644 index 0000000..4037086 --- /dev/null +++ b/core/src/main/java/com/f_lab/joyeuse_planete/core/util/log/LogUtil.java @@ -0,0 +1,14 @@ +package com.f_lab.joyeuse_planete.core.util.log; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class LogUtil { + public static void exception(String method, Throwable e) { + log.error("오류가 발생하였습니다. method = {} message = {}", method, e.getMessage(), e); + } + + public static void retry(int attempts, String method) { + log.warn("시도 횟수={}, 다시 메서드={} 락을 얻기를 시도합니다", attempts, method); + } +} diff --git a/foods/src/main/java/com/f_lab/joyeuse_planete/foods/service/handler/OrderCreatedEventHandler.java b/foods/src/main/java/com/f_lab/joyeuse_planete/foods/service/handler/OrderCreatedEventHandler.java index 1c3622d..3520a17 100644 --- a/foods/src/main/java/com/f_lab/joyeuse_planete/foods/service/handler/OrderCreatedEventHandler.java +++ b/foods/src/main/java/com/f_lab/joyeuse_planete/foods/service/handler/OrderCreatedEventHandler.java @@ -4,6 +4,7 @@ import com.f_lab.joyeuse_planete.core.events.OrderCreatedEvent; import com.f_lab.joyeuse_planete.core.events.OrderCreationFailedEvent; import com.f_lab.joyeuse_planete.core.kafka.service.KafkaService; +import com.f_lab.joyeuse_planete.core.util.log.LogUtil; import com.f_lab.joyeuse_planete.foods.service.FoodService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -40,7 +41,7 @@ public void reserveFoodAfterOrderCreatedEvent(@Payload OrderCreatedEvent orderCr try { foodService.reserve(orderCreatedEvent.getFoodId(), orderCreatedEvent.getQuantity()); } catch(Exception e) { - log.error("오류가 발생하였습니다. message = {}", e.getMessage(), e); + LogUtil.exception("OrderCreatedEventHandler.reserveFoodAfterOrderCreatedEvent", e); kafkaService.sendKafkaEvent(foodProcessFailEvent, OrderCreationFailedEvent.toEvent(orderCreatedEvent)); throw e; @@ -53,7 +54,7 @@ private void sendKafkaOrderCreatedEvent(OrderCreatedEvent orderCreatedEvent) { try { kafkaService.sendKafkaEvent(foodReservationEvent, orderCreatedEvent); } catch (Exception e) { - log.error("오류가 발생하였습니다. message = {}", e.getMessage(), e); + LogUtil.exception("OrderCreatedEventHandler.sendKafkaOrderCreatedEvent", e); kafkaService.sendKafkaEvent(foodProcessFailEvent, OrderCreationFailedEvent.toEvent(orderCreatedEvent)); throw e; From 95a1fb109bf8a8f1758d7ff18fd3bb1f3062d6ef Mon Sep 17 00:00:00 2001 From: koreanMike513 Date: Wed, 29 Jan 2025 16:59:28 +0000 Subject: [PATCH 11/24] refactor: Applied LogUtil --- .../f_lab/joyeuse_planete/orders/service/OrderService.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/orders/src/main/java/com/f_lab/joyeuse_planete/orders/service/OrderService.java b/orders/src/main/java/com/f_lab/joyeuse_planete/orders/service/OrderService.java index 40e86dc..74e9974 100644 --- a/orders/src/main/java/com/f_lab/joyeuse_planete/orders/service/OrderService.java +++ b/orders/src/main/java/com/f_lab/joyeuse_planete/orders/service/OrderService.java @@ -4,6 +4,7 @@ import com.f_lab.joyeuse_planete.core.domain.Order; import com.f_lab.joyeuse_planete.core.kafka.service.KafkaService; +import com.f_lab.joyeuse_planete.core.util.log.LogUtil; import com.f_lab.joyeuse_planete.orders.domain.OrderSearchCondition; import com.f_lab.joyeuse_planete.orders.dto.response.OrderDTO; import com.f_lab.joyeuse_planete.orders.dto.request.OrderCreateRequestDTO; @@ -39,8 +40,8 @@ public OrderCreateResponseDTO createFoodOrder(OrderCreateRequestDTO request) { try { orderRepository.save(order); } catch (Exception e) { - log.error("오류가 발생하였습니다. message = {}", e.getMessage(), e); - + LogUtil.exception("OrderService.createFoodOrder", e); + throw e; } sendKafkaOrderCreatedEvent(request, order); From de7af1172b03439ade76dd28ca72f175dcd1906a Mon Sep 17 00:00:00 2001 From: koreanMike513 Date: Wed, 29 Jan 2025 18:14:23 +0000 Subject: [PATCH 12/24] refactor: Updated Foods Kafka DeadLetter Handler --- .../core/exceptions/ErrorCode.java | 2 +- .../kafka/config/KafkaConsumerConfig.java | 16 ++++++---- .../core/kafka/util/ExceptionUtil.java | 4 +-- .../OrderCreatedDeadLetterTopicHandler.java | 30 ++++++++++++++----- 4 files changed, 36 insertions(+), 16 deletions(-) diff --git a/core/src/main/java/com/f_lab/joyeuse_planete/core/exceptions/ErrorCode.java b/core/src/main/java/com/f_lab/joyeuse_planete/core/exceptions/ErrorCode.java index e2dff59..17355ff 100644 --- a/core/src/main/java/com/f_lab/joyeuse_planete/core/exceptions/ErrorCode.java +++ b/core/src/main/java/com/f_lab/joyeuse_planete/core/exceptions/ErrorCode.java @@ -16,7 +16,7 @@ public enum ErrorCode { // KAFKA KAFKA_RETRY_FAIL_EXCEPTION("오류 발생! 잠시 후 다시 시도해주세요.", 503), - + KAFKA_DEAD_LETTER_TOPIC_FAIL_EXCEPTION("데드레터토픽을 다루는데 실패하였습니다.", 500), ; private String description; diff --git a/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/config/KafkaConsumerConfig.java b/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/config/KafkaConsumerConfig.java index 291bd71..1957dff 100644 --- a/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/config/KafkaConsumerConfig.java +++ b/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/config/KafkaConsumerConfig.java @@ -4,6 +4,7 @@ import com.f_lab.joyeuse_planete.core.kafka.exceptions.NonRetryableException; import com.f_lab.joyeuse_planete.core.kafka.exceptions.RetryableException; import com.f_lab.joyeuse_planete.core.kafka.util.ExceptionUtil; +import com.f_lab.joyeuse_planete.core.util.log.LogUtil; import lombok.extern.slf4j.Slf4j; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.common.TopicPartition; @@ -17,8 +18,6 @@ import org.springframework.kafka.support.KafkaHeaders; import org.springframework.util.backoff.BackOff; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; import java.util.Map; import java.util.function.BiFunction; @@ -40,6 +39,9 @@ public abstract class KafkaConsumerConfig { @Value("${spring.kafka.consumer.isolation-level:read_committed}") protected String ISOLATION_LEVEL; + @Value("${retry.attempts:5}") + private int RETRY_ATTEMPTS; + abstract public ConcurrentKafkaListenerContainerFactory kafkaListenerContainerFactory(); abstract protected Map consumerConfig(); abstract protected String deadLetterTopicName(); @@ -49,8 +51,12 @@ public abstract class KafkaConsumerConfig { protected BiFunction, Exception, TopicPartition> defaultDeadLetterTopicStrategy(String deadLetterTopic) { return (record, ex) -> { ex = ExceptionUtil.unwrap(ex); - record.headers().add(KafkaHeaders.EXCEPTION_MESSAGE, ex.getMessage().getBytes(StandardCharsets.UTF_8)); - record.headers().add(KafkaHeaders.ORIGINAL_TOPIC, record.topic().getBytes(StandardCharsets.UTF_8)); + + LogUtil.exception("KafkaConsumerConfig.defaultDeadLetterTopicStrategy", ex); + + record.headers().add(KafkaHeaders.EXCEPTION_FQCN, ex.getClass().getName().getBytes()); + record.headers().add(KafkaHeaders.EXCEPTION_MESSAGE, ex.getMessage().getBytes()); + record.headers().add(KafkaHeaders.ORIGINAL_TOPIC, record.topic().getBytes()); return new TopicPartition(deadLetterTopic, -1); }; @@ -61,7 +67,7 @@ protected DeadLetterPublishingRecoverer deadLetterPublishingRecoverer() { } public BackOff defaultBackOffStrategy() { - return new ExponentialBackOffWithMaxRetries(5); + return new ExponentialBackOffWithMaxRetries(RETRY_ATTEMPTS); } public DefaultErrorHandler defaultErrorHandler() { diff --git a/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/util/ExceptionUtil.java b/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/util/ExceptionUtil.java index 454fe2d..6e0bd2b 100644 --- a/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/util/ExceptionUtil.java +++ b/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/util/ExceptionUtil.java @@ -23,7 +23,7 @@ public static Exception unwrap(Exception e) { return e; } - public static boolean checkRequeue(String message) { - return !nonRequeueList.contains(message); + public static boolean noRequeue(String message) { + return nonRequeueList.contains(message); } } diff --git a/foods/src/main/java/com/f_lab/joyeuse_planete/foods/service/handler/OrderCreatedDeadLetterTopicHandler.java b/foods/src/main/java/com/f_lab/joyeuse_planete/foods/service/handler/OrderCreatedDeadLetterTopicHandler.java index 71305c7..3f9c42f 100644 --- a/foods/src/main/java/com/f_lab/joyeuse_planete/foods/service/handler/OrderCreatedDeadLetterTopicHandler.java +++ b/foods/src/main/java/com/f_lab/joyeuse_planete/foods/service/handler/OrderCreatedDeadLetterTopicHandler.java @@ -2,7 +2,8 @@ import com.f_lab.joyeuse_planete.core.events.OrderCreatedEvent; import com.f_lab.joyeuse_planete.core.events.OrderCreationFailedEvent; -import com.f_lab.joyeuse_planete.core.kafka.exceptions.NonRetryableException; +import com.f_lab.joyeuse_planete.core.exceptions.ErrorCode; +import com.f_lab.joyeuse_planete.core.exceptions.JoyeusePlaneteApplicationException; import com.f_lab.joyeuse_planete.core.kafka.exceptions.RetryableException; import com.f_lab.joyeuse_planete.core.kafka.service.KafkaService; import com.f_lab.joyeuse_planete.core.kafka.util.ExceptionUtil; @@ -29,25 +30,38 @@ public class OrderCreatedDeadLetterTopicHandler { private final KafkaService kafkaService; @KafkaHandler - public void processDeadLetterTopic( - @Payload OrderCreatedEvent orderCreatedEvent, - @Header(value = KafkaHeaders.EXCEPTION_MESSAGE, required = false) String exceptionMessage, - @Header(value = KafkaHeaders.ORIGINAL_TOPIC, required = false) String originalTopic) { + public void processDeadOrderCreatedEvent(@Payload OrderCreatedEvent orderCreatedEvent, + @Header(value = KafkaHeaders.EXCEPTION_FQCN, required = false) String exceptionName, + @Header(value = KafkaHeaders.EXCEPTION_MESSAGE, required = false) String exceptionMessage, + @Header(value = KafkaHeaders.ORIGINAL_TOPIC, required = false) String originalTopic) { - log.info("PAYLOAD = {} MESSAGE = {}, TOPIC = {}", orderCreatedEvent, exceptionMessage, originalTopic); + // TODO: THINK ABOUT THE LOGICS; + } + @KafkaHandler + public void processDeadOrderCreationFailedEvent(@Payload OrderCreationFailedEvent orderCreationFailedEvent, + @Header(value = KafkaHeaders.EXCEPTION_FQCN, required = false) String exceptionName, + @Header(value = KafkaHeaders.EXCEPTION_MESSAGE, required = false) String exceptionMessage, + @Header(value = KafkaHeaders.ORIGINAL_TOPIC, required = false) String originalTopic + ) { if (Objects.isNull(exceptionMessage) || Objects.isNull(originalTopic) || - !ExceptionUtil.checkRequeue(exceptionMessage) + ExceptionUtil.noRequeue(exceptionMessage) ) { return; } + //TODO: 추후에 exponential 하게 구현할 수 있음 try { Thread.sleep(Integer.parseInt(FIVE_SECONDS)); - kafkaService.sendKafkaEvent(originalTopic, orderCreatedEvent); } catch (InterruptedException e) { throw new RetryableException(); } + + try { + kafkaService.sendKafkaEvent(originalTopic, orderCreationFailedEvent); + } catch(Exception e) { + throw new JoyeusePlaneteApplicationException(ErrorCode.KAFKA_DEAD_LETTER_TOPIC_FAIL_EXCEPTION); + } } } From 53e8648df3ec936b30eb1dc3be4f49a960d6efa3 Mon Sep 17 00:00:00 2001 From: koreanMike513 Date: Wed, 29 Jan 2025 22:42:03 +0000 Subject: [PATCH 13/24] update: Updated Food entity and Errorcodes --- .../java/com/f_lab/joyeuse_planete/core/domain/Food.java | 7 +++++++ .../com/f_lab/joyeuse_planete/core/domain/OrderStatus.java | 1 + .../core/events/OrderCreationFailedEvent.java | 3 +++ .../f_lab/joyeuse_planete/core/exceptions/ErrorCode.java | 2 ++ 4 files changed, 13 insertions(+) diff --git a/core/src/main/java/com/f_lab/joyeuse_planete/core/domain/Food.java b/core/src/main/java/com/f_lab/joyeuse_planete/core/domain/Food.java index 09a87d8..c49b3b6 100644 --- a/core/src/main/java/com/f_lab/joyeuse_planete/core/domain/Food.java +++ b/core/src/main/java/com/f_lab/joyeuse_planete/core/domain/Food.java @@ -57,4 +57,11 @@ public void minusQuantity(int quantity) { totalQuantity -= quantity; } + + public void plusQuantity(int quantity) { + if (totalQuantity > Integer.MAX_VALUE - quantity) + throw new JoyeusePlaneteApplicationException(ErrorCode.FOOD_QUANTITY_OVERFLOW); + + totalQuantity += quantity; + } } diff --git a/core/src/main/java/com/f_lab/joyeuse_planete/core/domain/OrderStatus.java b/core/src/main/java/com/f_lab/joyeuse_planete/core/domain/OrderStatus.java index 8c940ce..72e4f32 100644 --- a/core/src/main/java/com/f_lab/joyeuse_planete/core/domain/OrderStatus.java +++ b/core/src/main/java/com/f_lab/joyeuse_planete/core/domain/OrderStatus.java @@ -11,6 +11,7 @@ public enum OrderStatus { MEMBER_CANCELED("승인된 결제가 사용자에 의해서 취소된 상태입니다."), STORE_CANCELED("승인된 결제가 가게에 의해서 취소된 상태입니다."), EXPIRED("주문의 유효 시간 5분이 지나 거래가 취소된 상태입니다."), + FAIL("주문이 실패하였습니다.") ; private final String description; diff --git a/core/src/main/java/com/f_lab/joyeuse_planete/core/events/OrderCreationFailedEvent.java b/core/src/main/java/com/f_lab/joyeuse_planete/core/events/OrderCreationFailedEvent.java index ccc3eee..9f8fca7 100644 --- a/core/src/main/java/com/f_lab/joyeuse_planete/core/events/OrderCreationFailedEvent.java +++ b/core/src/main/java/com/f_lab/joyeuse_planete/core/events/OrderCreationFailedEvent.java @@ -12,6 +12,8 @@ @AllArgsConstructor public class OrderCreationFailedEvent { + private Long orderId; + private Long foodId; private Long storeId; @@ -20,6 +22,7 @@ public class OrderCreationFailedEvent { public static OrderCreationFailedEvent toEvent(OrderCreatedEvent orderCreatedEvent) { return OrderCreationFailedEvent.builder() + .orderId(orderCreatedEvent.getOrderId()) .foodId(orderCreatedEvent.getFoodId()) .storeId(orderCreatedEvent.getStoreId()) .quantity(orderCreatedEvent.getQuantity()) diff --git a/core/src/main/java/com/f_lab/joyeuse_planete/core/exceptions/ErrorCode.java b/core/src/main/java/com/f_lab/joyeuse_planete/core/exceptions/ErrorCode.java index 17355ff..52ce70b 100644 --- a/core/src/main/java/com/f_lab/joyeuse_planete/core/exceptions/ErrorCode.java +++ b/core/src/main/java/com/f_lab/joyeuse_planete/core/exceptions/ErrorCode.java @@ -10,6 +10,8 @@ public enum ErrorCode { FOOD_NOT_EXIST_EXCEPTION("상품이 존재하지 않습니다.", 400), FOOD_NOT_ENOUGH_STOCK("상품의 수량이 부족합니다", 409), + FOOD_QUANTITY_OVERFLOW("상품의 수량이 최대 값을 넘었습니다.", 406), + ORDER_NOT_EXIST_EXCEPTION("존재하지 않는 주문입니다.", 406), // LOCK LOCK_ACQUISITION_FAIL_EXCEPTION("현재 너무 많은 요청을 처리하고 있습니다. 다시 시도해주세요 (락)",503), From 9a6fb50ab7cf2b307d66a885c9841a32f223693f Mon Sep 17 00:00:00 2001 From: koreanMike513 Date: Wed, 29 Jan 2025 22:46:29 +0000 Subject: [PATCH 14/24] update: Updated Kafka Retry AOP --- .../core/kafka/aspect/KafkaRetryAspect.java | 7 ++++++- .../core/kafka/config/KafkaConsumerConfig.java | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/aspect/KafkaRetryAspect.java b/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/aspect/KafkaRetryAspect.java index 233b7a9..347d278 100644 --- a/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/aspect/KafkaRetryAspect.java +++ b/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/aspect/KafkaRetryAspect.java @@ -4,6 +4,7 @@ import com.f_lab.joyeuse_planete.core.exceptions.ErrorCode; import com.f_lab.joyeuse_planete.core.util.log.LogUtil; import lombok.extern.slf4j.Slf4j; +import org.apache.kafka.common.KafkaException; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; @@ -26,7 +27,7 @@ public Object kafkaRetry(ProceedingJoinPoint joinPoint) { while (attempts < MAX_RETRY) { try { return joinPoint.proceed(); - } catch (Throwable e) { + } catch (KafkaException e) { LogUtil.retry(++attempts, joinPoint.getSignature().toString()); try { @@ -35,9 +36,13 @@ public Object kafkaRetry(ProceedingJoinPoint joinPoint) { LogUtil.exception(joinPoint.getSignature().toString(), ex); throw new JoyeusePlaneteApplicationException(ErrorCode.KAFKA_RETRY_FAIL_EXCEPTION, ex); } + } catch (Throwable e) { + LogUtil.exception(joinPoint.getSignature().toString(), e); + throw new JoyeusePlaneteApplicationException(ErrorCode.KAFKA_RETRY_FAIL_EXCEPTION, e); } } throw new JoyeusePlaneteApplicationException(ErrorCode.KAFKA_RETRY_FAIL_EXCEPTION); } + } diff --git a/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/config/KafkaConsumerConfig.java b/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/config/KafkaConsumerConfig.java index 1957dff..5e45b55 100644 --- a/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/config/KafkaConsumerConfig.java +++ b/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/config/KafkaConsumerConfig.java @@ -55,7 +55,7 @@ public abstract class KafkaConsumerConfig { LogUtil.exception("KafkaConsumerConfig.defaultDeadLetterTopicStrategy", ex); record.headers().add(KafkaHeaders.EXCEPTION_FQCN, ex.getClass().getName().getBytes()); - record.headers().add(KafkaHeaders.EXCEPTION_MESSAGE, ex.getMessage().getBytes()); + record.headers().add(KafkaHeaders.EXCEPTION_MESSAGE, ((ex.getMessage() != null) ? ex.getMessage() : "null").getBytes()); record.headers().add(KafkaHeaders.ORIGINAL_TOPIC, record.topic().getBytes()); return new TopicPartition(deadLetterTopic, -1); From 3cb89d95f6ca76087076faa49021797952aa2e7c Mon Sep 17 00:00:00 2001 From: koreanMike513 Date: Wed, 29 Jan 2025 22:53:55 +0000 Subject: [PATCH 15/24] feat: Added foods project event logics --- .../foods/service/FoodService.java | 7 +++ .../OrderCreatedDeadLetterTopicHandler.java | 2 +- .../handler/OrderCreatedEventHandler.java | 4 +- .../OrderCreationFailEventHandler.java | 50 +++++++++++++++++++ foods/src/main/resources/application.yml | 12 ++--- 5 files changed, 65 insertions(+), 10 deletions(-) create mode 100644 foods/src/main/java/com/f_lab/joyeuse_planete/foods/service/handler/OrderCreationFailEventHandler.java diff --git a/foods/src/main/java/com/f_lab/joyeuse_planete/foods/service/FoodService.java b/foods/src/main/java/com/f_lab/joyeuse_planete/foods/service/FoodService.java index 3546200..8da198b 100644 --- a/foods/src/main/java/com/f_lab/joyeuse_planete/foods/service/FoodService.java +++ b/foods/src/main/java/com/f_lab/joyeuse_planete/foods/service/FoodService.java @@ -25,6 +25,13 @@ public void reserve(Long foodId, int quantity) { foodRepository.save(food); } + @Transactional + public void release(Long foodId, int quantity) { + Food food = findFoodWithLock(foodId); + food.plusQuantity(quantity); + foodRepository.save(food); + } + private Food findFoodWithLock(Long foodId) { return foodRepository.findFoodByFoodIdWithPessimisticLock(foodId) .orElseThrow(() -> new JoyeusePlaneteApplicationException(ErrorCode.FOOD_NOT_EXIST_EXCEPTION)); diff --git a/foods/src/main/java/com/f_lab/joyeuse_planete/foods/service/handler/OrderCreatedDeadLetterTopicHandler.java b/foods/src/main/java/com/f_lab/joyeuse_planete/foods/service/handler/OrderCreatedDeadLetterTopicHandler.java index 3f9c42f..0f1b75f 100644 --- a/foods/src/main/java/com/f_lab/joyeuse_planete/foods/service/handler/OrderCreatedDeadLetterTopicHandler.java +++ b/foods/src/main/java/com/f_lab/joyeuse_planete/foods/service/handler/OrderCreatedDeadLetterTopicHandler.java @@ -64,4 +64,4 @@ public void processDeadOrderCreationFailedEvent(@Payload OrderCreationFailedEven throw new JoyeusePlaneteApplicationException(ErrorCode.KAFKA_DEAD_LETTER_TOPIC_FAIL_EXCEPTION); } } -} +} \ No newline at end of file diff --git a/foods/src/main/java/com/f_lab/joyeuse_planete/foods/service/handler/OrderCreatedEventHandler.java b/foods/src/main/java/com/f_lab/joyeuse_planete/foods/service/handler/OrderCreatedEventHandler.java index 3520a17..a069b91 100644 --- a/foods/src/main/java/com/f_lab/joyeuse_planete/foods/service/handler/OrderCreatedEventHandler.java +++ b/foods/src/main/java/com/f_lab/joyeuse_planete/foods/service/handler/OrderCreatedEventHandler.java @@ -7,7 +7,6 @@ import com.f_lab.joyeuse_planete.core.util.log.LogUtil; import com.f_lab.joyeuse_planete.foods.service.FoodService; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.kafka.annotation.KafkaHandler; import org.springframework.kafka.annotation.KafkaListener; @@ -16,9 +15,9 @@ import org.springframework.transaction.annotation.Transactional; -@Slf4j @Component @RequiredArgsConstructor +@Transactional("transactionManager") @KafkaListener( topics = { "${orders.events.topic.name}" }, groupId = "${spring.kafka.consumer.group-id}" @@ -36,7 +35,6 @@ public class OrderCreatedEventHandler { private String foodProcessFailEvent; @KafkaHandler - @Transactional("transactionManager") public void reserveFoodAfterOrderCreatedEvent(@Payload OrderCreatedEvent orderCreatedEvent) { try { foodService.reserve(orderCreatedEvent.getFoodId(), orderCreatedEvent.getQuantity()); diff --git a/foods/src/main/java/com/f_lab/joyeuse_planete/foods/service/handler/OrderCreationFailEventHandler.java b/foods/src/main/java/com/f_lab/joyeuse_planete/foods/service/handler/OrderCreationFailEventHandler.java new file mode 100644 index 0000000..f56f06b --- /dev/null +++ b/foods/src/main/java/com/f_lab/joyeuse_planete/foods/service/handler/OrderCreationFailEventHandler.java @@ -0,0 +1,50 @@ +package com.f_lab.joyeuse_planete.foods.service.handler; + +import com.f_lab.joyeuse_planete.core.events.OrderCreationFailedEvent; +import com.f_lab.joyeuse_planete.core.kafka.service.KafkaService; +import com.f_lab.joyeuse_planete.core.util.log.LogUtil; +import com.f_lab.joyeuse_planete.foods.service.FoodService; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.kafka.annotation.KafkaHandler; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.messaging.handler.annotation.Payload; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Component +@RequiredArgsConstructor +@Transactional("transactionManager") +@KafkaListener( + topics = { "${payments.events.topic.fail}" }, + groupId = "${spring.kafka.consumer.group-id}" +) +public class OrderCreationFailEventHandler { + + private final FoodService foodService; + private final KafkaService kafkaService; + + @Value("${foods.events.topic.fail}") + private String foodProcessFailEvent; + + @KafkaHandler + public void releaseFoodReservation(@Payload OrderCreationFailedEvent orderCreationFailedEvent) { + try { + foodService.release(orderCreationFailedEvent.getFoodId(), orderCreationFailedEvent.getQuantity()); + } catch (Exception e) { + LogUtil.exception("OrderCreationFailEventHandler.releaseFoodReservation", e); + throw e; + } + + sendKafkaOrderCreationFailEvent(orderCreationFailedEvent); + } + + private void sendKafkaOrderCreationFailEvent(OrderCreationFailedEvent orderCreationFailedEvent) { + try { + kafkaService.sendKafkaEvent(foodProcessFailEvent, orderCreationFailedEvent); + } catch (Exception e) { + LogUtil.exception("OrderCreatedEventHandler.sendKafkaOrderCreatedEvent", e); + throw e; + } + } +} diff --git a/foods/src/main/resources/application.yml b/foods/src/main/resources/application.yml index bd34d89..3fef246 100644 --- a/foods/src/main/resources/application.yml +++ b/foods/src/main/resources/application.yml @@ -26,11 +26,16 @@ foods: events: topic: name: foods.order-created-event - fail: foods.foods-reservation-fail-event + fail: foods.order-creation-failed-event dead-letter-topic: name: foods.dead-letter-topic +payments: + events: + topic: + fail: payments.order-creation-failed-event + kafka: topic: partitions: 3 @@ -38,11 +43,6 @@ kafka: container: concurrency: 3 -non-retryable-errors: - - "상품이 존재하지 않습니다." - - "상품의 수량이 부족합니다" - - "현재 너무 많은 요청을 처리하고 있습니다. 다시 시도해주세요 (락)" - logging: level: org.hibernate.sql: TRACE From 4040d456291ad8fafc2a17f861eaf8da30206f9a Mon Sep 17 00:00:00 2001 From: koreanMike513 Date: Wed, 29 Jan 2025 22:57:53 +0000 Subject: [PATCH 16/24] feat: Added payment project --- .../payment/config/KafkaConfig.java | 4 +- .../payment/service/PaymentService.java | 4 ++ .../handler/OrderCreatedEventHandler.java | 60 +++++++++++++++++ .../PaymentDeadLetterTopicHandler.java | 66 +++++++++++++++++++ payment/src/main/resources/application.yml | 7 +- 5 files changed, 138 insertions(+), 3 deletions(-) create mode 100644 payment/src/main/java/com/f_lab/joyeuse_planete/payment/service/handler/OrderCreatedEventHandler.java create mode 100644 payment/src/main/java/com/f_lab/joyeuse_planete/payment/service/handler/PaymentDeadLetterTopicHandler.java diff --git a/payment/src/main/java/com/f_lab/joyeuse_planete/payment/config/KafkaConfig.java b/payment/src/main/java/com/f_lab/joyeuse_planete/payment/config/KafkaConfig.java index bb72a63..60dc8b9 100644 --- a/payment/src/main/java/com/f_lab/joyeuse_planete/payment/config/KafkaConfig.java +++ b/payment/src/main/java/com/f_lab/joyeuse_planete/payment/config/KafkaConfig.java @@ -47,7 +47,7 @@ public KafkaService kafkaService(KafkaTemplate kafkaTemplate) { } @Bean - public NewTopic foodsReservationFailEvent() { + public NewTopic paymentProcessedEvent() { return TopicBuilder .name(PAYMENT_PROCESSED_EVENT) .partitions(TOPIC_PARTITIONS) @@ -55,7 +55,7 @@ public NewTopic foodsReservationFailEvent() { } @Bean - public NewTopic foodsFoodsReservationProcessedEvent() { + public NewTopic paymentProcessingFailEvent() { return TopicBuilder .name(PAYMENT_PROCESS_FAILED_EVENT) .partitions(TOPIC_PARTITIONS) diff --git a/payment/src/main/java/com/f_lab/joyeuse_planete/payment/service/PaymentService.java b/payment/src/main/java/com/f_lab/joyeuse_planete/payment/service/PaymentService.java index b794705..cffaf78 100644 --- a/payment/src/main/java/com/f_lab/joyeuse_planete/payment/service/PaymentService.java +++ b/payment/src/main/java/com/f_lab/joyeuse_planete/payment/service/PaymentService.java @@ -6,4 +6,8 @@ @Service @RequiredArgsConstructor public class PaymentService { + + public void process() { + + } } diff --git a/payment/src/main/java/com/f_lab/joyeuse_planete/payment/service/handler/OrderCreatedEventHandler.java b/payment/src/main/java/com/f_lab/joyeuse_planete/payment/service/handler/OrderCreatedEventHandler.java new file mode 100644 index 0000000..b9ef4dc --- /dev/null +++ b/payment/src/main/java/com/f_lab/joyeuse_planete/payment/service/handler/OrderCreatedEventHandler.java @@ -0,0 +1,60 @@ +package com.f_lab.joyeuse_planete.payment.service.handler; + +import com.f_lab.joyeuse_planete.core.events.OrderCreatedEvent; +import com.f_lab.joyeuse_planete.core.events.OrderCreationFailedEvent; +import com.f_lab.joyeuse_planete.core.exceptions.JoyeusePlaneteApplicationException; +import com.f_lab.joyeuse_planete.core.kafka.service.KafkaService; +import com.f_lab.joyeuse_planete.core.util.log.LogUtil; +import com.f_lab.joyeuse_planete.payment.service.PaymentService; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.kafka.annotation.KafkaHandler; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.messaging.handler.annotation.Payload; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + + +@Component +@RequiredArgsConstructor +@Transactional("transactionManager") +@KafkaListener( + topics = { "${foods.events.topic.name}" }, + groupId = "${spring.kafka.consumer.group-id}" +) +public class OrderCreatedEventHandler { + + private final PaymentService paymentService; + private final KafkaService kafkaService; + + @Value("${payments.events.topic.name}") + private String paymentCompleteEvent; + + @Value("${payments.events.topic.fail}") + private String paymentProcessingFailEvent; + + @KafkaHandler + public void processPaymentAfterFoodProcessing(@Payload OrderCreatedEvent orderCreatedEvent) { + try { + paymentService.process(); + } catch (Exception e) { + LogUtil.exception("OrderCreatedEventHandler.processPaymentAfterFoodProcessing", e); + kafkaService.sendKafkaEvent(paymentProcessingFailEvent, OrderCreationFailedEvent.toEvent(orderCreatedEvent)); + + throw e; + } + + sendKafkaOrderCreationEvent(orderCreatedEvent); + } + + private void sendKafkaOrderCreationEvent(OrderCreatedEvent orderCreatedEvent) { + try { + kafkaService.sendKafkaEvent(paymentCompleteEvent, orderCreatedEvent); + } catch (Exception e) { + LogUtil.exception("OrderCreatedEventHandler.sendKafkaOrderCreationEvent", e); + kafkaService.sendKafkaEvent(paymentProcessingFailEvent, OrderCreationFailedEvent.toEvent(orderCreatedEvent)); + + throw e; + } + } +} diff --git a/payment/src/main/java/com/f_lab/joyeuse_planete/payment/service/handler/PaymentDeadLetterTopicHandler.java b/payment/src/main/java/com/f_lab/joyeuse_planete/payment/service/handler/PaymentDeadLetterTopicHandler.java new file mode 100644 index 0000000..1edbc6f --- /dev/null +++ b/payment/src/main/java/com/f_lab/joyeuse_planete/payment/service/handler/PaymentDeadLetterTopicHandler.java @@ -0,0 +1,66 @@ +package com.f_lab.joyeuse_planete.payment.service.handler; + +import com.f_lab.joyeuse_planete.core.events.OrderCreatedEvent; +import com.f_lab.joyeuse_planete.core.events.OrderCreationFailedEvent; +import com.f_lab.joyeuse_planete.core.exceptions.ErrorCode; +import com.f_lab.joyeuse_planete.core.exceptions.JoyeusePlaneteApplicationException; +import com.f_lab.joyeuse_planete.core.kafka.exceptions.RetryableException; +import com.f_lab.joyeuse_planete.core.kafka.service.KafkaService; +import com.f_lab.joyeuse_planete.core.kafka.util.ExceptionUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.kafka.annotation.KafkaHandler; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.kafka.support.KafkaHeaders; +import org.springframework.messaging.handler.annotation.Header; +import org.springframework.messaging.handler.annotation.Payload; +import org.springframework.stereotype.Component; + +import java.util.Objects; + +import static com.f_lab.joyeuse_planete.core.util.time.TimeConstantsString.FIVE_SECONDS; + +@Slf4j +@Component +@RequiredArgsConstructor +@KafkaListener(topics = "${payments.dead-letter-topic.name}", groupId = "${spring.kafka.consumer.group-id}") +public class PaymentDeadLetterTopicHandler { + + private final KafkaService kafkaService; + + @KafkaHandler + public void processDeadOrderCreatedEvent(@Payload OrderCreatedEvent orderCreatedEvent, + @Header(value = KafkaHeaders.EXCEPTION_FQCN, required = false) String exceptionName, + @Header(value = KafkaHeaders.EXCEPTION_MESSAGE, required = false) String exceptionMessage, + @Header(value = KafkaHeaders.ORIGINAL_TOPIC, required = false) String originalTopic) { + + // TODO: THINK ABOUT THE LOGICS; + } + + @KafkaHandler + public void processDeadOrderCreationFailedEvent(@Payload OrderCreationFailedEvent orderCreationFailedEvent, + @Header(value = KafkaHeaders.EXCEPTION_FQCN, required = false) String exceptionName, + @Header(value = KafkaHeaders.EXCEPTION_MESSAGE, required = false) String exceptionMessage, + @Header(value = KafkaHeaders.ORIGINAL_TOPIC, required = false) String originalTopic + ) { + if (Objects.isNull(exceptionMessage) || + Objects.isNull(originalTopic) || + ExceptionUtil.noRequeue(exceptionMessage) + ) { + return; + } + + //TODO: 추후에 exponential 하게 구현할 수 있음 + try { + Thread.sleep(Integer.parseInt(FIVE_SECONDS)); + } catch (InterruptedException e) { + throw new RetryableException(); + } + + try { + kafkaService.sendKafkaEvent(originalTopic, orderCreationFailedEvent); + } catch(Exception e) { + throw new JoyeusePlaneteApplicationException(ErrorCode.KAFKA_DEAD_LETTER_TOPIC_FAIL_EXCEPTION); + } + } +} diff --git a/payment/src/main/resources/application.yml b/payment/src/main/resources/application.yml index 16a5b67..e6adf37 100644 --- a/payment/src/main/resources/application.yml +++ b/payment/src/main/resources/application.yml @@ -15,12 +15,17 @@ spring: payments: events: topic: - name: payments.order-created-event + name: payments.order-creation-event fail: payments.order-creation-failed-event dead-letter-topic: name: payments.dead-letter-topic +foods: + events: + topic: + name: foods.order-created-event + kafka: topic: partitions: 3 From 1a50ef8afda05c5266f26b91c22805c3360d5095 Mon Sep 17 00:00:00 2001 From: koreanMike513 Date: Wed, 29 Jan 2025 22:59:57 +0000 Subject: [PATCH 17/24] feat: Added kafka events to orders project --- .../orders/service/OrderService.java | 16 ++++++++++ .../OrderCreationFailEventHandler.java | 30 +++++++++++++++++++ orders/src/main/resources/application.yml | 5 ++++ 3 files changed, 51 insertions(+) create mode 100644 orders/src/main/java/com/f_lab/joyeuse_planete/orders/service/handler/OrderCreationFailEventHandler.java diff --git a/orders/src/main/java/com/f_lab/joyeuse_planete/orders/service/OrderService.java b/orders/src/main/java/com/f_lab/joyeuse_planete/orders/service/OrderService.java index 74e9974..e829a37 100644 --- a/orders/src/main/java/com/f_lab/joyeuse_planete/orders/service/OrderService.java +++ b/orders/src/main/java/com/f_lab/joyeuse_planete/orders/service/OrderService.java @@ -3,6 +3,9 @@ import com.f_lab.joyeuse_planete.core.domain.Order; +import com.f_lab.joyeuse_planete.core.domain.OrderStatus; +import com.f_lab.joyeuse_planete.core.exceptions.ErrorCode; +import com.f_lab.joyeuse_planete.core.exceptions.JoyeusePlaneteApplicationException; import com.f_lab.joyeuse_planete.core.kafka.service.KafkaService; import com.f_lab.joyeuse_planete.core.util.log.LogUtil; import com.f_lab.joyeuse_planete.orders.domain.OrderSearchCondition; @@ -18,6 +21,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.Optional; + @Slf4j @Service @Transactional(readOnly = true) @@ -34,6 +39,12 @@ public Page findOrders(OrderSearchCondition condition, Pageable pageab return orderRepository.findOrders(condition, pageable); } + public void updateOrderStatusToCancel(Long orderId) { + Order order = findOrderById(orderId); + order.setStatus(OrderStatus.FAIL); + orderRepository.save(order); + } + @Transactional public OrderCreateResponseDTO createFoodOrder(OrderCreateRequestDTO request) { Order order = request.toEntity(); @@ -57,5 +68,10 @@ public void sendKafkaOrderCreatedEvent(OrderCreateRequestDTO request, Order orde throw new RuntimeException(e); } } + + private Order findOrderById(Long orderId) { + return orderRepository.findById(orderId) + .orElseThrow(() -> new JoyeusePlaneteApplicationException(ErrorCode.ORDER_NOT_EXIST_EXCEPTION)); + } } diff --git a/orders/src/main/java/com/f_lab/joyeuse_planete/orders/service/handler/OrderCreationFailEventHandler.java b/orders/src/main/java/com/f_lab/joyeuse_planete/orders/service/handler/OrderCreationFailEventHandler.java new file mode 100644 index 0000000..4510ca9 --- /dev/null +++ b/orders/src/main/java/com/f_lab/joyeuse_planete/orders/service/handler/OrderCreationFailEventHandler.java @@ -0,0 +1,30 @@ +package com.f_lab.joyeuse_planete.orders.service.handler; + +import com.f_lab.joyeuse_planete.core.events.OrderCreationFailedEvent; +import com.f_lab.joyeuse_planete.orders.service.OrderService; +import lombok.RequiredArgsConstructor; +import org.springframework.kafka.annotation.KafkaHandler; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.messaging.handler.annotation.Payload; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + + +@Component +@RequiredArgsConstructor +@Transactional("transactionManager") +@KafkaListener( + topics = { "${foods.events.topic.fail}" }, + groupId = "${spring.kafka.consumer.group-id}" +) +public class OrderCreationFailEventHandler { + + private final OrderService orderService; + + @KafkaHandler + public void processOrderCreationFailEvent(@Payload OrderCreationFailedEvent orderCreationFailedEvent) { + orderService.updateOrderStatusToCancel(orderCreationFailedEvent.getOrderId()); + + + } +} diff --git a/orders/src/main/resources/application.yml b/orders/src/main/resources/application.yml index 83139c4..0a4af4b 100644 --- a/orders/src/main/resources/application.yml +++ b/orders/src/main/resources/application.yml @@ -19,6 +19,11 @@ orders: dead-letter-topic: name: orders.dead-letter-topic +foods: + events: + topic: + fail: foods.order-creation-failed-event + kafka: topic: partitions: 3 From 9e0961dc1a86c50eb5880161e11e6f19838c0bfd Mon Sep 17 00:00:00 2001 From: koreanMike513 Date: Thu, 30 Jan 2025 14:56:28 +0000 Subject: [PATCH 18/24] fix: Fixed updateOrderStatus method --- .../f_lab/joyeuse_planete/orders/service/OrderService.java | 5 +++-- .../service/handler/OrderCreationFailEventHandler.java | 5 ++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/orders/src/main/java/com/f_lab/joyeuse_planete/orders/service/OrderService.java b/orders/src/main/java/com/f_lab/joyeuse_planete/orders/service/OrderService.java index e829a37..1da180a 100644 --- a/orders/src/main/java/com/f_lab/joyeuse_planete/orders/service/OrderService.java +++ b/orders/src/main/java/com/f_lab/joyeuse_planete/orders/service/OrderService.java @@ -39,9 +39,10 @@ public Page findOrders(OrderSearchCondition condition, Pageable pageab return orderRepository.findOrders(condition, pageable); } - public void updateOrderStatusToCancel(Long orderId) { + @Transactional + public void updateOrderStatus(Long orderId, OrderStatus status) { Order order = findOrderById(orderId); - order.setStatus(OrderStatus.FAIL); + order.setStatus(status); orderRepository.save(order); } diff --git a/orders/src/main/java/com/f_lab/joyeuse_planete/orders/service/handler/OrderCreationFailEventHandler.java b/orders/src/main/java/com/f_lab/joyeuse_planete/orders/service/handler/OrderCreationFailEventHandler.java index 4510ca9..6a576c6 100644 --- a/orders/src/main/java/com/f_lab/joyeuse_planete/orders/service/handler/OrderCreationFailEventHandler.java +++ b/orders/src/main/java/com/f_lab/joyeuse_planete/orders/service/handler/OrderCreationFailEventHandler.java @@ -1,5 +1,6 @@ package com.f_lab.joyeuse_planete.orders.service.handler; +import com.f_lab.joyeuse_planete.core.domain.OrderStatus; import com.f_lab.joyeuse_planete.core.events.OrderCreationFailedEvent; import com.f_lab.joyeuse_planete.orders.service.OrderService; import lombok.RequiredArgsConstructor; @@ -23,8 +24,6 @@ public class OrderCreationFailEventHandler { @KafkaHandler public void processOrderCreationFailEvent(@Payload OrderCreationFailedEvent orderCreationFailedEvent) { - orderService.updateOrderStatusToCancel(orderCreationFailedEvent.getOrderId()); - - + orderService.updateOrderStatus(orderCreationFailedEvent.getOrderId(), OrderStatus.FAIL); } } From dcd17b1b60dd1c9e1ebd91f860d8473570cbc541 Mon Sep 17 00:00:00 2001 From: koreanMike513 Date: Thu, 30 Jan 2025 15:02:27 +0000 Subject: [PATCH 19/24] fix: Fixed kafka catch block for exceptions that should not be tried --- .../joyeuse_planete/core/kafka/service/KafkaService.java | 8 +++++++- .../foods/service/handler/OrderCreatedEventHandler.java | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/service/KafkaService.java b/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/service/KafkaService.java index 46f7d74..4817595 100644 --- a/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/service/KafkaService.java +++ b/core/src/main/java/com/f_lab/joyeuse_planete/core/kafka/service/KafkaService.java @@ -2,8 +2,12 @@ import com.f_lab.joyeuse_planete.core.kafka.aspect.KafkaRetry; import com.f_lab.joyeuse_planete.core.kafka.exceptions.RetryableException; +import com.f_lab.joyeuse_planete.core.util.log.LogUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.kafka.common.errors.BrokerNotAvailableException; +import org.apache.kafka.common.errors.DisconnectException; +import org.apache.kafka.common.errors.NetworkException; import org.springframework.kafka.core.KafkaTemplate; import org.springframework.transaction.annotation.Transactional; @@ -20,8 +24,10 @@ public class KafkaService { public void sendKafkaEvent(String event, Object object) { try { kafkaTemplate.send(event, object); + } catch(DisconnectException | NetworkException | BrokerNotAvailableException e) { + LogUtil.exception("KafkaService.sendKafkaEvent (DisconnectException | NetworkException | BrokerNotAvailableException)", e); } catch(Exception e) { - log.error("오류가 발생하였습니다. event = {}, message = {}", event, e.getMessage(), e); + LogUtil.exception("KafkaService.sendKafkaEvent (Exception)", e); throw new RetryableException(); } } diff --git a/foods/src/main/java/com/f_lab/joyeuse_planete/foods/service/handler/OrderCreatedEventHandler.java b/foods/src/main/java/com/f_lab/joyeuse_planete/foods/service/handler/OrderCreatedEventHandler.java index a069b91..03759c7 100644 --- a/foods/src/main/java/com/f_lab/joyeuse_planete/foods/service/handler/OrderCreatedEventHandler.java +++ b/foods/src/main/java/com/f_lab/joyeuse_planete/foods/service/handler/OrderCreatedEventHandler.java @@ -7,6 +7,8 @@ import com.f_lab.joyeuse_planete.core.util.log.LogUtil; import com.f_lab.joyeuse_planete.foods.service.FoodService; import lombok.RequiredArgsConstructor; +import org.apache.kafka.common.errors.DisconnectException; +import org.apache.kafka.common.errors.NetworkException; import org.springframework.beans.factory.annotation.Value; import org.springframework.kafka.annotation.KafkaHandler; import org.springframework.kafka.annotation.KafkaListener; From 793cd3a52f298656076573f86c50742170d2f178 Mon Sep 17 00:00:00 2001 From: koreanMike513 Date: Thu, 30 Jan 2025 15:05:14 +0000 Subject: [PATCH 20/24] fix: Fixed error code --- .../com/f_lab/joyeuse_planete/core/exceptions/ErrorCode.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/com/f_lab/joyeuse_planete/core/exceptions/ErrorCode.java b/core/src/main/java/com/f_lab/joyeuse_planete/core/exceptions/ErrorCode.java index 52ce70b..815e865 100644 --- a/core/src/main/java/com/f_lab/joyeuse_planete/core/exceptions/ErrorCode.java +++ b/core/src/main/java/com/f_lab/joyeuse_planete/core/exceptions/ErrorCode.java @@ -18,7 +18,7 @@ public enum ErrorCode { // KAFKA KAFKA_RETRY_FAIL_EXCEPTION("오류 발생! 잠시 후 다시 시도해주세요.", 503), - KAFKA_DEAD_LETTER_TOPIC_FAIL_EXCEPTION("데드레터토픽을 다루는데 실패하였습니다.", 500), + KAFKA_DEAD_LETTER_TOPIC_FAIL_EXCEPTION("오류 발생! 잠시 후 다시 시도해주세요.", 500), ; private String description; From 33687d924442441efe9d7509eb43f533b1abf981 Mon Sep 17 00:00:00 2001 From: koreanMike513 Date: Thu, 30 Jan 2025 15:12:47 +0000 Subject: [PATCH 21/24] fix: Uncommented tests --- foods/build.gradle | 1 + .../foods/FoodsApplicationTests.java | 20 +- .../foods/aspect/LockRetryAspectTest.java | 224 +++++++++--------- 3 files changed, 124 insertions(+), 121 deletions(-) diff --git a/foods/build.gradle b/foods/build.gradle index fd2199c..4920751 100644 --- a/foods/build.gradle +++ b/foods/build.gradle @@ -12,4 +12,5 @@ dependencies { implementation 'org.springframework.kafka:spring-kafka' testImplementation 'org.springframework.kafka:spring-kafka' + testImplementation 'org.springframework.kafka:spring-kafka-test' } \ No newline at end of file diff --git a/foods/src/test/java/com/f_lab/joyeuse_planete/foods/FoodsApplicationTests.java b/foods/src/test/java/com/f_lab/joyeuse_planete/foods/FoodsApplicationTests.java index 9ba72ca..88010e1 100644 --- a/foods/src/test/java/com/f_lab/joyeuse_planete/foods/FoodsApplicationTests.java +++ b/foods/src/test/java/com/f_lab/joyeuse_planete/foods/FoodsApplicationTests.java @@ -2,12 +2,14 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; -// -//@SpringBootTest -//class FoodsApplicationTests { -// -// @Test -// void contextLoads() { -// } -// -//} +import org.springframework.kafka.test.context.EmbeddedKafka; + +@EmbeddedKafka +@SpringBootTest +class FoodsApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/foods/src/test/java/com/f_lab/joyeuse_planete/foods/aspect/LockRetryAspectTest.java b/foods/src/test/java/com/f_lab/joyeuse_planete/foods/aspect/LockRetryAspectTest.java index 3679a0e..87a49ec 100644 --- a/foods/src/test/java/com/f_lab/joyeuse_planete/foods/aspect/LockRetryAspectTest.java +++ b/foods/src/test/java/com/f_lab/joyeuse_planete/foods/aspect/LockRetryAspectTest.java @@ -1,112 +1,112 @@ -//package com.f_lab.joyeuse_planete.foods.aspect; -// -//import com.f_lab.joyeuse_planete.core.aspect.RetryOnLockFailure; -//import com.f_lab.joyeuse_planete.core.domain.Food; -//import com.f_lab.joyeuse_planete.foods.repository.FoodRepository; -//import jakarta.persistence.LockTimeoutException; -//import jakarta.persistence.PessimisticLockException; -//import lombok.RequiredArgsConstructor; -//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.boot.test.context.TestConfiguration; -//import org.springframework.context.annotation.Bean; -//import org.springframework.test.context.bean.override.mockito.MockitoBean; -// -//import java.util.Optional; -// -//import static org.assertj.core.api.Assertions.assertThatThrownBy; -//import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; -//import static org.mockito.ArgumentMatchers.anyLong; -//import static org.mockito.Mockito.times; -//import static org.mockito.Mockito.verify; -//import static org.mockito.Mockito.when; -// -//@SpringBootTest -//class LockRetryAspectTest { -// -// @MockitoBean -// private FoodRepository foodRepository; -// -// @Autowired -// private FoodTestService foodTestService; -// -// @TestConfiguration -// static class LockRetryAspectTestConfig { -// @Bean -// public FoodTestService foodTestService(FoodRepository foodRepository) { -// return new FoodTestService(foodRepository); -// } -// } -// -// @Test -// @DisplayName("락 없이 첫 시도에 성공") -// void test_find_food_lock_and_retry_success() { -// // given -// Long foodId = 1L; -// Food expectedFood = createFood(foodId); -// -// // when -// when(foodRepository.findFoodByFoodIdWithPessimisticLock(foodId)) -// .thenReturn(Optional.ofNullable(expectedFood)); -// -// Food foundFood = foodTestService.findFood(foodId).get(); -// -// // then -// assertThat(foundFood.getId()).isEqualTo(foodId); -// } -// -// @Test -// @DisplayName("첫 번째 시도는 실패하고 두 번째 시도에 성공") -// void test_find_food_lock_and_retry_fail_on_first_then_success() { -// // given -// Long foodId = 1L; -// Food expectedFood = createFood(foodId); -// -// // when -// when(foodRepository.findFoodByFoodIdWithPessimisticLock(anyLong())) -// .thenThrow(new PessimisticLockException()) -// .thenReturn(Optional.ofNullable(expectedFood)); -// -// Food foundFood = foodTestService.findFood(foodId).get(); -// -// // then -// assertThat(foundFood.getId()).isEqualTo(foodId); -// verify(foodRepository, times(2)).findFoodByFoodIdWithPessimisticLock(foodId); -// } -// -// @Test -// @DisplayName("락 타임아웃으로 최대 재시도 후 실패") -// void test_find_food_lock_and_retry_fail() { -// // given -// Long foodId = 1L; -// -// // when -// when(foodRepository.findFoodByFoodIdWithPessimisticLock(anyLong())) -// .thenThrow(new PessimisticLockException()) -// .thenThrow(new LockTimeoutException()) -// .thenThrow(new LockTimeoutException()); -// -// // then -// assertThatThrownBy(() -> foodTestService.findFood(foodId)) -// .isInstanceOf(RuntimeException.class) -// .hasMessage("현재 너무 많은 요청을 처리하고 있습니다. 다시 시도해주세요"); -// } -// -// private Food createFood(Long foodId) { -// return Food.builder() -// .id(foodId) -// .build(); -// } -// -// @RequiredArgsConstructor -// static class FoodTestService { -// private final FoodRepository foodRepository; -// -// @RetryOnLockFailure -// public Optional findFood(Long foodId) { -// return foodRepository.findFoodByFoodIdWithPessimisticLock(foodId); -// } -// } -//} \ No newline at end of file +package com.f_lab.joyeuse_planete.foods.aspect; + +import com.f_lab.joyeuse_planete.core.aspect.RetryOnLockFailure; +import com.f_lab.joyeuse_planete.core.domain.Food; +import com.f_lab.joyeuse_planete.foods.repository.FoodRepository; +import jakarta.persistence.LockTimeoutException; +import jakarta.persistence.PessimisticLockException; +import lombok.RequiredArgsConstructor; +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.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.bean.override.mockito.MockitoBean; + +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@SpringBootTest +class LockRetryAspectTest { + + @MockitoBean + private FoodRepository foodRepository; + + @Autowired + private FoodTestService foodTestService; + + @TestConfiguration + static class LockRetryAspectTestConfig { + @Bean + public FoodTestService foodTestService(FoodRepository foodRepository) { + return new FoodTestService(foodRepository); + } + } + + @Test + @DisplayName("락 없이 첫 시도에 성공") + void test_find_food_lock_and_retry_success() { + // given + Long foodId = 1L; + Food expectedFood = createFood(foodId); + + // when + when(foodRepository.findFoodByFoodIdWithPessimisticLock(foodId)) + .thenReturn(Optional.ofNullable(expectedFood)); + + Food foundFood = foodTestService.findFood(foodId).get(); + + // then + assertThat(foundFood.getId()).isEqualTo(foodId); + } + + @Test + @DisplayName("첫 번째 시도는 실패하고 두 번째 시도에 성공") + void test_find_food_lock_and_retry_fail_on_first_then_success() { + // given + Long foodId = 1L; + Food expectedFood = createFood(foodId); + + // when + when(foodRepository.findFoodByFoodIdWithPessimisticLock(anyLong())) + .thenThrow(new PessimisticLockException()) + .thenReturn(Optional.ofNullable(expectedFood)); + + Food foundFood = foodTestService.findFood(foodId).get(); + + // then + assertThat(foundFood.getId()).isEqualTo(foodId); + verify(foodRepository, times(2)).findFoodByFoodIdWithPessimisticLock(foodId); + } + + @Test + @DisplayName("락 타임아웃으로 최대 재시도 후 실패") + void test_find_food_lock_and_retry_fail() { + // given + Long foodId = 1L; + + // when + when(foodRepository.findFoodByFoodIdWithPessimisticLock(anyLong())) + .thenThrow(new PessimisticLockException()) + .thenThrow(new LockTimeoutException()) + .thenThrow(new LockTimeoutException()); + + // then + assertThatThrownBy(() -> foodTestService.findFood(foodId)) + .isInstanceOf(RuntimeException.class) + .hasMessage("현재 너무 많은 요청을 처리하고 있습니다. 다시 시도해주세요 (락)"); + } + + private Food createFood(Long foodId) { + return Food.builder() + .id(foodId) + .build(); + } + + @RequiredArgsConstructor + static class FoodTestService { + private final FoodRepository foodRepository; + + @RetryOnLockFailure + public Optional findFood(Long foodId) { + return foodRepository.findFoodByFoodIdWithPessimisticLock(foodId); + } + } +} \ No newline at end of file From 0db36313db2818283fc75297759b17a7051137b9 Mon Sep 17 00:00:00 2001 From: koreanMike513 Date: Thu, 30 Jan 2025 15:15:08 +0000 Subject: [PATCH 22/24] fix: Fixed ALL status and incomplete tests --- .../repository/OrderRepositoryCustomImpl.java | 4 +- .../repository/OrderRepositoryTest.java | 60 +------------------ 2 files changed, 4 insertions(+), 60 deletions(-) diff --git a/orders/src/main/java/com/f_lab/joyeuse_planete/orders/repository/OrderRepositoryCustomImpl.java b/orders/src/main/java/com/f_lab/joyeuse_planete/orders/repository/OrderRepositoryCustomImpl.java index c1fc9af..6a71945 100644 --- a/orders/src/main/java/com/f_lab/joyeuse_planete/orders/repository/OrderRepositoryCustomImpl.java +++ b/orders/src/main/java/com/f_lab/joyeuse_planete/orders/repository/OrderRepositoryCustomImpl.java @@ -92,9 +92,7 @@ public Page findOrders(OrderSearchCondition condition, Pageable pageab } private BooleanExpression eqStatus(String status) { - return (status != null && !status.equals("ALL")) - ? order.status.eq(OrderStatus.valueOf(status)) - : null; + return (status != null) ? order.status.eq(OrderStatus.valueOf(status)) : null; } private BooleanExpression dateGoe(LocalDateTime date) { diff --git a/orders/src/test/java/com/f_lab/joyeuse_planete/orders/repository/OrderRepositoryTest.java b/orders/src/test/java/com/f_lab/joyeuse_planete/orders/repository/OrderRepositoryTest.java index 70ffaa8..d8bf44d 100644 --- a/orders/src/test/java/com/f_lab/joyeuse_planete/orders/repository/OrderRepositoryTest.java +++ b/orders/src/test/java/com/f_lab/joyeuse_planete/orders/repository/OrderRepositoryTest.java @@ -1,68 +1,14 @@ package com.f_lab.joyeuse_planete.orders.repository; -import com.f_lab.joyeuse_planete.core.domain.Order; -import com.f_lab.joyeuse_planete.core.domain.OrderStatus; -import com.f_lab.joyeuse_planete.orders.domain.OrderSearchCondition; -import com.f_lab.joyeuse_planete.orders.dto.response.OrderDTO; -import org.junit.jupiter.api.BeforeEach; -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.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; import org.springframework.kafka.test.context.EmbeddedKafka; -import java.math.BigDecimal; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; - - @EmbeddedKafka @SpringBootTest class OrderRepositoryTest { - @Autowired OrderRepository orderRepository; - - @BeforeEach - void beforeEach() { - orderRepository.saveAll(getOrderList()); - } - - @Test - @DisplayName("AAA") - void test() { - // given - OrderSearchCondition condition = new OrderSearchCondition(); - Pageable pageable = PageRequest.of(condition.getPage(), condition.getSize()); - - // when - Page result = orderRepository.findOrders(condition, pageable); - - for (Order o : orderRepository.findAll()) { - System.out.println("ACtuAL = " + o); - - } - // then - System.out.println("RESULT " + result); - } - - private List getOrderList() { - List orders = new ArrayList<>(); - - for (int i = 1; i <= 20; i++) { - OrderStatus status = (i % 3 == 0) ? OrderStatus.READY : (i % 2 == 0 ? OrderStatus.IN_PROGRESS : OrderStatus.DONE); - - orders.add(Order.builder() - .totalCost(BigDecimal.ONE) - .quantity(i % 5 + 1) - .status(status) - .collectionTime(LocalDateTime.now().plusDays(i)) - .build()); - } - - return orders; - } + @Autowired + OrderRepository orderRepository; } \ No newline at end of file From b2b5722a3f44e458d4911bcc39deba6dc868ac5f Mon Sep 17 00:00:00 2001 From: koreanMike513 Date: Thu, 30 Jan 2025 15:21:06 +0000 Subject: [PATCH 23/24] fix: Left log for unhandled dead letter topic --- .../java/com/f_lab/joyeuse_planete/core/util/log/LogUtil.java | 4 ++++ .../service/handler/OrderCreatedDeadLetterTopicHandler.java | 2 ++ .../service/handler/PaymentDeadLetterTopicHandler.java | 2 ++ 3 files changed, 8 insertions(+) diff --git a/core/src/main/java/com/f_lab/joyeuse_planete/core/util/log/LogUtil.java b/core/src/main/java/com/f_lab/joyeuse_planete/core/util/log/LogUtil.java index 4037086..a9e1702 100644 --- a/core/src/main/java/com/f_lab/joyeuse_planete/core/util/log/LogUtil.java +++ b/core/src/main/java/com/f_lab/joyeuse_planete/core/util/log/LogUtil.java @@ -11,4 +11,8 @@ public static void exception(String method, Throwable e) { public static void retry(int attempts, String method) { log.warn("시도 횟수={}, 다시 메서드={} 락을 얻기를 시도합니다", attempts, method); } + + public static void deadLetterMissingFormats(String exceptionName, String exceptionMessage, String originalTopic) { + log.warn("DEAD LETTER TOPIC 에서 오류가 발생하였습니다. topic = {} name = {} message = {}", originalTopic, exceptionName, exceptionMessage); + } } diff --git a/foods/src/main/java/com/f_lab/joyeuse_planete/foods/service/handler/OrderCreatedDeadLetterTopicHandler.java b/foods/src/main/java/com/f_lab/joyeuse_planete/foods/service/handler/OrderCreatedDeadLetterTopicHandler.java index 0f1b75f..531bcb1 100644 --- a/foods/src/main/java/com/f_lab/joyeuse_planete/foods/service/handler/OrderCreatedDeadLetterTopicHandler.java +++ b/foods/src/main/java/com/f_lab/joyeuse_planete/foods/service/handler/OrderCreatedDeadLetterTopicHandler.java @@ -7,6 +7,7 @@ import com.f_lab.joyeuse_planete.core.kafka.exceptions.RetryableException; import com.f_lab.joyeuse_planete.core.kafka.service.KafkaService; import com.f_lab.joyeuse_planete.core.kafka.util.ExceptionUtil; +import com.f_lab.joyeuse_planete.core.util.log.LogUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.kafka.annotation.KafkaHandler; @@ -48,6 +49,7 @@ public void processDeadOrderCreationFailedEvent(@Payload OrderCreationFailedEven Objects.isNull(originalTopic) || ExceptionUtil.noRequeue(exceptionMessage) ) { + LogUtil.deadLetterMissingFormats(exceptionName, exceptionMessage, originalTopic); return; } diff --git a/payment/src/main/java/com/f_lab/joyeuse_planete/payment/service/handler/PaymentDeadLetterTopicHandler.java b/payment/src/main/java/com/f_lab/joyeuse_planete/payment/service/handler/PaymentDeadLetterTopicHandler.java index 1edbc6f..cb1851e 100644 --- a/payment/src/main/java/com/f_lab/joyeuse_planete/payment/service/handler/PaymentDeadLetterTopicHandler.java +++ b/payment/src/main/java/com/f_lab/joyeuse_planete/payment/service/handler/PaymentDeadLetterTopicHandler.java @@ -7,6 +7,7 @@ import com.f_lab.joyeuse_planete.core.kafka.exceptions.RetryableException; import com.f_lab.joyeuse_planete.core.kafka.service.KafkaService; import com.f_lab.joyeuse_planete.core.kafka.util.ExceptionUtil; +import com.f_lab.joyeuse_planete.core.util.log.LogUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.kafka.annotation.KafkaHandler; @@ -47,6 +48,7 @@ public void processDeadOrderCreationFailedEvent(@Payload OrderCreationFailedEven Objects.isNull(originalTopic) || ExceptionUtil.noRequeue(exceptionMessage) ) { + LogUtil.deadLetterMissingFormats(exceptionName, exceptionMessage, originalTopic); return; } From 4667cba7f7e6e85620d82938cb0f6f40117fe668 Mon Sep 17 00:00:00 2001 From: koreanMike513 Date: Thu, 30 Jan 2025 15:23:48 +0000 Subject: [PATCH 24/24] fix: Fixed * import statements and error codes --- .../java/com/f_lab/joyeuse_planete/core/domain/Order.java | 8 ++++++-- .../f_lab/joyeuse_planete/core/exceptions/ErrorCode.java | 2 +- .../joyeuse_planete/foods/aspect/LockRetryAspectTest.java | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/com/f_lab/joyeuse_planete/core/domain/Order.java b/core/src/main/java/com/f_lab/joyeuse_planete/core/domain/Order.java index 8829f2e..985e0e4 100644 --- a/core/src/main/java/com/f_lab/joyeuse_planete/core/domain/Order.java +++ b/core/src/main/java/com/f_lab/joyeuse_planete/core/domain/Order.java @@ -1,7 +1,6 @@ package com.f_lab.joyeuse_planete.core.domain; -import com.f_lab.joyeuse_planete.core.domain.base.BaseEntity; import com.f_lab.joyeuse_planete.core.domain.base.BaseTimeEntity; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; @@ -12,7 +11,12 @@ import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToOne; import jakarta.persistence.Table; -import lombok.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; import java.math.BigDecimal; import java.time.LocalDateTime; diff --git a/core/src/main/java/com/f_lab/joyeuse_planete/core/exceptions/ErrorCode.java b/core/src/main/java/com/f_lab/joyeuse_planete/core/exceptions/ErrorCode.java index 815e865..0037376 100644 --- a/core/src/main/java/com/f_lab/joyeuse_planete/core/exceptions/ErrorCode.java +++ b/core/src/main/java/com/f_lab/joyeuse_planete/core/exceptions/ErrorCode.java @@ -14,7 +14,7 @@ public enum ErrorCode { ORDER_NOT_EXIST_EXCEPTION("존재하지 않는 주문입니다.", 406), // LOCK - LOCK_ACQUISITION_FAIL_EXCEPTION("현재 너무 많은 요청을 처리하고 있습니다. 다시 시도해주세요 (락)",503), + LOCK_ACQUISITION_FAIL_EXCEPTION("현재 너무 많은 요청을 처리하고 있습니다. 다시 시도해주세요.",503), // KAFKA KAFKA_RETRY_FAIL_EXCEPTION("오류 발생! 잠시 후 다시 시도해주세요.", 503), diff --git a/foods/src/test/java/com/f_lab/joyeuse_planete/foods/aspect/LockRetryAspectTest.java b/foods/src/test/java/com/f_lab/joyeuse_planete/foods/aspect/LockRetryAspectTest.java index 87a49ec..a44c523 100644 --- a/foods/src/test/java/com/f_lab/joyeuse_planete/foods/aspect/LockRetryAspectTest.java +++ b/foods/src/test/java/com/f_lab/joyeuse_planete/foods/aspect/LockRetryAspectTest.java @@ -91,7 +91,7 @@ void test_find_food_lock_and_retry_fail() { // then assertThatThrownBy(() -> foodTestService.findFood(foodId)) .isInstanceOf(RuntimeException.class) - .hasMessage("현재 너무 많은 요청을 처리하고 있습니다. 다시 시도해주세요 (락)"); + .hasMessage("현재 너무 많은 요청을 처리하고 있습니다. 다시 시도해주세요."); } private Food createFood(Long foodId) {