DEV Community

Daniel Arrais
Daniel Arrais

Posted on

Criando componentes do Rabbitmq de forma dinâmica no Spring Boot

Esses dias me deparei com o seguinte desafio: listar no properties de um projeto Spring Boot as filas que são utilizadas no projeto e criá-las no RabbitMQ de acordo com essa lista. Até então eu criava as filas uma a uma declarando uma @Bean (Exemplo 1) e assim toda vez que a aplicação é iniciada as devidas filas são criadas, caso já não existam. O problema aqui é a necessidade de alterar código toda vez que for necessário criar uma nova fila.

Exemplo 1

@Bean  
public Queue example1Queue() {  
    log.info("example1Queue created");  
    return QueueBuilder.durable("example1Queue").build();  
}

@Bean  
public Queue example2Queue() {  
    log.info("example2Queue created");  
    return QueueBuilder.durable("example2Queue").build();  
}
Enter fullscreen mode Exit fullscreen mode

Criar uma @Bean que retorna uma Queue é o jeito básico de criar filas com o Spring, mas se precisamos de algo mais flexível e dinâmico precisamos outro meio... precisamos da classe Declarables.

Declarables

A ideia geral do desafio era obter flexibilidade e centralizar as informações das filas em uma lista no arquivo properties do projeto, sem ser necessário alterar código Java para configurar uma nova fila. Nesse contexto seria interessante poder criar várias filas de uma vez só e descobri que isso é possível, utilizando a classe Declarables . Declarables é uma coleção que suporta implementações de Declarable, como Bind, Queue e Exchange (e creio que outras). Para utilizá-la seguimos a mesma lógica do exemplo 1, declarando uma @Bean, só que em vez de retornar um componente especifico, retornamos uma instância de Declarables com tudo que precisamos criar. No exemplo 2 eu recrio o exemplo 1 usando Declarables.

Exemplo 2

@Bean  
public Declarables queues() {  
    return buildQueues();  
}

private Declarables buildQueues() {  
    return new Declarables(  
        QueueBuilder.durable("example1Queue")  
                    .build(),  
        QueueBuilder.durable("example2Queue")  
                    .build()  
    );
}
Enter fullscreen mode Exit fullscreen mode

Vejam como é interessante, simples e nos abre possibilidades, como pegar uma lista de filas de um lugar qualquer, iterá-las e montar as filas. É isso que vamos fazer! Mas cuidado! Alerto que devemos utilizar responsabilidade. Por exemplo no projeto aqui da firma eu criei muitas coisas com ela, Queues, Binds e Exchanges, mas separadamente, para não virar bagunça. Flexibilidade deve ser adicionada com cuidado para não virar zorra e deixar o projeto impossível de manter.

Agora que sabemos utilizar a classe Declarables vamos guardar nossa lista de filas no properties do projeto.

Criando a lista de filas no arquivo properties

Agora vamos guardar as filas no arquivo properties (usando yaml) do projeto para podermos recuperar elas na nossa Bean e criá-las. Para isso eu pensei na estrutura do exemplo 3.

Exemplo 3

queues:  
    example1Queue:  
        name: example1Queue  
        tll: 1000
    example2Queue:  
        name: example2Queue
        tll: 1000
Enter fullscreen mode Exit fullscreen mode

Usei a estrutura de keys e valores mas também poderíamos usar uma lista de strings (exemplo 4) com os nomes das filas ou uma lista de objetos sem chave (exemplo 5). Adotei a estrutura do exemplo 3 para nos permitir referenciar as filas em outros locais, como nas anotações @RabbitListener (exemplo: @RabbitListener(queues = "${queues.example1Queue.name}")). A estrutura que sugeri (e a do exemplo 5) também permite adicionar outras propriedades pertinente as filas, como o ttl .

Exemplo 4

queues:  example1Queue, example2Queue
Enter fullscreen mode Exit fullscreen mode

Exemplo 5

queues: 
    - 
        name: example1Queue  
        tll: 1000
    -
        name: example2Queue
        tll: 1000
Enter fullscreen mode Exit fullscreen mode

Agora que temos nossas filas registradas no properties devemos mapear elas para uma classe para podermos recuperar as informações onde precisamos.

Recuperando as configurações da filas

Para recuperar os valores da lista de filas vamos criar a classe QueuesProperties (exemplo 6). Nela temos uma classe que representa a Queue e temos um Map de Queue. A classe Queue tem os atributos name e tll. Na propriedade queues eu tentei utilizar lista mas não deu certo, então criei um getQueues() que retorna uma lista.

Exemplo 6

@Data 
public class QueuesProperties {  
    private Map<String, Queue> queues;  

    public Collection<Queue> getQueues() {  
        if (CollectionUtils.isEmpty(queues)) return new ArrayList<>();  
        return queues.values();  
    }

    @Data   
    public static class Queue {   
        private String name;  
        private int tll;  
    } 
}
Enter fullscreen mode Exit fullscreen mode

Depois de montarmos nosso Pojo para armazenas as filas, devemos dizer ao Spring que essa classe representa propriedades, para podemos utilizar a injeção de dependências. Para isso utilizamos as anotações @Component e @ConfigurationProperties("propertie") (exemplo 7). Na @ConfigurationProperties teríamos que informar o prefixo da nossa propriedade, mas como ela está no topo da hierarquia, não precisamos.

Exemplo 7

@Data 
@Component    
@ConfigurationProperties
public class QueuesProperties {  
    ...
}
Enter fullscreen mode Exit fullscreen mode

Também podemos validar nossas anotações utilizando o Spring Validation e impedir que a aplicação seja executada caso esteja faltando alguma informação. Sabemos por exemplo que o atributo name da Queue deve ser obrigatório, então podemos anotar ele com @NotBlank e anotamos as classes com @Validated, para que o Spring entenda que deve validar elas (exemplo 8).

Exemplo 8

...
@Data  
@Validated  
public static class Queue {  
    @NotBlank  
    private String name;  
    private int tll;  
}
...
Enter fullscreen mode Exit fullscreen mode

Feito tudo isso, nossa classe completa ficou assim:

Exemplo 9 (QueuesProperties.java)

@Data  
@Validated  
@Component    
@ConfigurationProperties
public class QueuesProperties {  

    private Map<String, Queue> queues;  

    public Collection<Queue> getQueues() {  
        if (CollectionUtils.isEmpty(queues)) return new ArrayList<>();  
        return queues.values();  
    }  

    @Data  
    @Validated  
    public static class Queue {  
        @NotBlank  
        private String name;  
        private int tll;  
    }  
}
Enter fullscreen mode Exit fullscreen mode

Agora podemos injetar nossa classe de propriedades usando injeção de dependência do Spring em qualquer classe que tenha suporte.

Criando nossas filas

Para criar nossas filas vamos criar uma classe anotada com @Component chamada QueuesInitializer, onde declararemos nossa @Bean de criação de filas. Nela injetaremos nossas propriedades utilizando uma variável final (é necessário criar um construtor para que o Spring possa instanciá-la, para isso eu uso a anotação @RequiredArgsConstructor do Lombok).

Depois declaramos a nossa @Bean que retorna uma Declarables com nossas filas construídas. Como mostra o exemplo 10:

Exemplo 10 (QueuesInitializer.java)

@Component  
@RequiredArgsConstructor  
public class QueuesInitializer {  

    private final QueuesProperties queuesProperties;  

    @Bean  
    public Declarables queues() {  
        return buildQueues();  
    }  

    private Declarables buildQueues() {  
        List<Queue> queues = queuesProperties.getQueues().stream().map(queue -> {  
            return QueueBuilder.durable(queue.getName())  
                               .ttl(queue.getTll())  
                               .build();  
        }).collect(Collectors.toList());  

        return new Declarables(queues);  
    }  

}
Enter fullscreen mode Exit fullscreen mode

Pronto, é isso, simples e fácil.

Repositório

Você pode encontrar o código apresentado neste repositório.

Top comments (0)