Introdução

A comunicação assíncrona é um elemento crucial na construção de sistemas informacionais modernos, permitindo a troca de mensagens e eventos entre diferentes componentes de forma eficiente e escalável. Neste tutorial, iremos explorar duas das ferramentas mais populares para implementar essa comunicação: Apache Kafka e RabbitMQ. O Apache Kafka é uma plataforma de streaming que permite a publicação e a assinatura de fluxos de registros em tempo real, enquanto o RabbitMQ é um broker de mensagens que facilita o envio e recebimento de mensagens entre aplicações. Ao final deste tutorial, você terá uma compreensão sólida das diferenças entre essas duas tecnologias, sabendo quando e como utilizar cada uma delas em uma aplicação Java utilizando o framework Spring Boot.

Etapas

  1. Configuração do Ambiente de Desenvolvimento

    Certifique-se de ter o JDK (Java Development Kit) e o Maven instalados em sua máquina. Verifique as versões instaladas usando os comandos `java -version` e `mvn -version`. Para este tutorial, utilizaremos o Apache Kafka e RabbitMQ, então, instale também o Docker para facilitar a execução desses serviços.

    commands
    # Verificar versões instaladas
    java -version
    mvn -version

  2. Criação do Projeto Spring Boot

    Utilize o Spring Initializr para gerar um novo projeto Spring Boot com as dependências necessárias. Configure o projeto com as opções: Project: Maven, Language: Java, Spring Boot: última versão, Packaging: Jar, Java: 11 ou superior. Adicione as dependências ‘Spring Web’, ‘Spring for Apache Kafka’ e ‘Spring AMQP’. Baixe e descompacte o projeto em seu ambiente de trabalho.

    pom.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0</modelVersion>
      <groupId>com.example</groupId>
      <artifactId>async-communication</artifactId>
      <version>0.0.1-SNAPSHOT</version>
      <name>async-communication</name>
      <description>Exemplo de comunicação assíncrona com Kafka e RabbitMQ</description>
      <properties>
        <java.version>11</java.version>
      </properties>
      <dependencies>
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
          <groupId>org.springframework.kafka</groupId>
          <artifactId>spring-kafka</artifactId>
        </dependency>
        <dependency>
          <groupId>org.springframework.amqp</groupId>
          <artifactId>spring-amqp</artifactId>
        </dependency>
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-test</artifactId>
          <scope>test</scope>
        </dependency>
      </dependencies>
      <build>
        <plugins>
          <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
          </plugin>
        </plugins>
      </build>
    </project>

  3. Executando o RabbitMQ e Kafka com Docker

    Utilize o Docker para executar instâncias do RabbitMQ e Kafka. Crie um arquivo `docker-compose.yml` com a configuração necessária para ambos os serviços.

    docker-compose.yml
    version: '3'
    services:
      rabbitmq:
        image: rabbitmq:3-management
        ports:
          - "5672:5672"
          - "15672:15672"
      kafka:
        image: wurstmeister/kafka:latest
        ports:
          - "9092:9092"
        environment:
          KAFKA_ADVERTISED_LISTENERS: INSIDE://kafka:9092,OUTSIDE://localhost:9092
          KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INSIDE:PLAINTEXT,OUTSIDE:PLAINTEXT
          KAFKA_LISTENERS: INSIDE://0.0.0.0:9092,OUTSIDE://0.0.0.0:9092
          KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      zookeeper:
        image: wurstmeister/zookeeper:latest
        ports:
          - "2181:2181"

  4. Configuração do Kafka Producer e Consumer

    Crie classes para o produtor e consumidor do Kafka. A classe `KafkaProducerService` enviará mensagens para um tópico, enquanto a classe `KafkaConsumerService` receberá essas mensagens.

    KafkaProducerService.java
    package com.example.asynccommunication.service;
    
    import org.apache.kafka.clients.producer.KafkaProducer;
    import org.apache.kafka.clients.producer.ProducerRecord;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Service;
    
    import java.util.Properties;
    
    @Service
    public class KafkaProducerService {
    
        private final KafkaProducer<String, String> producer;
    
        public KafkaProducerService(@Value("${kafka.bootstrap.servers}") String bootstrapServers) {
            Properties props = new Properties();
            props.put("bootstrap.servers", bootstrapServers);
            props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
            props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
            this.producer = new KafkaProducer<>(props);
        }
    
        public void sendMessage(String topic, String message) {
            producer.send(new ProducerRecord<>(topic, message));
        }
    }
    

    KafkaConsumerService.java
    package com.example.asynccommunication.service;
    
    import org.springframework.kafka.annotation.KafkaListener;
    import org.springframework.stereotype.Service;
    
    @Service
    public class KafkaConsumerService {
    
        @KafkaListener(topics = "my-topic", groupId = "group_id")
        public void listen(String message) {
            System.out.println("Received message: " + message);
        }
    }
    

  5. Configuração do RabbitMQ Producer e Consumer

    Implemente as classes para o produtor e consumidor do RabbitMQ. A classe `RabbitMQProducerService` enviará mensagens para uma fila, enquanto a classe `RabbitMQConsumerService` irá receber essas mensagens.

    RabbitMQProducerService.java
    package com.example.asynccommunication.service;
    
    import org.springframework.amqp.rabbit.core.RabbitTemplate;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    @Service
    public class RabbitMQProducerService {
    
        @Autowired
        private RabbitTemplate rabbitTemplate;
    
        public void sendMessage(String queue, String message) {
            rabbitTemplate.convertAndSend(queue, message);
        }
    }
    

    RabbitMQConsumerService.java
    package com.example.asynccommunication.service;
    
    import org.springframework.amqp.rabbit.annotation.RabbitListener;
    import org.springframework.stereotype.Service;
    
    @Service
    public class RabbitMQConsumerService {
    
        @RabbitListener(queues = "my-queue")
        public void listen(String message) {
            System.out.println("Received message from RabbitMQ: " + message);
        }
    }
    

  6. Implementação do Controlador

    Crie um controlador para expor endpoints que permitam o envio de mensagens tanto para o RabbitMQ quanto para o Kafka.

    MessageController.java
    package com.example.asynccommunication.controller;
    
    import com.example.asynccommunication.service.KafkaProducerService;
    import com.example.asynccommunication.service.RabbitMQProducerService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.*;
    
    @RestController
    @RequestMapping("/api/messages")
    public class MessageController {
    
        @Autowired
        private KafkaProducerService kafkaProducerService;
    
        @Autowired
        private RabbitMQProducerService rabbitMQProducerService;
    
        @PostMapping("/kafka")
        public void sendKafkaMessage(@RequestBody String message) {
            kafkaProducerService.sendMessage("my-topic", message);
        }
    
        @PostMapping("/rabbitmq")
        public void sendRabbitMQMessage(@RequestBody String message) {
            rabbitMQProducerService.sendMessage("my-queue", message);
        }
    }
    

  7. Implementação de Testes Unitários

    Crie testes unitários para assegurar que as funcionalidades de produção e consumo de mensagens estão funcionando corretamente.

    MessageControllerTest.java
    package com.example.asynccommunication.controller;
    
    import com.example.asynccommunication.service.KafkaProducerService;
    import com.example.asynccommunication.service.RabbitMQProducerService;
    import org.junit.jupiter.api.Test;
    import org.mockito.InjectMocks;
    import org.mockito.Mock;
    import org.mockito.MockitoAnnotations;
    
    import static org.mockito.Mockito.verify;
    
    public class MessageControllerTest {
    
        @InjectMocks
        private MessageController messageController;
    
        @Mock
        private KafkaProducerService kafkaProducerService;
    
        @Mock
        private RabbitMQProducerService rabbitMQProducerService;
    
        @BeforeEach
        public void init() {
            MockitoAnnotations.openMocks(this);
        }
    
        @Test
        public void testSendKafkaMessage() {
            String message = "Hello Kafka";
            messageController.sendKafkaMessage(message);
            verify(kafkaProducerService).sendMessage("my-topic", message);
        }
    
        @Test
        public void testSendRabbitMQMessage() {
            String message = "Hello RabbitMQ";
            messageController.sendRabbitMQMessage(message);
            verify(rabbitMQProducerService).sendMessage("my-queue", message);
        }
    }
    

  8. Executando a Aplicação e Testes

    Use o Maven para compilar e executar a aplicação. Em seguida, utilize ferramentas como Postman ou cURL para testar os endpoints que enviam mensagens para o RabbitMQ e Kafka.

    commands
    # Compilar e executar a aplicação
    mvn spring-boot:run
    # Executar os testes unitários
    mvn test

    curl_examples
    # Enviar uma mensagem para Kafka
    curl -X POST -H "Content-Type: text/plain" --data "Hello Kafka" http://localhost:8080/api/messages/kafka
    # Enviar uma mensagem para RabbitMQ
    curl -X POST -H "Content-Type: text/plain" --data "Hello RabbitMQ" http://localhost:8080/api/messages/rabbitmq

Conclusão

Neste tutorial, exploramos o uso de Apache Kafka e RabbitMQ para implementar comunicação assíncrona em sistemas informacionais modernos. Aprendemos como configurar ambos os serviços usando Docker, como implementar os produtores e consumidores necessários e como expor uma API RESTful para enviar mensagens. Ao final, você tem as ferramentas básicas para integrar comunicação assíncrona em suas aplicações Java utilizando Spring Boot, possibilitando escalabilidade e eficiência na troca de mensagens entre serviços.

Hashtags

#Java #SpringBoot #ApacheKafka #RabbitMQ #ComunicaçãoAssíncrona #DesenvolvimentoDeSoftware