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.yml
que será nossa "infraestrutura de delivery":
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
# 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:
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()
}
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
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...
}
# 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
@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:
@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
@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
# 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:8080
para 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 Kafka | Analogia Delivery | No Código |
---|---|---|
Producer | Restaurante criando pedidos | RestauranteService.criarPedido() |
Topic | Categoria de comida (Pizza, Burger) | pedidos-pizza, pedidos-burger |
Consumer | Entregador especializado | @KafkaListener methods |
Consumer Group | Equipe de entregadores por região | groupId = "entregadores-pizza" |
Partition | Diferentes cozinhas do mesmo restaurante | Distribuição automática pelo Kafka |