Portfólio — Engenharia de Software Sênior

Hub de notificações
assíncronas

Plataforma event-driven para despacho multi-canal com garantias de entrega at-least-once, deduplicação atômica e circuit breaker por canal — construída sobre Java 21, Kafka e Resilience4j.

scroll

Tecnologias

Java 21 Spring Boot 3.4 Apache Kafka Redis 7 Resilience4j 2.2 PostgreSQL 16 Flyway Micrometer Prometheus Grafana Spring Security + JWT SpringDoc OpenAPI Docker Compose GitHub Actions Clean Architecture Hexagonal / Ports & Adapters JUnit 5 @EmbeddedKafka Testcontainers

Como uma notificação é entregue

Do POST REST até a confirmação de entrega, cada etapa carrega garantias explícitas de idempotência, resiliência e rastreabilidade.

01

⚡

API REST

Cliente autentica com JWT e faz POST com idempotencyKey. A API retorna 202 Accepted imediatamente — sem esperar a entrega.

02

📨

Kafka Pending

Notificação persiste como PENDING no PostgreSQL e é publicada em notifications.pending. Produtor usa idempotência nativa do Kafka.

03

🔒

Redis Dedup

Consumer verifica SETNX atômico no Redis. Eventos duplicados de partições concorrentes são descartados sem efeito colateral no banco.

04

🛡️

Circuit Breaker

Resilience4j isola falhas por canal. EMAIL e WEBHOOK têm CBs independentes — falha em um não afeta o outro. CB aberto roteia direto para DLQ.

05

✅

SENT ou DLQ

Sucesso atualiza para SENT. Falha sustentada vai para notifications.dlq com audit trail imutável via trigger PostgreSQL.

Decisões de arquitetura

Escolhas com tradeoffs documentados — o tipo de conversa que distingue uma entrevista técnica sênior de uma apresentação de CRUD.

Transporte

Kafka em vez de RabbitMQ

Log imutável permite replay de eventos. DLQ é um tópico nativo — reprocessar é um --from-beginning. RabbitMQ resolveria menos por mais complexidade operacional.

Idempotência

Redis SETNX, não constraint SQL

Operação atômica sem locks explícitos. A alternativa via UNIQUE no PostgreSQL exige tratar ConstraintViolationException e é 10–50× mais lenta sob concorrência de partições.

Resiliência

Retry em dois níveis independentes

@Retry trata blips rápidos (300ms/600ms inline). Tópico notifications.retry trata falhas sustentadas com delay exponencial via header ByteBuffer. Cada nível tem causa distinta.

Domínio

CircuitBreakerOpenException no núcleo

Exceção de domínio (não de infra) permite que o NotificationDispatchService roteia CB aberto direto para DLQ sem vazar abstração de infraestrutura para a camada de aplicação.

Persistência

Auditoria imutável via trigger

Trigger AFTER UPDATE OF status garante que nenhum código de aplicação precise lembrar de gravar o histórico. A tabela notification_audit nunca é escrita diretamente pela app.

Arquitetura

Clean Architecture / Hexagonal

Domain zero-deps — nenhuma anotação Spring no núcleo. Portas e adaptadores permitem trocar Kafka por SQS ou PostgreSQL por MongoDB sem tocar em regras de negócio.

Um comando para tudo

Docker Compose sobe 9 serviços com healthchecks e dependências corretas. Nenhuma configuração manual necessária.

bash
# clone o repositório
git clone https://github.com/priscila-nascimento/notify-hub
cd notify-hub

# sobe todos os serviços
docker compose up -d

# autenticar e guardar o token
TOKEN=$(curl -s -X POST http://localhost:8080/api/v1/auth/token \
  -H "Content-Type: application/json" \
  -d '{"clientId":"demo","clientSecret":"demo-secret"}' \
  | grep -o '"accessToken":"[^"]*"' | cut -d'"' -f4)

# enviar notificação por e-mail
curl -s -X POST http://localhost:8080/api/v1/notifications \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"channel":"EMAIL","recipient":"u@exemplo.com",
      "payload":{"subject":"Olá","body":"Notificação entregue."},
      "idempotencyKey":"demo-001"}'
:8080 Swagger UI :3000 Grafana admin/admin :9090 Prometheus :8025 MailHog :8090 Kafka UI

Métricas prontas para Prometheus

Dashboard Grafana pré-provisionado com taxa de entrega por canal, latência p50/p95/p99, DLQ counter e estado dos Circuit Breakers.

notify_hub_sent_total

Counter

Entregas bem-sucedidas por canal.

channel

notify_hub_failed_total

Counter

Falhas com motivo discriminado.

channelreason

notify_hub_latency_seconds

Histogram · SLO buckets

Latência de entrega p50 / p95 / p99.

channel

notify_hub_dlq_total

Counter

Eventos para dead-letter queue após retries esgotados.

channel

resilience4j_circuitbreaker_state

Gauge

CLOSED / OPEN / HALF_OPEN por canal em tempo real.

namestate