Apache Kafka:

Construindo o iFood dos Dados com Docker e Java

Imagine que você precisa gerenciar milhares de pedidos de comida simultaneamente, com diferentes restaurantes, entregadores e clientes. Como garantir que nenhum pedido se perca e que tudo chegue no destino certo? O Apache Kafka resolve exatamente isso no contexto de dados e vamos construir nosso próprio "iFood" para entender na prática!

🚚 A Analogia: Kafka como Central de Delivery

Antes de colocar a mão no código, vamos entender os conceitos do Kafka através de uma analogia que todos conhecem:

🏢 Sistema de Delivery

  • Central iFood: Coordena tudo
  • Restaurantes: Produzem pedidos
  • Categorias: Pizza, Burger, Sushi
  • Entregadores: Consomem pedidos
  • Equipes: Grupos por região

⚙️ Apache Kafka

  • Kafka Cluster: Infraestrutura
  • Producers: Enviam mensagens
  • Topics: Categorias de dados
  • Consumers: Processam mensagens
  • Consumer Groups: Processamento paralelo

🐳 Setup do Ambiente: Nossa Central de Delivery

Vamos criar nosso ambiente Kafka usando Docker. Primeiro, o docker-compose.ymlque será nossa "infraestrutura de delivery":

docker-compose.yml
version: '3.8'
services:
  zookeeper:
    image: confluentinc/cp-zookeeper:7.9.1
    hostname: zookeeper
    container_name: zookeeper
    ports:
      - "2181:2181"
    environment:
      ZOOKEEPER_CLIENT_PORT: 2181
      ZOOKEEPER_TICK_TIME: 2000

  kafka:
    image: confluentinc/cp-kafka:7.9.1
    hostname: kafka
    container_name: kafka
    depends_on:
      - zookeeper
    ports:
      - "9092:9092"
    environment:
      KAFKA_BROKER_ID: 1
      KAFKA_ZOOKEEPER_CONNECT: 'zookeeper:2181'
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9092
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1

  kafka-ui:
    image: provectuslabs/kafka-ui:latest
    container_name: kafka-ui
    depends_on:
      - kafka
    ports:
      - "8080:8080"
    environment:
      KAFKA_CLUSTERS_0_NAME: local
      KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:29092
Terminal - Subindo a Infraestrutura
# Clone ou crie o projeto
mkdir kafka-delivery && cd kafka-delivery

# Suba os serviços
docker-compose up -d

# Verifique se tudo está rodando
docker-compose ps

# Acesse a UI do Kafka em http://localhost:8080

🍕 Criando o Restaurante: Producer em Java

Agora vamos criar nosso "restaurante" - um Producer que enviará pedidos para o Kafka. Primeiro, a estrutura do projeto Spring Boot com Gradle:

build.gradle - Dependências
plugins {
    id 'org.springframework.boot' version '3.1.5'
    id 'io.spring.dependency-management' version '1.1.3'
    id 'java'
}

group = 'com.delivery'
version = '0.0.1-SNAPSHOT'
java.sourceCompatibility = JavaVersion.VERSION_17

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.kafka:spring-kafka'
    implementation 'com.fasterxml.jackson.core:jackson-databind'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.springframework.kafka:spring-kafka-test'
}

tasks.named('test') {
    useJUnitPlatform()
}
src/main/resources/application.yml
spring:
  kafka:
    bootstrap-servers: localhost:9092
    producer:
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
      properties:
        spring.json.type.mapping: pedido:com.delivery.model.Pedido
    consumer:
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer
      properties:
        spring.json.type.mapping: pedido:com.delivery.model.Pedido
        spring.json.trusted.packages: "com.delivery.model"

server:
  port: 8082 # para não conflitar com kafka-ui

logging:
  level:
    com.delivery: DEBUG
    org.springframework.kafka: INFO
src/main/java/model/Pedido.java
package com.delivery.model;

import com.fasterxml.jackson.annotation.JsonFormat;
import java.time.LocalDateTime;
import java.util.UUID;

public class Pedido {
    private String id;
    private String restaurante;
    private String cliente;
    private String item;
    private Double preco;
    private String status;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime timestamp;

    // Construtor padrão
    public Pedido() {
        this.id = UUID.randomUUID().toString();
        this.timestamp = LocalDateTime.now();
        this.status = "CRIADO";
    }

    // Getters e Setters...
}
Terminal - Executando com Gradle
# 1. Certifique-se que o Kafka está rodando
docker-compose ps

# 2. Execute a aplicação Spring Boot com Gradle
./gradlew bootRun

# Ou para Windows:
gradlew.bat bootRun

# 3. Para buildar o projeto:
./gradlew build

# 4. Para executar os testes:
./gradlew test
src/main/java/service/RestauranteService.java
@Service
public class RestauranteService {

    @Autowired
    private KafkaTemplate<String, Object> kafkaTemplate;

    // Nosso "cardápio" de restaurantes
    private final List<String> restaurantes = Arrays.asList(
        "Pizza Express", "Burger King", "Sushi House", "Taco Bell"
    );

    public void criarPedido(String restaurante, String cliente, String item, Double preco) {
        Pedido pedido = new Pedido();
        pedido.setRestaurante(restaurante);
        pedido.setCliente(cliente);
        pedido.setItem(item);
        pedido.setPreco(preco);

        // Determina o "tipo de comida" (topic) baseado no restaurante
        String topicName = determinarTopic(restaurante);

        // Envia o pedido para a "central de delivery" (Kafka)
        kafkaTemplate.send(topicName, pedido.getId(), pedido);
        System.out.println("Pedido enviado: " + pedido.getId() + " para topic: " + topicName);
    }

    private String determinarTopic(String restaurante) {
        if (restaurante.toLowerCase().contains("pizza")) return "pedidos-pizza";
        if (restaurante.toLowerCase().contains("burger")) return "pedidos-burger";
        if (restaurante.toLowerCase().contains("sushi")) return "pedidos-sushi";
        return "pedidos-diversos";
    }
}

🚚 Criando os Entregadores: Consumers

Agora vamos criar nossos "entregadores" - os Consumers que processarão os pedidos de cada categoria:

src/main/java/service/EntregadorService.java
@Service
public class EntregadorService {

    // Entregadores especializados em Pizza
    @KafkaListener(topics = "pedidos-pizza", groupId = "entregadores-pizza")
    public void processarPedidoPizza(Pedido pedido) {
        System.out.println("Entregador de Pizza pegou pedido: " + pedido.getId());
        simularEntrega(pedido, "Pizza");
    }

    // Entregadores especializados em Burger
    @KafkaListener(topics = "pedidos-burger", groupId = "entregadores-burger")
    public void processarPedidoBurger(Pedido pedido) {
        System.out.println("Entregador de Burger pegou pedido: " + pedido.getId());
        simularEntrega(pedido, "Burger");
    }

    // Entregadores especializados em Sushi
    @KafkaListener(topics = "pedidos-sushi", groupId = "entregadores-sushi")
    public void processarPedidoSushi(Pedido pedido) {
        System.out.println("Entregador de Sushi pegou pedido: " + pedido.getId());
        simularEntrega(pedido, "Sushi");
    }

    private void simularEntrega(Pedido pedido, String tipo) {
        try {
            // Simula tempo de entrega (diferentes para cada tipo)
            int tempoEntrega = tipo.equals("Sushi") ? 3000 : 2000;
            Thread.sleep(tempoEntrega);

            System.out.println("Pedido " + pedido.getId() + " de " + tipo + " entregue para " + pedido.getCliente());
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

🎮 API para Simular Pedidos

src/main/java/controller/DeliveryController.java
@RestController
@RequestMapping("/api/delivery")
public class DeliveryController {

    @Autowired
    private RestauranteService restauranteService;

    @PostMapping("/pedido")
    public ResponseEntity<String> criarPedido(
            @RequestParam String restaurante,
            @RequestParam String cliente,
            @RequestParam String item,
            @RequestParam Double preco) {

        restauranteService.criarPedido(restaurante, cliente, item, preco);
        return ResponseEntity.ok("Pedido criado com sucesso!");
    }

    @PostMapping("/pedidos-massa")
    public ResponseEntity<String> criarPedidosEmMassa() {
        // Simula rush hour!
        List<String> clientes = Arrays.asList("João", "Maria", "Pedro", "Ana");
        Random random = new Random();

        for (int i = 0; i < 10; i++) {
            String cliente = clientes.get(random.nextInt(clientes.size()));
            restauranteService.criarPedido("Pizza Express", cliente, "Pizza Margherita", 25.90);
            restauranteService.criarPedido("Burger King", cliente, "Whopper", 18.50);
            restauranteService.criarPedido("Sushi House", cliente, "Combo Salmão", 45.00);
        }

        return ResponseEntity.ok("30 pedidos criados para simular rush hour!");
    }
}

🚀 Testando Nossa Central de Delivery

Terminal - Executando o Projeto
# 1. Certifique-se que o Kafka está rodando
docker-compose ps

# 2. Execute a aplicação Spring Boot
./mvnw spring-boot:run

# 3. Teste um pedido individual
curl -X POST "http://localhost:8080/api/delivery/pedido" \
  -d "restaurante=Pizza Express&cliente=João&item=Pizza Margherita&preco=25.90"

# 4. Simule rush hour (30 pedidos!)
curl -X POST "http://localhost:8080/api/delivery/pedidos-massa"

🎯 O que você verá acontecendo:

  • Logs do Producer: Restaurantes enviando pedidos para topics específicos
  • Logs dos Consumers: Entregadores especializados pegando pedidos
  • Processamento Paralelo: Múltiplos pedidos sendo entregues simultaneamente
  • Kafka UI: Visualização em tempo real no http://localhost:8080

📊 Monitoramento: Acompanhando as Entregas

Acesse o Kafka UI em http://localhost:8080para ver em tempo real:

📈 Métricas que você verá:

  • Topics: pedidos-pizza, pedidos-burger, pedidos-sushi
  • Partitions: Como os pedidos são distribuídos
  • Consumer Groups: Equipes de entregadores
  • Lag: Pedidos pendentes vs processados

🔍 Insights importantes:

  • Throughput: Quantos pedidos/segundo
  • Latência: Tempo entre criação e entrega
  • Balanceamento: Distribuição entre entregadores
  • Resiliência: Como o sistema lida com falhas

🎯 Conceitos Kafka na Prática

Conceito KafkaAnalogia DeliveryNo Código
ProducerRestaurante criando pedidosRestauranteService.criarPedido()
TopicCategoria de comida (Pizza, Burger)pedidos-pizza, pedidos-burger
ConsumerEntregador especializado@KafkaListener methods
Consumer GroupEquipe de entregadores por regiãogroupId = "entregadores-pizza"
PartitionDiferentes cozinhas do mesmo restauranteDistribuição automática pelo Kafka
🎉 Parabéns! Você acabou de construir uma "central de delivery" completa usando Kafka! Agora você entende como sistemas distribuídos modernos processam milhões de eventos por segundo - do pedido à entrega, tudo orquestrado de forma resiliente e escalável! 🚀
📝 Conteúdo criado por naysinger.tech