Skip to content

caiquedebrito/logistics-platform

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Logistics Platform — Real-Time Delivery Monitoring (Event-Driven Microservices)

Sistema distribuído para monitorar entregas em tempo real: recebe pings GPS, detecta desvio de rota e atrasos, gera alertas, notifica o time de suporte e dispara recálculo de rota automaticamente. O projeto foi desenhado como um showcase de arquitetura moderna: Microservices + Event-Driven Architecture + Transactional Outbox + Polyglot Persistence + Resilience.

Índice

  • Visão Geral
  • Arquitetura
  • Serviços
  • Event-Driven Architecture (EDA)
  • Eventos e Topologia de Mensageria
  • Persistência (Polyglot)
  • Padrões e Práticas
  • Design Decisions
  • Como Rodar Localmente
  • Rotas Principais
  • Operação (DLQ e Reprocessamento)
  • Testes e Cenários

Visão Geral

Problema de negócio

Uma empresa de logística precisa monitorar milhares de entregas em tempo real. Se um motorista:

  • desviar da rota, ou
  • sofrer atraso,

o sistema deve:

  • gerar um alerta,
  • notificar o suporte,
  • e recalcular a rota (automaticamente) quando necessário.

Objetivo técnico

Demonstrar domínio prático de:

  • Microsserviços
  • Event-Driven Architecture
  • Comunicação assíncrona com broker (RabbitMQ)
  • Persistência poliglota (PostgreSQL + Redis)
  • Resiliência (Circuit Breaker, DLQ, idempotência)

Arquitetura

Componentes

  • Tracking Service (Java / Spring Boot) Responsável por ingestão de pings GPS, detecção de rota/atraso, criação de alertas, histórico e publicação de eventos.
  • Notification Service (Node.js) Consumidor de eventos de alerta; especializado em I/O (e-mail/log/push/SMS). Implementa idempotência e DLQ.
  • Route Optimization Service (Laravel / PHP) Consumidor de solicitações de recálculo; calcula uma nova rota/ETA (MVP) e publica o resultado.
  • Backoffice Dashboard (Next.js) Painel operacional para visualizar entregas/alertas, tratar alertas (ACK), monitorar DLQs e reprocessar mensagens.

Infraestrutura

  • RabbitMQ (Message Broker)
  • PostgreSQL (dados transacionais e histórico)
  • Redis (cache em tempo real: última localização / cooldown / idempotência)
  • Prometheus + Grafana (métricas e dashboards)

Serviços

1) Tracking Service (Spring Boot)

Responsabilidade:

  • Receber pings GPS (/tracking/pings)
  • Validar se o motorista está na rota
  • Detectar alertas: ROUTE_DEVIATION e DELAY
  • Persistir histórico (Postgres)
  • Atualizar cache de “última localização” (Redis)
  • Publicar eventos de domínio via Outbox

Por que Spring Boot?

  • Ecossistema maduro para APIs críticas
  • Integração robusta com DB (JPA/Flyway), Redis, RabbitMQ, Actuator
  • Ótimo suporte a observabilidade e resiliência (Micrometer, Resilience4j)

2) Notification Service (Node.js)

Responsabilidade:

  • Consumir evento alert.created
  • Validar schema do evento
  • Garantir idempotência (Redis SET NX)
  • Enviar notificação (log/email; extensível para SMS/push)
  • Retry com backoff e DLQ para falhas definitivas

Por que Node.js?

  • Excelente para workloads I/O bound
  • Facilidade para integrar e expandir canais (SMTP, APIs externas)
  • Bom fit para serviço “edge” de notificação

3) Route Optimization Service (Laravel/PHP)

Responsabilidade:

  • Consumir route.recalc.requested
  • Recalcular rota/ETA (MVP) e publicar route.recalc.completed

Por que Laravel?

  • Produtividade alta e ecossistema forte
  • Bom para construir regras e evoluir para algoritmos complexos
  • Aproveita domínio prévio da stack

4) Backoffice Dashboard (Next.js)

Responsabilidade:

  • Listar entregas, ETA e última localização
  • Ver alertas em tempo real (SSE) e tratar (ACK)
  • Monitorar DLQ e reprocessar mensagens usando RabbitMQ Management API

Por que Next.js?

  • UI moderna, rápida e escalável
  • Integra bem com SSR/CSR conforme necessidade
  • Facilita criação de “Ops pages” no mesmo frontend

Event-Driven Architecture (EDA)

Este sistema usa Event-Driven Architecture para desacoplar serviços e permitir:

  • escalabilidade independente
  • tolerância a falhas
  • processamento assíncrono e eventual consistency

Regra principal: serviços não “chamam” outros serviços para tarefas internas (ex.: notificar ou recalcular). Em vez disso, eles publicam eventos de domínio e consumidores reagem a esses eventos.

Exemplo (desvio de rota)

  1. Tracking detecta ROUTE_DEVIATION
  2. Tracking grava no banco e publica alert.created
  3. Notification consome alert.created e notifica suporte
  4. Tracking também publica route.recalc.requested (com cooldown)
  5. Route Optimization consome e publica route.recalc.completed
  6. Tracking consome e atualiza rota/ETA

Eventos e Topologia de Mensageria

Exchange (topic)

  • logistics.events

Routing keys (event types)

  • alert.created
  • route.recalc.requested
  • route.recalc.completed

Queues (principais)

  • q.notificationalert.created
  • q.route_optroute.recalc.requested
  • q.tracking.route_updatesroute.recalc.completed

Dead Letter Queues (DLQ)

  • q.notification.dlq
  • q.route_opt.dlq
  • q.tracking.route_updates.dlq

Por que DLQ?

  • Separar falhas permanentes do fluxo normal
  • Permitir investigação e reprocessamento
  • Evitar loop infinito de requeue

Persistência (Polyglot Persistence)

PostgreSQL (dados transacionais)

  • deliveries: entrega, rota, destino, ETA recalculado
  • location_pings: histórico de localização
  • alerts: alertas gerados e status (ACK)
  • outbox_events: eventos pendentes/publicados
  • consumed_events: idempotência no consumo (Tracking)

Por que PostgreSQL?

  • Consistência forte e transações
  • Ótimo para histórico e queries operacionais
  • Base sólida para Outbox e auditoria

Redis (tempo real e coordenação)

  • cache da última localização do motorista (alta frequência)
  • cooldown anti-spam de recálculo
  • idempotência do Notification (evita duplicar envio)

Por que Redis?

  • Latência baixa e alto throughput
  • Ideal para dados “hot” e chaves com TTL

Padrões e Práticas

1) Transactional Outbox (Tracking)

Eventos são gravados na tabela outbox_events na mesma transação do domínio (alerta/ping). Um publisher assíncrono lê a outbox e publica no broker.

Por quê? Evita inconsistências do tipo “salvou no DB mas não publicou no broker”.


2) Idempotência

  • Notification: Redis SET NX usando eventId
  • Tracking consumer: consumed_events impede reaplicar route.recalc.completed

Por quê? Mensageria normalmente é at-least-once, então duplicatas podem ocorrer.


3) Resiliência

  • Circuit Breaker (Resilience4j) no publisher da outbox
  • DLQ para mensagens inválidas ou falhas definitivas
  • Retry com backoff no Notification

Por quê? Se a notificação cair, o Tracking não para. O sistema se mantém funcional em modo degradado.


4) Bounded Contexts (DDD)

Divisão por contexto:

  • Tracking: monitoramento e regras de alerta
  • Notification: canais e entrega de notificação
  • Route Optimization: algoritmo de recálculo
  • Backoffice: operação e visualização

Por quê? Reduz acoplamento e permite evolução independente.


Design Decisions (Decisões de Arquitetura)

Esta seção explica as principais escolhas técnicas e os trade-offs por trás do projeto.

1) RabbitMQ (Message Broker) em vez de Kafka

Decisão: usar RabbitMQ para a mensageria do MVP. Por quê:

  • Setup local mais simples (excelente para portfólio e execução rápida).
  • Boa combinação com work queues e DLQ.
  • Integração direta com Spring AMQP, Node (amqplib) e consumidores simples no Laravel.

Trade-offs:

  • Kafka é mais indicado quando se precisa de streaming de altíssimo throughput, retenção longa e replay como primeira classe.
  • Para este projeto, o foco é demonstrar EDA e confiabilidade com DLQ + idempotência + outbox, o que RabbitMQ cobre muito bem.

2) Exchange topic + routing keys (eventType)

Decisão: um exchange topic (logistics.events) com routing keys como alert.created, route.recalc.requested, etc. Por quê:

  • Facilita evolução: novos consumidores podem assinar padrões (alert.*, route.*).
  • Evita “filas ponto-a-ponto” rígidas e reduz acoplamento.

Trade-offs:

  • Requer disciplina de naming/versão de eventos.
  • Mudanças de schema precisam ser tratadas com compatibilidade (ver decisão 9).

3) Transactional Outbox no Tracking (em vez de publicar direto no broker)

Decisão: o Tracking persiste eventos em outbox_events na mesma transação do domínio e publica de forma assíncrona. Por quê:

  • Garante consistência: se salvou alerta/ping, o evento não se perde.
  • Evita bugs clássicos (“salvou no DB mas falhou ao publicar”).

Trade-offs:

  • Introduz “eventual consistency”: o evento pode sair alguns segundos depois.
  • Exige um publisher agendado (scheduler) e métricas de backlog.

4) Polyglot Persistence: PostgreSQL + Redis

Decisão: Postgres para dados transacionais/histórico e Redis para cache em tempo real e coordenação. Por quê:

  • Postgres é ideal para histórico e consistência (deliveries, alerts, pings, outbox).
  • Redis é ideal para “hot data” (última localização, TTL, cooldown, idempotência) com baixa latência.

Trade-offs:

  • Mais componentes para operar (mas isso é parte do valor do projeto).
  • Necessidade de tratar degradação: sem Redis, o sistema deve continuar (com funcionalidade reduzida).

5) Validação de rota no Tracking (Domínio) e otimização no Laravel (Separação por Bounded Context)

Decisão:

  • Tracking decide quando recálculo é necessário (regra de negócio).
  • Route Optimization decide como recalcular (algoritmo).

Por quê:

  • Mantém responsabilidades claras.
  • Permite evoluir o algoritmo de rota sem tocar no Tracking.
  • Demonstra separação por contexto (DDD/Bounded Contexts).

Trade-offs:

  • Mais mensagens e eventual consistency entre pedir recálculo e receber resultado.

6) Idempotência: Redis no Notification e consumed_events no Tracking

Decisão:

  • Notification: idempotência por eventId via Redis (SET NX com TTL).
  • Tracking consumer: idempotência por eventId em tabela consumed_events.

Por quê:

  • Mensageria tende a ser at-least-once. Duplicatas podem ocorrer.
  • Evita notificação duplicada e reprocessamento indevido.

Trade-offs:

  • Redis indisponível pode afetar a garantia do Notification (decisão 11).
  • Tabela consumed_events cresce (mitigável com retenção/TTL por job).

7) DLQ (Dead Letter Queue) + Reprocessamento via Backoffice

Decisão: todas as filas críticas possuem DLQ e existe página /dlq no Backoffice para operação. Por quê:

  • Falhas permanentes (schema inválido, delivery inexistente, etc.) devem sair do fluxo normal.
  • Operação e debug ficam simples: visualizar, corrigir causa, reprocessar.

Trade-offs:

  • Reprocessar exige cuidado para não republicar “lixo” continuamente.
  • Ideal ter política clara de “quando reprocessar” e auditoria (roadmap).

8) Circuit Breaker no publisher da Outbox (Tracking)

Decisão: aplicar Circuit Breaker (Resilience4j) para que indisponibilidade do broker não degrade o Tracking. Por quê:

  • O Tracking é o serviço crítico de ingestão (não pode travar).
  • Se RabbitMQ estiver instável, o sistema opera “em modo degradado” e a Outbox acumula.

Trade-offs:

  • O backlog pode crescer e aumentar o tempo até notificações/recálculo.
  • Necessita monitoramento (Prometheus) e alertas.

9) Versionamento e compatibilidade de eventos (contrato)

Decisão: eventos possuem envelope padrão (eventId, eventType, occurredAt, correlationId, data). Por quê:

  • Facilita rastreamento e padroniza consumidores.
  • Dá base para evoluir schemas com compatibilidade.

Estratégia recomendada:

  • Mudanças compatíveis: adicionar campos opcionais.
  • Mudanças incompatíveis: criar novo eventType ou versão (alert.created.v2) e manter consumidores antigos por um período.

10) SSE para alertas no Backoffice (em vez de WebSocket)

Decisão: usar Server-Sent Events para stream de alertas em tempo real. Por quê:

  • Simples, leve, e excelente para “push do servidor → cliente”.
  • Reconexão automática e menor complexidade que WebSocket para este caso.

Trade-offs:

  • SSE é unidirecional (server → client). Para interação (Ack), usamos HTTP normal.
  • Para casos avançados (bidirecional, multiplexação intensa), WebSocket pode ser melhor.

11) Estratégia de degradação quando Redis falha

Decisão:

  • Tracking: Redis é “nice-to-have” (cache). Sem Redis, continua registrando pings/alertas no Postgres.
  • Notification: Redis é “safety-critical” para idempotência; sem Redis, preferimos falhar e usar DLQ (evita duplicar notificações).

Por quê:

  • Notificações duplicadas podem causar ruído operacional (muito ruim).
  • Melhor deixar a mensagem na DLQ e reprocessar quando o Redis voltar.

12) Observabilidade primeiro: Prometheus/Grafana como parte do sistema

Decisão: métricas foram tratadas como requisito de arquitetura e não “extra”. Por quê:

  • Em sistemas EDA, gargalos aparecem em filas, DLQ, outbox backlog e circuit breaker.
  • Métricas permitem detectar degradação antes de virar incidente.

Trade-offs:

  • Exige setup adicional (Prometheus/Grafana/exporters), mas melhora muito a qualidade do projeto.

Como Rodar Localmente

1) Subir infraestrutura

cd infra
docker compose up -d

2) Rodar Tracking (Spring Boot)

cd apps/tracking-service
./mvnw spring-boot:run
  • Swagger: http://localhost:8081/swagger-ui.html

3) Rodar Notification

cd apps/notification-service
npm i
npm run dev

4) Rodar Route Optimization consumer

cd apps/route-optimization-service
composer install
php artisan rabbit:consume-route

5) Rodar Backoffice

cd apps/backoffice-dashboard
npm i
npm run dev
  • Dashboard: http://localhost:3000
  • DLQ monitor (ops): http://localhost:3000/dlq

Rotas Principais (Tracking API)

  • POST /deliveries — cria entrega (rota e destino)
  • GET /deliveries — lista entregas com lastLocation e ETA
  • POST /tracking/pings — ingestão GPS e geração de alertas
  • GET /alerts?onlyUnacked=true — lista alertas pendentes
  • POST /alerts/{id}/ack — trata alerta
  • GET /stream/alerts — SSE de alertas em tempo real
  • POST /deliveries/{id}/route/recalculate — (opcional) recálculo manual via Outbox

Operação: DLQ e Reprocessamento

O Backoffice possui uma página /dlq para:

  • visualizar contagem de mensagens nas DLQs
  • reprocessar mensagens (republicar no exchange) via RabbitMQ Management API

Testes e Cenários

  • Happy path (desvio → alerta → notifica → recalc → update)
  • Duplicatas (idempotência)
  • RabbitMQ fora (Outbox + Circuit Breaker)
  • Consumers fora (fila acumula e drena depois)
  • Mensagens inválidas (DLQ)
  • Reprocessamento via Backoffice

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published