Skip to content

xssrae/eda-study

Repository files navigation

EDA Study

Projeto de estudo de Arquitetura Orientada a Eventos (EDA) demonstrando comunicação assíncrona entre microsserviços utilizando Kafka, AWS S3 e Spring Boot com Kotlin.

📋 Visão Geral

O projeto é composto por dois microsserviços que se comunicam de forma assíncrona através do Kafka:

  • Producer Service: Expõe uma API REST e publica eventos de pedido no Kafka
  • Consumer Service (Costumer Service): Consome eventos de pedido do Kafka e, opcionalmente, persiste os dados no S3

Arquitetura

┌─────────────────────────────────────────────────────────────┐
│                    Producer Service (:8081)                  │
│  - API REST Spring Boot                                      │
│  - POST /orders → publica OrderEvent no Kafka               │
└─────────────────────────────────────────────────────────────┘
                           │
                    (orders-topic)
                           │
┌─────────────────────────────────────────────────────────────┐
│                  Consumer Service (:8082)                    │
│  - Aplicação Spring Boot                                     │
│  - @KafkaListener no orders-topic                            │
│  - S3StorageService (@Profile("aws") — desativado por padrão)│
└─────────────────────────────────────────────────────────────┘

Infraestrutura (Docker Compose):
- Kafka broker          → porta 9092 (externa) / 29092 (interna)
- Zookeeper             → porta 2181
- Kafka UI              → porta 8080  →  http://localhost:8080
- LocalStack (S3)       → porta 4566

🛠️ Tecnologias

  • Linguagem: Kotlin
  • Framework: Spring Boot 3.2.3
  • Message Broker: Apache Kafka (confluentinc/cp-kafka:7.5.0)
  • AWS SDK: v2 (software.amazon.awssdk, async/await via kotlinx-coroutines-jdk8)
  • Build: Gradle (build.gradle.kts)
  • Java: 21
  • Suporte Assíncrono: Kotlin Coroutines

📦 Pré-requisitos

Verifique as instalações:

java -version
docker --version
docker-compose --version

🚀 Iniciando o Projeto

Passo 1: Subir a Infraestrutura

cd eda-study
docker-compose up -d
docker-compose ps   # todos os serviços devem exibir "Up"
Container Porta Finalidade
zookeeper 2181 Coordenação do Kafka
kafka 9092 Broker (externa / Spring Boot)
kafka 29092 Broker (interna / Docker)
kafka-ui 8080 Interface web → http://localhost:8080
localstack 4566 Emulação do S3

⚠️ Aguarde ~20 segundos após o docker-compose up antes de iniciar os serviços — o Kafka precisa de alguns instantes para inicializar.

Passo 2: Iniciar o Producer Service

cd eda-study/producer-service

# Linux/Mac
./gradlew bootRun

# Windows
gradlew.bat bootRun

O Producer inicia em http://localhost:8081

Passo 3: Iniciar o Consumer Service

Abra um novo terminal:

cd eda-study/costumer-service

# Linux/Mac
./gradlew bootRun

# Windows
gradlew.bat bootRun

O Consumer inicia em http://localhost:8082

📝 Enviando um Evento de Pedido

Via curl

curl -X POST http://localhost:8081/orders \
  -H "Content-Type: application/json" \
  -d '{
    "orderId": "order-001",
    "costumerId": "cliente-123",
    "items": [
      { "productId": "prod-A", "quantity": 2, "price": 49.90 },
      { "productId": "prod-B", "quantity": 1, "price": 99.90 }
    ],
    "totalAmount": 199.70,
    "status": "PENDING"
  }'

Via PowerShell

Invoke-RestMethod -Uri "http://localhost:8081/orders" `
  -Method POST `
  -ContentType "application/json" `
  -Body '{
    "orderId": "order-001",
    "costumerId": "cliente-123",
    "items": [
      { "productId": "prod-A", "quantity": 2, "price": 49.90 },
      { "productId": "prod-B", "quantity": 1, "price": 99.90 }
    ],
    "totalAmount": 199.70,
    "status": "PENDING"
  }'

Resposta esperada:

{
  "message": "Evento de pedido enviado com sucesso"
}

⚠️ Atenção: o campo é costumerId (com o) — deve bater exatamente com o DTO.

Log esperado no Consumer

Após enviar a requisição, o terminal do consumer deve exibir:

✅ Evento recebido!
   orderId   = order-001
   costumerId= cliente-123
   status    = PENDING
   total     = 199.70
   partition = 0 | offset = 0
📦 Processando pedido order-001

Via Kafka UI

  1. Acesse http://localhost:8080
  2. Vá em Topics → orders-topic → Produce Message
  3. Cole o payload JSON e clique em Produce

📁 Estrutura do Projeto

eda-study/
├── docker-compose.yml
├── README.md
│
├── producer-service/
│   ├── src/main/kotlin/com/xssrae/producer_service/
│   │   ├── ProducerServiceApplication.kt
│   │   ├── config/
│   │   │   ├── KafkaConfig.kt          # beans KafkaTemplate + ProducerFactory
│   │   │   └── KafkaTopicConfig.kt     # cria o orders-topic automaticamente
│   │   ├── controller/
│   │   │   └── OrderController.kt      # POST /orders
│   │   ├── domain/
│   │   │   ├── OrderEvent.kt           # data class
│   │   │   ├── OrderItem.kt            # data class
│   │   │   └── OrderStatus.kt          # sealed class
│   │   └── producer/
│   │       └── OrderEventProducer.kt   # suspend fun + coroutines
│   ├── src/main/resources/application.yml
│   ├── src/test/resources/application.yml
│   └── build.gradle.kts
│
├── costumer-service/
│   ├── src/main/kotlin/com/xssrae/costumer_service/
│   │   ├── CostumerServiceApplication.kt
│   │   ├── config/
│   │   │   └── AppConfig.kt            # bean ObjectMapper
│   │   ├── costumer/
│   │   │   └── OrderEventCostumer.kt   # @KafkaListener
│   │   ├── domain/
│   │   │   ├── OrderEvent.kt           # data class (status: String)
│   │   │   └── OrderItem.kt
│   │   └── service/
│   │       └── S3StorageService.kt     # @Profile("aws") — desativado localmente
│   ├── src/main/resources/application.yml
│   ├── src/test/resources/application.yml
│   └── build.gradle.kts
│
└── localstack-data/                    # volume de persistência do LocalStack

🔧 Configuração

Mapa de Portas

Serviço Porta
Kafka UI 8080
Producer Service 8081
Consumer Service 8082
Kafka (externa) 9092
LocalStack S3 4566
Zookeeper 2181

Principais propriedades do application.yml

# Ambos os serviços
spring:
  kafka:
    bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS:localhost:9092}

app:
  kafka:
    topics:
      orders: orders-topic

aws:
  region: ${AWS_REGION:us-east-1}
  s3:
    bucket: ${S3_BUCKET:eda-study-bucket}
    endpoint: ${LOCALSTACK_ENDPOINT:http://localhost:4566}

Ativando a integração com S3

O S3StorageService está anotado com @Profile("aws") e não é carregado por padrão.

Para ativá-lo, inicie o consumer com o profile aws:

./gradlew bootRun --args='--spring.profiles.active=aws'

Certifique-se de que o LocalStack está rodando e o bucket foi criado antes de ativar.

🛑 Encerrando os Serviços

# Para todos os containers Docker
docker-compose down

# Para e remove os volumes (limpa dados do Kafka e LocalStack)
docker-compose down -v

Encerre os serviços Spring Boot com Ctrl+C em cada terminal.

🔍 Solução de Problemas

Consumer não recebe mensagens

  1. Confirme que ambos os serviços usam o mesmo nome de tópico (orders-topic)
  2. Verifique se app.kafka.topics.orders existe no application.yml do consumer
  3. Procure nos logs do consumer por Subscribed to topic(s): orders-topic
  4. Confirme que o @KafkaListener referencia ${app.kafka.topics.orders}

Kafka UI não conecta ao broker

O Kafka UI roda dentro do Docker e precisa usar o listener interno. Certifique-se de que o docker-compose.yml possui:

kafka:
  environment:
    KAFKA_LISTENERS: INTERNAL://0.0.0.0:29092,EXTERNAL://0.0.0.0:9092
    KAFKA_ADVERTISED_LISTENERS: INTERNAL://kafka:29092,EXTERNAL://localhost:9092
    KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT
    KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL

kafka-ui:
  environment:
    KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:29092   # listener interno

Conflito de portas

# Encontrar processo usando uma porta (Windows)
netstat -ano | findstr :8081

# Encerrar pelo PID
taskkill /PID <PID> /F

PlaceholderResolutionException na inicialização

Adicione valores padrão em todas as anotações @Value:

@Value("\${aws.s3.bucket:eda-study-bucket}") private val bucket: String
@Value("\${aws.region:us-east-1}") private val region: String
@Value("\${aws.s3.endpoint:http://localhost:4566}") private val endpoint: String

Bean KafkaTemplate não encontrado

Certifique-se de que o arquivo KafkaConfig.kt existe dentro de config/ e declara os beans producerFactory e kafkaTemplate. Consulte producer-service/config/KafkaConfig.kt.

400 Bad Request no POST /orders

Verifique se o corpo da requisição bate exatamente com os campos do DTO — especialmente costumerId (com o, não customerId).

🚀 Futuras Melhorias

  • Armazenamento via Bucket S3: ativar o S3StorageService no consumer para persistir cada evento consumido como um arquivo JSON no S3, utilizando o LocalStack em desenvolvimento e um bucket real na AWS em produção. A integração já está implementada e desativada via @Profile("aws"), aguardando validação do fluxo completo do Kafka antes de ser habilitada.

📚 Recursos de Aprendizado

📄 Licença

Projeto de estudo — livre para uso e modificação.


Última atualização: Abril de 2026

About

Study project demonstrating asynchronous communication between microservices using Kafka, AWS S3, and Spring Boot with Kotlin.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages