BAEL-7199: Dead Letter Queue for Kafka with Spring
This commit is contained in:
parent
9dcd9c9c20
commit
fd9618cda6
|
@ -0,0 +1,36 @@
|
||||||
|
package com.baeldung.spring.kafka.dlt;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.kafka.clients.consumer.ConsumerConfig;
|
||||||
|
import org.apache.kafka.common.serialization.StringDeserializer;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
|
||||||
|
import org.springframework.kafka.core.ConsumerFactory;
|
||||||
|
import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
|
||||||
|
import org.springframework.kafka.support.serializer.JsonDeserializer;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class KafkaConsumerConfig {
|
||||||
|
|
||||||
|
@Value(value = "${spring.kafka.bootstrap-servers}")
|
||||||
|
private String bootstrapServers;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ConsumerFactory<String, Payment> consumerFactory() {
|
||||||
|
Map<String, Object> config = new HashMap<>();
|
||||||
|
config.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
|
||||||
|
|
||||||
|
return new DefaultKafkaConsumerFactory<>(config, new StringDeserializer(), new JsonDeserializer<>(Payment.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ConcurrentKafkaListenerContainerFactory<String, Payment> containerFactory() {
|
||||||
|
ConcurrentKafkaListenerContainerFactory<String, Payment> factory = new ConcurrentKafkaListenerContainerFactory<>();
|
||||||
|
factory.setConsumerFactory(consumerFactory());
|
||||||
|
return factory;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package com.baeldung.spring.kafka.dlt;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.kafka.annotation.EnableKafka;
|
||||||
|
|
||||||
|
@EnableKafka
|
||||||
|
@SpringBootApplication
|
||||||
|
public class KafkaDltApplication {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(KafkaDltApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package com.baeldung.spring.kafka.dlt;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.kafka.clients.producer.ProducerConfig;
|
||||||
|
import org.apache.kafka.common.serialization.StringSerializer;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.kafka.core.DefaultKafkaProducerFactory;
|
||||||
|
import org.springframework.kafka.core.KafkaTemplate;
|
||||||
|
import org.springframework.kafka.core.ProducerFactory;
|
||||||
|
import org.springframework.kafka.support.serializer.JsonSerializer;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class KafkaRetryConfig {
|
||||||
|
|
||||||
|
@Value(value = "${spring.kafka.bootstrap-servers}")
|
||||||
|
private String bootstrapServers;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ProducerFactory<String, Payment> producerFactory() {
|
||||||
|
Map<String, Object> config = new HashMap<>();
|
||||||
|
config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
|
||||||
|
|
||||||
|
return new DefaultKafkaProducerFactory<>(config, new StringSerializer(), new JsonSerializer<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public KafkaTemplate<String, Payment> retryableTopicKafkaTemplate() {
|
||||||
|
return new KafkaTemplate<>(producerFactory());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
package com.baeldung.spring.kafka.dlt;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Currency;
|
||||||
|
import java.util.StringJoiner;
|
||||||
|
|
||||||
|
public class Payment {
|
||||||
|
private String reference;
|
||||||
|
private BigDecimal amount;
|
||||||
|
private Currency currency;
|
||||||
|
|
||||||
|
public String getReference() {
|
||||||
|
return reference;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReference(String reference) {
|
||||||
|
this.reference = reference;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getAmount() {
|
||||||
|
return amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAmount(BigDecimal amount) {
|
||||||
|
this.amount = amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Currency getCurrency() {
|
||||||
|
return currency;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCurrency(Currency currency) {
|
||||||
|
this.currency = currency;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return new StringJoiner(", ", Payment.class.getSimpleName() + "[", "]").add("reference='" + reference + "'")
|
||||||
|
.add("amount=" + amount)
|
||||||
|
.add("currency=" + currency)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package com.baeldung.spring.kafka.dlt.listener;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.kafka.annotation.DltHandler;
|
||||||
|
import org.springframework.kafka.annotation.KafkaListener;
|
||||||
|
import org.springframework.kafka.annotation.RetryableTopic;
|
||||||
|
import org.springframework.kafka.retrytopic.DltStrategy;
|
||||||
|
import org.springframework.kafka.support.KafkaHeaders;
|
||||||
|
import org.springframework.messaging.handler.annotation.Header;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import com.baeldung.spring.kafka.dlt.Payment;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class PaymentListenerDltFailOnError {
|
||||||
|
private final Logger log = LoggerFactory.getLogger(PaymentListenerDltFailOnError.class);
|
||||||
|
|
||||||
|
@RetryableTopic(attempts = "1", kafkaTemplate = "retryableTopicKafkaTemplate", dltStrategy = DltStrategy.FAIL_ON_ERROR)
|
||||||
|
@KafkaListener(topics = { "payments-fail-on-error-dlt" }, groupId = "payments")
|
||||||
|
public void handlePayment(Payment payment, @Header(KafkaHeaders.RECEIVED_TOPIC) String topic) {
|
||||||
|
log.info("Event on main topic={}, payload={}", topic, payment);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DltHandler
|
||||||
|
public void handleDltPayment(Payment payment, @Header(KafkaHeaders.RECEIVED_TOPIC) String topic) {
|
||||||
|
log.info("Event on dlt topic={}, payload={}", topic, payment);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package com.baeldung.spring.kafka.dlt.listener;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.kafka.annotation.DltHandler;
|
||||||
|
import org.springframework.kafka.annotation.KafkaListener;
|
||||||
|
import org.springframework.kafka.annotation.RetryableTopic;
|
||||||
|
import org.springframework.kafka.retrytopic.DltStrategy;
|
||||||
|
import org.springframework.kafka.support.KafkaHeaders;
|
||||||
|
import org.springframework.messaging.handler.annotation.Header;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import com.baeldung.spring.kafka.dlt.Payment;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class PaymentListenerDltRetryOnError {
|
||||||
|
private final Logger log = LoggerFactory.getLogger(PaymentListenerDltRetryOnError.class);
|
||||||
|
|
||||||
|
@RetryableTopic(attempts = "1", kafkaTemplate = "retryableTopicKafkaTemplate", dltStrategy = DltStrategy.ALWAYS_RETRY_ON_ERROR)
|
||||||
|
@KafkaListener(topics = { "payments-retry-on-error-dlt" }, groupId = "payments")
|
||||||
|
public void handlePayment(Payment payment, @Header(KafkaHeaders.RECEIVED_TOPIC) String topic) {
|
||||||
|
log.info("Event on main topic={}, payload={}", topic, payment);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DltHandler
|
||||||
|
public void handleDltPayment(Payment payment, @Header(KafkaHeaders.RECEIVED_TOPIC) String topic) {
|
||||||
|
log.info("Event on dlt topic={}, payload={}", topic, payment);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package com.baeldung.spring.kafka.dlt.listener;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.kafka.annotation.DltHandler;
|
||||||
|
import org.springframework.kafka.annotation.KafkaListener;
|
||||||
|
import org.springframework.kafka.annotation.RetryableTopic;
|
||||||
|
import org.springframework.kafka.retrytopic.DltStrategy;
|
||||||
|
import org.springframework.kafka.support.KafkaHeaders;
|
||||||
|
import org.springframework.messaging.handler.annotation.Header;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import com.baeldung.spring.kafka.dlt.Payment;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class PaymentListenerNoDlt {
|
||||||
|
private final Logger log = LoggerFactory.getLogger(PaymentListenerNoDlt.class);
|
||||||
|
|
||||||
|
@RetryableTopic(attempts = "1", kafkaTemplate = "retryableTopicKafkaTemplate", dltStrategy = DltStrategy.NO_DLT)
|
||||||
|
@KafkaListener(topics = { "payments-no-dlt" }, groupId = "payments")
|
||||||
|
public void handlePayment(Payment payment, @Header(KafkaHeaders.RECEIVED_TOPIC) String topic) {
|
||||||
|
log.info("Event on main topic={}, payload={}", topic, payment);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DltHandler
|
||||||
|
public void handleDltPayment(Payment payment, @Header(KafkaHeaders.RECEIVED_TOPIC) String topic) {
|
||||||
|
log.info("Event on dlt topic={}, payload={}", topic, payment);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
package com.baeldung.spring.kafka.dlt;
|
||||||
|
|
||||||
|
import static com.baeldung.spring.kafka.dlt.PaymentTestUtils.createPayment;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.doAnswer;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
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.mock.mockito.SpyBean;
|
||||||
|
import org.springframework.kafka.config.KafkaListenerEndpointRegistry;
|
||||||
|
import org.springframework.kafka.core.KafkaTemplate;
|
||||||
|
import org.springframework.kafka.listener.MessageListenerContainer;
|
||||||
|
import org.springframework.kafka.test.context.EmbeddedKafka;
|
||||||
|
import org.springframework.kafka.test.utils.ContainerTestUtils;
|
||||||
|
|
||||||
|
import com.baeldung.spring.kafka.dlt.listener.PaymentListenerDltFailOnError;
|
||||||
|
|
||||||
|
@SpringBootTest(classes = KafkaDltApplication.class)
|
||||||
|
@EmbeddedKafka(partitions = 1, brokerProperties = { "listeners=PLAINTEXT://localhost:9099", "port=9099" })
|
||||||
|
public class KafkaDltFailOnErrorIntegrationTest {
|
||||||
|
private static final String TOPIC = "payments-fail-on-error-dlt";
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private KafkaListenerEndpointRegistry kafkaListenerEndpointRegistry;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private KafkaTemplate<String, Payment> kafkaProducer;
|
||||||
|
|
||||||
|
@SpyBean
|
||||||
|
private PaymentListenerDltFailOnError paymentsConsumer;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
// wait for embedded Kafka
|
||||||
|
for (MessageListenerContainer messageListenerContainer : kafkaListenerEndpointRegistry.getListenerContainers()) {
|
||||||
|
ContainerTestUtils.waitForAssignment(messageListenerContainer, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenMainConsumerSucceeds_thenNoDltMessage() throws Exception {
|
||||||
|
CountDownLatch mainTopicCountDownLatch = new CountDownLatch(1);
|
||||||
|
|
||||||
|
doAnswer(invocation -> {
|
||||||
|
mainTopicCountDownLatch.countDown();
|
||||||
|
return null;
|
||||||
|
}).when(paymentsConsumer)
|
||||||
|
.handlePayment(any(), any());
|
||||||
|
|
||||||
|
kafkaProducer.send(TOPIC, createPayment("dlt-fail-main"));
|
||||||
|
|
||||||
|
assertThat(mainTopicCountDownLatch.await(5, TimeUnit.SECONDS)).isTrue();
|
||||||
|
verify(paymentsConsumer, never()).handleDltPayment(any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenDltConsumerFails_thenDltProcessingStops() throws Exception {
|
||||||
|
CountDownLatch mainTopicCountDownLatch = new CountDownLatch(1);
|
||||||
|
CountDownLatch dlTTopicCountDownLatch = new CountDownLatch(2);
|
||||||
|
|
||||||
|
doAnswer(invocation -> {
|
||||||
|
mainTopicCountDownLatch.countDown();
|
||||||
|
throw new Exception("Simulating error in main consumer");
|
||||||
|
}).when(paymentsConsumer)
|
||||||
|
.handlePayment(any(), any());
|
||||||
|
|
||||||
|
doAnswer(invocation -> {
|
||||||
|
dlTTopicCountDownLatch.countDown();
|
||||||
|
throw new Exception("Simulating error in dlt consumer");
|
||||||
|
}).when(paymentsConsumer)
|
||||||
|
.handleDltPayment(any(), any());
|
||||||
|
|
||||||
|
kafkaProducer.send(TOPIC, createPayment("dlt-fail"));
|
||||||
|
|
||||||
|
assertThat(mainTopicCountDownLatch.await(5, TimeUnit.SECONDS)).isTrue();
|
||||||
|
assertThat(dlTTopicCountDownLatch.await(5, TimeUnit.SECONDS)).isFalse();
|
||||||
|
assertThat(dlTTopicCountDownLatch.getCount()).isEqualTo(1);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
package com.baeldung.spring.kafka.dlt;
|
||||||
|
|
||||||
|
import static com.baeldung.spring.kafka.dlt.PaymentTestUtils.createPayment;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.awaitility.Awaitility.await;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.doAnswer;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
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.mock.mockito.SpyBean;
|
||||||
|
import org.springframework.kafka.config.KafkaListenerEndpointRegistry;
|
||||||
|
import org.springframework.kafka.core.KafkaTemplate;
|
||||||
|
import org.springframework.kafka.listener.MessageListenerContainer;
|
||||||
|
import org.springframework.kafka.test.context.EmbeddedKafka;
|
||||||
|
import org.springframework.kafka.test.utils.ContainerTestUtils;
|
||||||
|
|
||||||
|
import com.baeldung.spring.kafka.dlt.listener.PaymentListenerDltRetryOnError;
|
||||||
|
|
||||||
|
@SpringBootTest(classes = KafkaDltApplication.class)
|
||||||
|
@EmbeddedKafka(partitions = 1, brokerProperties = { "listeners=PLAINTEXT://localhost:9099", "port=9099" })
|
||||||
|
public class KafkaDltRetryOnErrorIntegrationTest {
|
||||||
|
private static final String TOPIC = "payments-retry-on-error-dlt";
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private KafkaListenerEndpointRegistry kafkaListenerEndpointRegistry;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private KafkaTemplate<String, Payment> kafkaProducer;
|
||||||
|
|
||||||
|
@SpyBean
|
||||||
|
private PaymentListenerDltRetryOnError paymentsConsumer;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
// wait for embedded Kafka
|
||||||
|
for (MessageListenerContainer messageListenerContainer : kafkaListenerEndpointRegistry.getListenerContainers()) {
|
||||||
|
ContainerTestUtils.waitForAssignment(messageListenerContainer, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenMainConsumerSucceeds_thenNoDltMessage() throws Exception {
|
||||||
|
CountDownLatch mainTopicCountDownLatch = new CountDownLatch(1);
|
||||||
|
|
||||||
|
doAnswer(invocation -> {
|
||||||
|
mainTopicCountDownLatch.countDown();
|
||||||
|
return null;
|
||||||
|
}).when(paymentsConsumer)
|
||||||
|
.handlePayment(any(), any());
|
||||||
|
|
||||||
|
kafkaProducer.send(TOPIC, createPayment("dlt-retry-main"));
|
||||||
|
|
||||||
|
assertThat(mainTopicCountDownLatch.await(5, TimeUnit.SECONDS)).isTrue();
|
||||||
|
verify(paymentsConsumer, never()).handleDltPayment(any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenDltConsumerFails_thenDltConsumerRetriesMessage() throws Exception {
|
||||||
|
CountDownLatch mainTopicCountDownLatch = new CountDownLatch(1);
|
||||||
|
CountDownLatch dlTTopicCountDownLatch = new CountDownLatch(3);
|
||||||
|
|
||||||
|
doAnswer(invocation -> {
|
||||||
|
mainTopicCountDownLatch.countDown();
|
||||||
|
throw new Exception("Simulating error in main consumer");
|
||||||
|
}).when(paymentsConsumer)
|
||||||
|
.handlePayment(any(), any());
|
||||||
|
|
||||||
|
doAnswer(invocation -> {
|
||||||
|
dlTTopicCountDownLatch.countDown();
|
||||||
|
throw new Exception("Simulating error in dlt consumer");
|
||||||
|
}).when(paymentsConsumer)
|
||||||
|
.handleDltPayment(any(), any());
|
||||||
|
|
||||||
|
kafkaProducer.send(TOPIC, createPayment("dlt-retry"));
|
||||||
|
|
||||||
|
assertThat(mainTopicCountDownLatch.await(5, TimeUnit.SECONDS)).isTrue();
|
||||||
|
assertThat(dlTTopicCountDownLatch.await(5, TimeUnit.SECONDS)).isTrue();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
package com.baeldung.spring.kafka.dlt;
|
||||||
|
|
||||||
|
import static com.baeldung.spring.kafka.dlt.PaymentTestUtils.createPayment;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.doAnswer;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
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.mock.mockito.SpyBean;
|
||||||
|
import org.springframework.kafka.config.KafkaListenerEndpointRegistry;
|
||||||
|
import org.springframework.kafka.core.KafkaTemplate;
|
||||||
|
import org.springframework.kafka.listener.MessageListenerContainer;
|
||||||
|
import org.springframework.kafka.test.context.EmbeddedKafka;
|
||||||
|
import org.springframework.kafka.test.utils.ContainerTestUtils;
|
||||||
|
|
||||||
|
import com.baeldung.spring.kafka.dlt.listener.PaymentListenerNoDlt;
|
||||||
|
|
||||||
|
@SpringBootTest(classes = KafkaDltApplication.class)
|
||||||
|
@EmbeddedKafka(partitions = 1, brokerProperties = { "listeners=PLAINTEXT://localhost:9099", "port=9099" })
|
||||||
|
public class KafkaNoDltIntegrationTest {
|
||||||
|
private static final String TOPIC = "payments-no-dlt";
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private KafkaListenerEndpointRegistry kafkaListenerEndpointRegistry;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private KafkaTemplate<String, Payment> kafkaProducer;
|
||||||
|
|
||||||
|
@SpyBean
|
||||||
|
private PaymentListenerNoDlt paymentsConsumer;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
// wait for embedded Kafka
|
||||||
|
for (MessageListenerContainer messageListenerContainer : kafkaListenerEndpointRegistry.getListenerContainers()) {
|
||||||
|
ContainerTestUtils.waitForAssignment(messageListenerContainer, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenMainConsumerSucceeds_thenNoDltMessage() throws Exception {
|
||||||
|
CountDownLatch mainTopicCountDownLatch = new CountDownLatch(1);
|
||||||
|
|
||||||
|
doAnswer(invocation -> {
|
||||||
|
mainTopicCountDownLatch.countDown();
|
||||||
|
return null;
|
||||||
|
}).when(paymentsConsumer)
|
||||||
|
.handlePayment(any(), any());
|
||||||
|
|
||||||
|
kafkaProducer.send(TOPIC, createPayment("no-dlt-main"));
|
||||||
|
|
||||||
|
assertThat(mainTopicCountDownLatch.await(5, TimeUnit.SECONDS)).isTrue();
|
||||||
|
verify(paymentsConsumer, never()).handleDltPayment(any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenMainConsumerFails_thenDltConsumerDoesNotReceiveMessage() throws Exception {
|
||||||
|
CountDownLatch mainTopicCountDownLatch = new CountDownLatch(1);
|
||||||
|
CountDownLatch dlTTopicCountDownLatch = new CountDownLatch(1);
|
||||||
|
|
||||||
|
doAnswer(invocation -> {
|
||||||
|
mainTopicCountDownLatch.countDown();
|
||||||
|
throw new Exception("Simulating error in main consumer");
|
||||||
|
}).when(paymentsConsumer)
|
||||||
|
.handlePayment(any(), any());
|
||||||
|
|
||||||
|
doAnswer(invocation -> {
|
||||||
|
dlTTopicCountDownLatch.countDown();
|
||||||
|
return null;
|
||||||
|
}).when(paymentsConsumer)
|
||||||
|
.handleDltPayment(any(), any());
|
||||||
|
|
||||||
|
kafkaProducer.send(TOPIC, createPayment("no-dlt"));
|
||||||
|
|
||||||
|
assertThat(mainTopicCountDownLatch.await(5, TimeUnit.SECONDS)).isTrue();
|
||||||
|
assertThat(dlTTopicCountDownLatch.await(5, TimeUnit.SECONDS)).isFalse();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package com.baeldung.spring.kafka.dlt;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Currency;
|
||||||
|
|
||||||
|
class PaymentTestUtils {
|
||||||
|
|
||||||
|
static Payment createPayment(String reference) {
|
||||||
|
Payment payment = new Payment();
|
||||||
|
payment.setAmount(BigDecimal.valueOf(71));
|
||||||
|
payment.setCurrency(Currency.getInstance("GBP"));
|
||||||
|
payment.setReference(reference);
|
||||||
|
return payment;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue