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