Criando componentes do Rabbitmq de forma dinâmica no Spring Boot
Tags: java, springboot, rabbitmq, properties
Published: 2023-05-22 11:59:57

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**
```java
@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();  
}
```

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`](https://docs.spring.io/spring-amqp/api/org/springframework/amqp/core/Declarables.html) . `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**
```java
@Bean  
public Declarables queues() {  
	return buildQueues();  
}

private Declarables buildQueues() {  
	return new Declarables(  
		QueueBuilder.durable("example1Queue")  
					.build(),  
		QueueBuilder.durable("example2Queue")  
					.build()  
	);
}
```

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**
```java
queues:  
	example1Queue:  
		name: example1Queue  
		tll: 1000
	example2Queue:  
		name: example2Queue
		tll: 1000
```

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**
```java
queues:  example1Queue, example2Queue
```

**Exemplo 5**
```java
queues: 
	- 
		name: example1Queue  
		tll: 1000
	-
		name: example2Queue
		tll: 1000
```

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**
```java
@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;  
	} 
}
```

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**
```java
@Data 
@Component    
@ConfigurationProperties
public class QueuesProperties {  
	...
}
```

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**
```java
...
@Data  
@Validated  
public static class Queue {  
	@NotBlank  
	private String name;  
	private int tll;  
}
...
```

Feito tudo isso, nossa classe completa ficou assim:

**Exemplo 9** ([QueuesProperties.java](https://github.com/danielarrais/multiple-queues-rabbit/blob/main/src/main/java/dev/danielarrais/multiplequeuesrabbit/broker/setup/QueuesProperties.java))
```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;  
	}  
}
```

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](https://github.com/danielarrais/multiple-queues-rabbit/blob/main/src/main/java/dev/danielarrais/multiplequeuesrabbit/broker/setup/QueuesInitializer.java))
```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);  
	}  
  
}
```

Pronto, é isso, simples e fácil.

# Repositório
Você pode encontrar o código apresentado neste [repositório](https://github.com/danielarrais/multiple-queues-rabbit).