-
Notifications
You must be signed in to change notification settings - Fork 417
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
#1148 - Enhance Error Handler, Serialize, Deserialize
- Loading branch information
Duy Le Van
committed
Oct 16, 2024
1 parent
baf7e28
commit b44442f
Showing
22 changed files
with
519 additions
and
200 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
36 changes: 36 additions & 0 deletions
36
common-library/src/main/java/com/yas/commonlibrary/kafka/cdc/BaseCdcConsumer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package com.yas.commonlibrary.kafka.cdc; | ||
|
||
import java.util.function.Consumer; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
import org.springframework.kafka.annotation.DltHandler; | ||
import org.springframework.kafka.support.KafkaHeaders; | ||
import org.springframework.messaging.MessageHeaders; | ||
import org.springframework.messaging.handler.annotation.Header; | ||
|
||
/** | ||
* Base class for CDC (Change Data Capture) Kafka consumers. | ||
* Provides common methods for processing messages and handling Dead Letter Topic (DLT) events. | ||
* | ||
* @param <T> Type of the message payload. | ||
*/ | ||
public abstract class BaseCdcConsumer<T> { | ||
|
||
public static final Logger LOGGER = LoggerFactory.getLogger(BaseCdcConsumer.class); | ||
|
||
protected void processMessage(T record, MessageHeaders headers, Consumer<T> consumer) { | ||
LOGGER.info("## Received message - headers: {}", headers); | ||
if (record == null) { | ||
LOGGER.warn("## Null payload received"); | ||
} else { | ||
LOGGER.info("## Processing record - Key: {} | Value: {}", headers.get(KafkaHeaders.RECEIVED_KEY), record); | ||
consumer.accept(record); | ||
LOGGER.info("## Record processed successfully - Key: {} \n", headers.get(KafkaHeaders.RECEIVED_KEY)); | ||
} | ||
} | ||
|
||
@DltHandler | ||
public void dlt(T data, @Header(KafkaHeaders.RECEIVED_TOPIC) String topic) { | ||
LOGGER.error("### Event from topic {} is dead lettered - event: {}", topic, data); | ||
} | ||
} |
43 changes: 43 additions & 0 deletions
43
common-library/src/main/java/com/yas/commonlibrary/kafka/cdc/RetrySupportDql.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package com.yas.commonlibrary.kafka.cdc; | ||
|
||
import java.lang.annotation.Documented; | ||
import java.lang.annotation.ElementType; | ||
import java.lang.annotation.Retention; | ||
import java.lang.annotation.RetentionPolicy; | ||
import java.lang.annotation.Target; | ||
import org.springframework.core.annotation.AliasFor; | ||
import org.springframework.kafka.annotation.RetryableTopic; | ||
import org.springframework.kafka.retrytopic.SameIntervalTopicReuseStrategy; | ||
import org.springframework.retry.annotation.Backoff; | ||
|
||
/** | ||
* Custom annotation that extends Spring's {@link RetryableTopic} to | ||
* add retry and dead letter queue (DLQ) support for Kafka listeners. | ||
* Provides additional configuration for retry backoff, number of attempts, | ||
* topic creation, and exclusion of certain exceptions. | ||
*/ | ||
@Documented | ||
@RetryableTopic | ||
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE}) | ||
@Retention(RetentionPolicy.RUNTIME) | ||
public @interface RetrySupportDql { | ||
|
||
@AliasFor(annotation = RetryableTopic.class, attribute = "backoff") | ||
Backoff backoff() default @Backoff(value = 6000); | ||
|
||
@AliasFor(annotation = RetryableTopic.class, attribute = "attempts") | ||
String attempts() default "4"; | ||
|
||
@AliasFor(annotation = RetryableTopic.class, attribute = "autoCreateTopics") | ||
String autoCreateTopics() default "true"; | ||
|
||
@AliasFor(annotation = RetryableTopic.class, attribute = "listenerContainerFactory") | ||
String listenerContainerFactory() default ""; | ||
|
||
@AliasFor(annotation = RetryableTopic.class, attribute = "exclude") | ||
Class<? extends Throwable>[] exclude() default {}; | ||
|
||
@AliasFor(annotation = RetryableTopic.class, attribute = "sameIntervalTopicReuseStrategy") | ||
SameIntervalTopicReuseStrategy sameIntervalTopicReuseStrategy() default SameIntervalTopicReuseStrategy.SINGLE_TOPIC; | ||
|
||
} |
54 changes: 54 additions & 0 deletions
54
...library/src/main/java/com/yas/commonlibrary/kafka/cdc/config/BaseKafkaListenerConfig.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package com.yas.commonlibrary.kafka.cdc.config; | ||
|
||
import java.util.Map; | ||
import org.apache.kafka.common.serialization.StringDeserializer; | ||
import org.springframework.boot.autoconfigure.kafka.KafkaProperties; | ||
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; | ||
import org.springframework.kafka.core.ConsumerFactory; | ||
import org.springframework.kafka.core.DefaultKafkaConsumerFactory; | ||
import org.springframework.kafka.support.serializer.ErrorHandlingDeserializer; | ||
import org.springframework.kafka.support.serializer.JsonDeserializer; | ||
|
||
/** | ||
* Base configuration class for setting up Kafka consumers with typed deserialization. | ||
* | ||
* @param <T> The type of messages consumed. | ||
*/ | ||
public abstract class BaseKafkaListenerConfig<T> { | ||
|
||
private final Class<T> type; | ||
private final KafkaProperties kafkaProperties; | ||
|
||
public BaseKafkaListenerConfig(Class<T> type, KafkaProperties kafkaProperties) { | ||
this.type = type; | ||
this.kafkaProperties = kafkaProperties; | ||
} | ||
|
||
public abstract ConcurrentKafkaListenerContainerFactory<String, T> listenerContainerFactory(); | ||
|
||
/** | ||
* Common instance type ConcurrentKafkaListenerContainerFactory. | ||
* | ||
* @return concurrentKafkaListenerContainerFactory {@link ConcurrentKafkaListenerContainerFactory}. | ||
*/ | ||
public ConcurrentKafkaListenerContainerFactory<String, T> kafkaListenerContainerFactory() { | ||
var factory = new ConcurrentKafkaListenerContainerFactory<String, T>(); | ||
factory.setConsumerFactory(typeConsumerFactory(type)); | ||
return factory; | ||
} | ||
|
||
private ConsumerFactory<String, T> typeConsumerFactory(Class<T> clazz) { | ||
Map<String, Object> props = buildConsumerProperties(); | ||
var serialize = new StringDeserializer(); | ||
// wrapper in case serialization/deserialization occur | ||
var jsonDeserializer = new JsonDeserializer<>(clazz); | ||
jsonDeserializer.addTrustedPackages("*"); | ||
var deserialize = new ErrorHandlingDeserializer<>(jsonDeserializer); | ||
return new DefaultKafkaConsumerFactory<>(props, serialize, deserialize); | ||
} | ||
|
||
private Map<String, Object> buildConsumerProperties() { | ||
return kafkaProperties.buildConsumerProperties(null); | ||
} | ||
|
||
} |
22 changes: 22 additions & 0 deletions
22
common-library/src/main/java/com/yas/commonlibrary/kafka/cdc/message/Operation.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package com.yas.commonlibrary.kafka.cdc.message; | ||
|
||
import com.fasterxml.jackson.annotation.JsonValue; | ||
|
||
public enum Operation { | ||
|
||
READ("r"), | ||
CREATE("c"), | ||
UPDATE("u"), | ||
DELETE("d"); | ||
|
||
private final String name; | ||
|
||
Operation(String name) { | ||
this.name = name; | ||
} | ||
|
||
@JsonValue | ||
public String getName() { | ||
return name; | ||
} | ||
} |
20 changes: 20 additions & 0 deletions
20
common-library/src/main/java/com/yas/commonlibrary/kafka/cdc/message/Product.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package com.yas.commonlibrary.kafka.cdc.message; | ||
|
||
import com.fasterxml.jackson.annotation.JsonProperty; | ||
import lombok.AllArgsConstructor; | ||
import lombok.Builder; | ||
import lombok.NoArgsConstructor; | ||
|
||
@lombok.Getter | ||
@lombok.Setter | ||
@Builder | ||
@NoArgsConstructor | ||
@AllArgsConstructor | ||
public class Product { | ||
|
||
private long id; | ||
|
||
@JsonProperty("is_published") | ||
private boolean isPublished; | ||
|
||
} |
23 changes: 23 additions & 0 deletions
23
common-library/src/main/java/com/yas/commonlibrary/kafka/cdc/message/ProductCdcMessage.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package com.yas.commonlibrary.kafka.cdc.message; | ||
|
||
import jakarta.validation.constraints.NotNull; | ||
import lombok.AllArgsConstructor; | ||
import lombok.Builder; | ||
import lombok.NoArgsConstructor; | ||
|
||
@lombok.Getter | ||
@lombok.Setter | ||
@Builder | ||
@NoArgsConstructor | ||
@AllArgsConstructor | ||
public class ProductCdcMessage { | ||
|
||
private Product after; | ||
|
||
private Product before; | ||
|
||
@NotNull | ||
private Operation op; | ||
|
||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
70 changes: 0 additions & 70 deletions
70
recommendation/src/main/java/com/yas/recommendation/consumer/ProductSyncDataConsumer.java
This file was deleted.
Oops, something went wrong.
24 changes: 24 additions & 0 deletions
24
...rc/main/java/com/yas/recommendation/kafka/config/consumer/AppKafkaListenerConfigurer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package com.yas.recommendation.kafka.config.consumer; | ||
|
||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.kafka.annotation.EnableKafka; | ||
import org.springframework.kafka.annotation.KafkaListenerConfigurer; | ||
import org.springframework.kafka.config.KafkaListenerEndpointRegistrar; | ||
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; | ||
|
||
@EnableKafka | ||
@Configuration | ||
public class AppKafkaListenerConfigurer implements KafkaListenerConfigurer { | ||
|
||
private LocalValidatorFactoryBean validator; | ||
|
||
public AppKafkaListenerConfigurer(LocalValidatorFactoryBean validator) { | ||
this.validator = validator; | ||
} | ||
|
||
@Override | ||
public void configureKafkaListeners(KafkaListenerEndpointRegistrar registrar) { | ||
// Enable message validation | ||
registrar.setValidator(this.validator); | ||
} | ||
} |
30 changes: 30 additions & 0 deletions
30
...main/java/com/yas/recommendation/kafka/config/consumer/ProductCdcKafkaListenerConfig.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package com.yas.recommendation.kafka.config.consumer; | ||
|
||
import com.yas.commonlibrary.kafka.cdc.config.BaseKafkaListenerConfig; | ||
import com.yas.commonlibrary.kafka.cdc.message.ProductCdcMessage; | ||
import org.springframework.boot.autoconfigure.kafka.KafkaProperties; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.kafka.annotation.EnableKafka; | ||
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; | ||
import org.springframework.scheduling.annotation.EnableScheduling; | ||
|
||
/** | ||
* Product CDC kafka listener, support convert product cdc message to java object. | ||
*/ | ||
@EnableKafka | ||
@Configuration | ||
@EnableScheduling | ||
public class ProductCdcKafkaListenerConfig extends BaseKafkaListenerConfig<ProductCdcMessage> { | ||
|
||
public ProductCdcKafkaListenerConfig(KafkaProperties kafkaProperties) { | ||
super(ProductCdcMessage.class, kafkaProperties); | ||
} | ||
|
||
@Bean(name = "productCdcListenerContainerFactory") | ||
@Override | ||
public ConcurrentKafkaListenerContainerFactory<String, ProductCdcMessage> listenerContainerFactory() { | ||
return super.kafkaListenerContainerFactory(); | ||
} | ||
|
||
} |
38 changes: 38 additions & 0 deletions
38
...endation/src/main/java/com/yas/recommendation/kafka/consumer/ProductSyncDataConsumer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package com.yas.recommendation.kafka.consumer; | ||
|
||
import com.yas.commonlibrary.kafka.cdc.BaseCdcConsumer; | ||
import com.yas.commonlibrary.kafka.cdc.RetrySupportDql; | ||
import com.yas.commonlibrary.kafka.cdc.message.ProductCdcMessage; | ||
import jakarta.validation.Valid; | ||
import org.springframework.kafka.annotation.KafkaListener; | ||
import org.springframework.messaging.MessageHeaders; | ||
import org.springframework.messaging.handler.annotation.Headers; | ||
import org.springframework.messaging.handler.annotation.Payload; | ||
import org.springframework.stereotype.Component; | ||
|
||
/** | ||
* Product synchronize data consumer for pgvector. | ||
*/ | ||
@Component | ||
public class ProductSyncDataConsumer extends BaseCdcConsumer<ProductCdcMessage> { | ||
|
||
private final ProductSyncService productSyncService; | ||
|
||
public ProductSyncDataConsumer(ProductSyncService productSyncService) { | ||
this.productSyncService = productSyncService; | ||
} | ||
|
||
@KafkaListener( | ||
id = "product-sync-recommendation", | ||
groupId = "product-sync", | ||
topics = "${product.topic.name}", | ||
containerFactory = "productCdcListenerContainerFactory" | ||
) | ||
@RetrySupportDql(listenerContainerFactory = "productCdcListenerContainerFactory") | ||
public void processMessage( | ||
@Payload(required = false) @Valid ProductCdcMessage productCdcMessage, | ||
@Headers MessageHeaders headers | ||
) { | ||
processMessage(productCdcMessage, headers, productSyncService::sync); | ||
} | ||
} |
Oops, something went wrong.