Portfólio — Engenharia de Software Sênior
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
Stack
Arquitetura
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.
Design
Escolhas com tradeoffs documentados — o tipo de conversa que distingue uma entrevista técnica sênior de uma apresentação de CRUD.
Transporte
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
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 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
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
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
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.
Execução local
Docker Compose sobe 9 serviços com healthchecks e dependências corretas. Nenhuma configuração manual necessária.
# 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"}'
Observabilidade
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.
notify_hub_failed_total
Counter
Falhas com motivo discriminado.
notify_hub_latency_seconds
Histogram · SLO buckets
Latência de entrega p50 / p95 / p99.
notify_hub_dlq_total
Counter
Eventos para dead-letter queue após retries esgotados.
resilience4j_circuitbreaker_state
Gauge
CLOSED / OPEN / HALF_OPEN por canal em tempo real.