// ESTUDO · MICROSSERVIÇOS POLIGLOTAS · OPEN SOURCE

LogisticSaaS

OmniDynamic Engine: um estudo de microsserviços poliglotas para precificação dinâmica e gestão de inventário em tempo real no varejo. Um campo de provas pra filas Kafka, atores com Akka.NET, observabilidade e o padrão BFF × frontend — com gRPC como próxima fronteira.

OPEN SOURCE MICROSSERVIÇOSKAFKAAKKA.NETPOLYGLOT

Motivação do estudo

O LogisticSaaS nasceu pra estudar sistemas distribuídos de ponta a ponta usando uma stack poliglota: escolhendo, de propósito, a linguagem mais adequada pra cada serviço em vez de padronizar tudo numa só.

A meta é exercitar os blocos de construção de um SaaS escalável de varejo:

  • Mensageria com filas
  • Comunicação interna via gRPC
  • Concorrência com Akka.NET
  • Observabilidade (logs, métricas e dashboards)
  • O padrão BFF entre os serviços e o frontend

O projeto foi conduzido com apoio do Antigravity + Gemini 3 Pro como exercício de orquestração assistida por IA.

O que foi feito

  1. Control Plane: frontend em Next.js 14 com dashboard de atores, inventário e um “Test Lab” pra simular regras de precificação.
  2. API Gateway / BFF: em NestJS, ponto único de entrada entre o frontend e os serviços — faz proxy REST pros serviços internos e expõe um endpoint SSE que repassa eventos do Kafka pro navegador em tempo real.
  3. Camada de ingestão: em Java 21 (Spring WebFlux), recebe telemetria via webhook e publica no Kafka (inventory-events).
  4. Pricing Solver: em C++ 20 (Drogon), consome o Kafka e calcula preço dinâmico via REST (POST /calculate) a partir de uma fórmula de blend (peso × multiplicador + markup) configurável por regra. gRPC está desenhado como próximo passo pra substituir o REST entre Gateway e Solver.
  5. Order Service: em .NET 8 com Akka.NET — um ator por reserva de estoque (TTL de 15min, auto-expiração via Scheduler), mais um worker que consome o Kafka, persiste pedidos no Supabase/PostgreSQL e republica no tópico order-events.
  6. Catalog Service: em .NET 8 (Minimal APIs), CRUD de produtos e estoque direto no Supabase/PostgreSQL, com trilha de auditoria (stock_audit) a cada mudança de quantidade.
  7. Mensageria e observabilidade: Apache Kafka (+ Zookeeper) entre os serviços; stack Loki + Promtail + Prometheus + cAdvisor + Grafana pra logs e métricas de todos os containers.
  8. Infra e orquestração: docker-compose + Makefile pro ambiente local (11+ serviços), Helm chart inicial pro Gateway em Kubernetes.

Em andamento: próximas frentes de estudo incluem gRPC real entre Gateway e Pricing Solver, RBAC nos endpoints do Gateway (hoje desprotegidos para facilitar o dev local), CQRS/Event Sourcing, caching e o padrão SAGA para transações distribuídas.

Como funciona

Visão geral do sistema

flowchart TB
    Browser["🌐 Navegador (usuário)"]
    FE["frontend :3000 — Next.js 14\nDashboard · Atores · Test Lab"]

    GW["gateway :3001 — NestJS BFF\nproxy REST · SSE /stream/kafka"]

    subgraph Poliglota["Serviços de domínio"]
        Ingestion["ingestion :3002\nJava 21 · Spring WebFlux"]
        Solver["pricing-solver :3003\nC++ 20 · Drogon"]
        Orders["order-service :3004\n.NET 8 · Akka.NET"]
        Catalog["catalog-service :3005\n.NET 8 · Minimal API"]
    end

    Kafka[["Kafka\ninventory-events · order-events"]]
    PG[("Supabase / PostgreSQL\npricing · orders · audit · catalog")]

    subgraph Obs["Observabilidade"]
        Promtail["promtail"]
        Loki[("loki")]
        Prometheus[("prometheus")]
        Grafana["grafana :3006"]
        cAdvisor["cadvisor"]
    end

    Browser --> FE
    FE -->|REST| GW
    FE -.->|SSE: eventos em tempo real| GW

    GW -->|"proxy /telemetry"| Ingestion
    GW -->|"REST: /pricing-rules, /calculate"| Solver
    GW -->|"REST: /reservations"| Orders
    GW -->|"REST: /api/products"| Catalog
    GW -.->|consome p/ SSE| Kafka

    Ingestion -->|publica| Kafka
    Kafka -->|consome| Solver
    Kafka -->|consome, filtra check_out/SALE| Orders
    Orders -->|publica order-events| Kafka

    Orders --> PG
    Catalog --> PG
    Solver -.->|regras de preço| PG

    Ingestion --> Promtail
    Solver --> Promtail
    Orders --> Promtail
    Catalog --> Promtail
    GW --> Promtail
    Promtail --> Loki
    cAdvisor --> Prometheus
    Loki --> Grafana
    Prometheus --> Grafana

Fluxo orientado a eventos: da telemetria ao pedido

sequenceDiagram
    participant Ext as Fonte externa (loja/PDV)
    participant Ing as ingestion (Java)
    participant K as Kafka
    participant Solver as pricing-solver (C++)
    participant Ord as order-service (.NET/Akka)
    participant DB as Supabase
    participant GW as gateway (SSE)
    participant FE as frontend

    Ext->>Ing: POST /telemetry (evento de estoque/venda)
    Ing->>K: publica em inventory-events

    K->>Solver: consome (recalcula sinais de preço)
    K->>Ord: consome (worker Kafka)

    alt evento é check_out ou SALE
        Ord->>Ord: monta Order a partir do evento
        Ord->>DB: INSERT orders
        Ord->>K: publica ORDER_CREATED em order-events
    end

    K->>GW: consome inventory-events + order-events
    GW-->>FE: SSE (Server-Sent Events) com o payload bruto
    FE-->>FE: dashboard atualiza em tempo real

Reserva de estoque com atores (Akka.NET)

sequenceDiagram
    actor U as Cliente (checkout)
    participant FE as frontend
    participant GW as gateway
    participant Ord as OrderController
    participant Sup as ReservationSupervisorActor
    participant Res as ReservationActor (por pedido)
    participant K as Kafka

    U->>FE: inicia checkout do SKU
    FE->>GW: POST /reservations
    GW->>Ord: proxy REST
    Ord->>Sup: Ask(CreateReservation)
    Sup->>Sup: existe ator para este orderId?
    alt não existe
        Sup->>Res: cria filho (Props por orderId)
    end
    Sup->>Res: encaminha CreateReservation
    Res->>Res: agenda auto-expiração (TTL 15min)
    Res-->>Sup: ReservationCreated
    Sup-->>Ord: ReservationCreated
    Ord->>K: publica RESERVATION_CREATED em order-events
    Ord-->>GW: 200 { status: RESERVED, expires_at }
    GW-->>FE: resposta repassada

    Note over Res: passados 15min sem confirmação
    Res->>Res: ReservationExpired (self-message agendado)
    Res->>Res: Context.Stop(Self)

Cálculo de preço dinâmico

sequenceDiagram
    actor A as Analista de preços
    participant FE as frontend (Test Lab)
    participant GW as gateway
    participant Solver as pricing-solver

    A->>FE: define/edita uma pricing rule
    FE->>GW: POST /pricing-rules
    GW->>Solver: proxy REST (axios)
    Solver-->>GW: regra criada (weight, multiplier, base_markup)
    GW-->>FE: 201 Created

    A->>FE: simula preço pra um SKU
    FE->>GW: POST /pricing-rules/calculate
    GW->>Solver: POST /calculate
    Solver->>Solver: finalPrice = basePrice·(1-weight) + (basePrice+base_markup)·multiplier·weight
    Solver-->>GW: preço final calculado
    GW-->>FE: resultado da simulação

Modelo de dados (Supabase/PostgreSQL)

Cada domínio tem seu próprio schema no mesmo cluster Postgres, isolados por schema (pricing, orders, audit) mais as tabelas do Catalog Service:

erDiagram
    PRICING_RULE ||--o{ PRICE_CHANGE_LOG : "gera ao disparar"

    PRICING_RULE {
        uuid id PK
        string name
        string target_scope "GLOBAL|CATEGORY|SKU|REGION"
        jsonb conditions
        jsonb action_logic
        double weight "0..1, peso do blend"
        double multiplier
        double base_markup
        bool is_active
    }
    PRICE_CHANGE_LOG {
        uuid id PK
        string sku_id
        decimal old_price
        decimal new_price
        uuid rule_id FK
        string triggered_by "SOLVER|MANUAL|SCHEDULED"
    }

    ORDER_EVENT {
        bigint id PK
        uuid aggregate_id "order/reservation id"
        string event_type "RESERVATION_CREATED|STOCK_LOCKED|ORDER_PAID"
        jsonb payload
        int version
    }
    INVENTORY_SNAPSHOT {
        string sku_id PK
        int total_reserved
        int total_available
    }

    PRODUCT ||--o{ STOCK_AUDIT : "audita mudanças de"
    PRODUCT {
        uuid id PK
        string sku
        string name
        decimal base_price
        int stock_quantity
    }
    STOCK_AUDIT {
        uuid id PK
        uuid product_id FK
        int change_amount
        int new_quantity
        string reason
    }

Stack poliglota

ServiçoTecnologia
Frontend / Control PlaneNext.js 14 (Vercel)
API Gateway / BFFNode.js · NestJS
IngestãoJava 21 · Spring WebFlux
Pricing SolverC++ 20 · Drogon (REST hoje, gRPC planejado)
Order / Catalog.NET 8 · Akka.NET · Supabase
MensageriaApache Kafka
ObservabilidadeLoki · Promtail · Prometheus · cAdvisor · Grafana

Como rodar

# Subir o cluster de serviços localmente
docker-compose up --build

# Comandos comuns ficam no Makefile
make help

# Código aberto

O LogisticSaaS é open source, um laboratório de padrões de sistemas distribuídos.