RabbitEventConfig.java

package org.darkroomlibrary.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.config.RetryInterceptorBuilder;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.retry.MessageRecoverer;
import org.springframework.amqp.rabbit.retry.RepublishMessageRecoverer;
import org.springframework.amqp.support.converter.DefaultJackson2JavaTypeMapper;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.amqp.SimpleRabbitListenerContainerFactoryConfigurer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConditionalOnProperty(name = "middleware.rabbit.enabled", havingValue = "true")
public class RabbitEventConfig {

    @Value("${middleware.rabbit.exchange:dark.room.library.events}")
    private String exchangeName;

    @Value("${middleware.rabbit.notification-routing-key:notification.task}")
    private String notificationRoutingKey;

    @Value("${middleware.rabbit.book-returned-routing-key:book.returned}")
    private String bookReturnedRoutingKey;

    @Value("${middleware.rabbit.notification-task-queue:dark.room.library.notification-task}")
    private String notificationTaskQueueName;

    @Value("${middleware.rabbit.book-returned-queue:dark.room.library.book-returned}")
    private String bookReturnedQueueName;

    @Value("${middleware.rabbit.dead-letter-exchange:dark.room.library.dead-letter}")
    private String deadLetterExchangeName;

    @Value("${middleware.rabbit.notification-dead-letter-queue:dark.room.library.notification-task.dead}")
    private String notificationDeadLetterQueueName;

    @Value("${middleware.rabbit.book-returned-dead-letter-queue:dark.room.library.book-returned.dead}")
    private String bookReturnedDeadLetterQueueName;

    @Value("${middleware.rabbit.notification-dead-letter-routing-key:dead.notification.task}")
    private String notificationDeadLetterRoutingKey;

    @Value("${middleware.rabbit.book-returned-dead-letter-routing-key:dead.book.returned}")
    private String bookReturnedDeadLetterRoutingKey;

    @Bean
    public DirectExchange domainEventExchange() {
        return new DirectExchange(exchangeName, true, false);
    }

    @Bean
    public MessageConverter messageConverter(ObjectMapper objectMapper) {
        Jackson2JsonMessageConverter converter = new Jackson2JsonMessageConverter(objectMapper);
        DefaultJackson2JavaTypeMapper typeMapper = new DefaultJackson2JavaTypeMapper();
        typeMapper.setTrustedPackages("org.darkroomlibrary.domain.model");
        converter.setJavaTypeMapper(typeMapper);
        return converter;
    }

    @Bean(name = "rabbitListenerContainerFactory")
    public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(
            SimpleRabbitListenerContainerFactoryConfigurer configurer,
            ConnectionFactory connectionFactory,
            MessageRecoverer poisonMessageRecoverer,
            @Value("${middleware.rabbit.recovery-interval-ms:30000}") long recoveryIntervalMs) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        configurer.configure(factory, connectionFactory);
        factory.setRecoveryInterval(recoveryIntervalMs);
        factory.setDefaultRequeueRejected(false);
        factory.setContainerCustomizer(container -> container.setAdviceChain(
                RetryInterceptorBuilder.stateless()
                        .maxAttempts(1)
                        .recoverer(poisonMessageRecoverer)
                        .build()));
        return factory;
    }

    @Bean
    public MessageRecoverer poisonMessageRecoverer(RabbitTemplate rabbitTemplate) {
        RepublishMessageRecoverer recoverer =
                new RepublishMessageRecoverer(rabbitTemplate, deadLetterExchangeName);
        recoverer.errorRoutingKeyPrefix("dead.");
        return recoverer;
    }

    @Bean
    public Queue notificationTaskQueue() {
        return QueueBuilder.durable(notificationTaskQueueName).build();
    }

    @Bean
    public Queue bookReturnedQueue() {
        return QueueBuilder.durable(bookReturnedQueueName).build();
    }

    @Bean
    public DirectExchange deadLetterExchange() {
        return new DirectExchange(deadLetterExchangeName, true, false);
    }

    @Bean
    public Queue notificationDeadLetterQueue() {
        return QueueBuilder.durable(notificationDeadLetterQueueName).build();
    }

    @Bean
    public Queue bookReturnedDeadLetterQueue() {
        return QueueBuilder.durable(bookReturnedDeadLetterQueueName).build();
    }

    @Bean
    public Binding notificationTaskBinding(Queue notificationTaskQueue, DirectExchange domainEventExchange) {
        return BindingBuilder.bind(notificationTaskQueue).to(domainEventExchange).with(notificationRoutingKey);
    }

    @Bean
    public Binding bookReturnedBinding(Queue bookReturnedQueue, DirectExchange domainEventExchange) {
        return BindingBuilder.bind(bookReturnedQueue).to(domainEventExchange).with(bookReturnedRoutingKey);
    }

    @Bean
    public Binding notificationDeadLetterBinding(
            Queue notificationDeadLetterQueue,
            DirectExchange deadLetterExchange) {
        return BindingBuilder.bind(notificationDeadLetterQueue)
                .to(deadLetterExchange)
                .with(notificationDeadLetterRoutingKey);
    }

    @Bean
    public Binding bookReturnedDeadLetterBinding(
            Queue bookReturnedDeadLetterQueue,
            DirectExchange deadLetterExchange) {
        return BindingBuilder.bind(bookReturnedDeadLetterQueue)
                .to(deadLetterExchange)
                .with(bookReturnedDeadLetterRoutingKey);
    }
}