Sistema simples de votação para demonstrar um fluxo sincrônico (REST) produzindo mensagens para RabbitMQ e um listener consumindo e processando votos. Persistência via JPA/Hibernate em PostgreSQL.
Stack: Java 21 • Spring Boot • Spring Web • Spring Data JPA • Spring AMQP • Flyway • RabbitMQ • PostgreSQL
src/main/java/com/api/votacao
├─ config/
│ └─ RabbitConfiguration.java # Exchange, fila, binding e conversor JSON
├─ dtos/
│ ├─ CandidatoRequest.java
│ ├─ CandidatoResponse.java
│ ├─ VotoRequest.java
│ └─ VotoResponse.java
├─ entities/
│ ├─ Candidato.java
│ └─ Voto.java
├─ listeners/
│ └─ ComputaVotoListener.java # @RabbitListener delegando para service
├─ repositories/
│ ├─ CandidatoRepository.java
│ └─ VotoRepository.java
├─ resources/
│ ├─ CandidatoResource.java
│ └─ VotoResource.java # Endpoints REST
└─ services/
├─ exceptions/
│ └─ ResourceNotFoundException.java
├─ CandidatoService.java
└─ VotoService.java # Publica no Rabbit e valida domínio
src/main/resources
├─ application.properties
├─ db.migration/ # (Opcional) scripts Flyway
├─ templates/, static/ # (se aplicável)
└─ data.sql # (Opcional) seeds iniciais
POST /api/candidatocadastra candidatos.POST /api/votorecebe um voto, valida o candidato e publica uma mensagem na exchange do RabbitMQ.ComputaVotoListenerconsome mensagens da filacomputar-voto.qe delega o processamento para um service de domínio (persistência do voto, regras, idempotência, etc.).
Boas práticas aplicadas: listener fino, regras de negócio nos services, DTOs de entrada/saída, validação de existência de candidato antes do publish.
- Java 21+
- Maven 3.9+ (ou usar
./mvnw) - PostgreSQL 15+ (ou Docker)
- RabbitMQ 3.13+ (com plugin de management recomendado)
Crie um docker-compose.yml (exemplo mínimo):
services:
postgres:
image: postgres:17
container_name: votacao-pg
environment:
POSTGRES_DB: votacao
POSTGRES_USER: votacao
POSTGRES_PASSWORD: votacao123
TZ: America/Sao_Paulo
PGTZ: America/Sao_Paulo
ports: ["5432:5432"]
volumes: ["pgdata:/var/lib/postgresql/data"]
healthcheck:
test: ["CMD-SHELL", "pg_isready -U votacao -d votacao"]
interval: 5s
timeout: 3s
retries: 10
rabbitmq:
image: rabbitmq:3.13-management
container_name: votacao-rabbit
ports:
- "5672:5672" # AMQP
- "15672:15672" # Management UI
healthcheck:
test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"]
interval: 5s
timeout: 3s
retries: 10
volumes:
pgdata:UI do RabbitMQ: http://localhost:15672 (user:
guest/ pass:guest)
src/main/resources/application.properties (exemplo):
spring.application.name=votacao
# PostgreSQL
spring.datasource.url=jdbc:postgresql://localhost:5432/votacao
spring.datasource.username=votacao
spring.datasource.password=votacao123
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.show-sql=false
spring.jpa.properties.hibernate.jdbc.time_zone=UTC
# Flyway (se usar)
spring.flyway.enabled=true
spring.flyway.locations=classpath:db/migration
# RabbitMQ
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
# Logs
logging.level.org.springframework.amqp=INFO
logging.level.com.api.votacao=DEBUGA RabbitConfiguration define (exemplo recomendado):
- Exchange:
computar-voto.ex(topic) - Fila:
computar-voto.queue - Routing key: ``
- Conversor:
Jackson2JsonMessageConverterpara publicar/consumir JSON
No publish (service):
rabbitTemplate.convertAndSend("computar-voto.ex", "", request);No listener:
@RabbitListener(queues = "computar-voto.queue")
public void onMessage(@Payload VotoRequest request) { ... }- Suba Postgres e RabbitMQ (Docker Compose acima).
- Rode a aplicação:
# usando wrapper do Maven
./mvnw spring-boot:run
# ou build + run
./mvnw clean package
java -jar target/votacao-*.jarA API sobe (por padrão) em http://localhost:8080.
curl -X POST http://localhost:8080/api/candidato \
-H "Content-Type: application/json" \
-d '{
"nome": "Candidato A"
}'curl http://localhost:8080/api/candidatocurl -X POST http://localhost:8080/api/voto \
-H "Content-Type: application/json" \
-d '{
"idCandidato": 1,
"idEleitor": 123
}'Resposta esperada (VotoResponse) com dados do candidato. O processamento completo do voto é feito de forma assíncrona pelo listener.
- Validação do candidato: antes de publicar voto, o service busca
candidatoRepository.findById(...)e lançaResourceNotFoundExceptionse não existir. - Integridade referencial: entidade
VotoreferenciaCandidatovia FK. - Idempotência (recomendado): crie uma constraint única (ex.:
eleitor_id + candidato_idou umvoto_uuid) para evitar votos duplicados em reprocessamentos. - DLQ/Retry (recomendado): configure DLQ e política de reentrega para mensagens problemáticas.
- Use Flyway em
db.migration(ex.:V1__create_tables.sql). - Opcionalmente, um
data.sqlpode criar candidatos iniciais para testes rápidos.
- Paginação e filtros nos endpoints
- Métricas e health checks (Actuator)
- Segurança (JWT)
- DLQ e política de retries no Rabbit
- Testes de integração (
@SpringBootTest) e testes unitários (Mockito)