Voltar para o Blog
6 minutos de leitura·20 de outubro de 2026

OnDelivery por dentro: arquitetura, stack e mapa do sistema

Como eu organizei o OnDelivery: monorepo, vários serviços, mensageria assíncrona e contêineres, com a stack por camada e o mapa do sistema.

EC
Éverton Carvalho
Software Engineer | Software Architect | CTO
OnDelivery por dentro: arquitetura, stack e mapa do sistema
Estudo de Caso Relacionado
OnDelivery: a arquitetura por trás da plataforma
Ver ADR

O OnDelivery é a plataforma que eu desenvolvo para restaurantes e deliveries: pedidos, PDV, entregas, catálogo, estoque, caixa, cupons, fidelidade e uma loja online para o cliente final, além de apps para garçom, entregador, cozinha (KDS), totem e cardápio QR.

Organizei o código como um monorepo com vários serviços. O backend usa NestJS e Express.js sobre PostgreSQL, com BullMQ e RabbitMQ no processamento assíncrono; os front-ends são em Next.js e React. Tudo roda em contêineres, com testes e CI no GitHub Actions.

Stack por camada

  • Linguagens: TypeScript, Go e Python
  • Runtime: Node.js
  • Backend: NestJS, Express.js e WebSocket / Socket.io
  • Frontend: Next.js, React, Tailwind CSS e TanStack Query
  • Dados: PostgreSQL com TypeORM e Redis
  • Mensageria: BullMQ e RabbitMQ
  • Validação: Zod
  • Testes: Jest, Vitest e Playwright
  • Entrega: Docker, GitHub Actions e NGINX
  • Observabilidade: OpenTelemetry, Grafana, Prometheus, Loki e Tempo

Arquitetura

Organizei o OnDelivery como um monorepo com vários pacotes de workspace. Cada serviço é construído a partir do próprio código e orquestrado com Docker Compose, e a comunicação assíncrona entre eles passa por BullMQ e RabbitMQ.

Os apps, a entrada, os serviços NestJS e o que cada um usa. As setas mostram como as partes se comunicam; os serviços conversam entre si por RabbitMQ.

flowchart LR
  subgraph fe["Front-ends"]
    sellers["Painel, PDV, KDS, garçom e totem (Next.js, PWA)"]
    web["Loja online (Next.js)"]
    qr["Cardápio QR (Next.js)"]
    native["App de garçom, entregador e maquininha (Expo)"]
    desk["App de impressão (Electron)"]
  end
  subgraph entry["Entrada"]
    bff["BFF do Next: cookie HttpOnly"]
    nginx["NGINX"]
  end
  subgraph svc["Serviços NestJS"]
    commerce["commerce: pedidos, estoque, caixa, KDS"]
    auth["auth"]
    payment["payment"]
    subs["subscription"]
    comm["communication: WhatsApp, e-mail, SMS"]
    proc["procurement: notas fiscais"]
    sefaz["sefaz: emissor fiscal"]
    others["catalog, affiliates e scheduling"]
  end
  subgraph data["Dados e mensageria"]
    pg[("PostgreSQL: um banco por serviço")]
    mq["RabbitMQ"]
    bull[("Redis: filas Bull")]
    cache[("Redis: cache")]
  end
  subgraph ext["Integrações"]
    paygw["Gateways de pagamento"]
    sefazext["SEFAZ"]
    wa["WhatsApp"]
  end
  obs["Observabilidade: OpenTelemetry e Grafana"]
  sellers -->|REST| bff
  web -->|REST| bff
  bff -->|Bearer| nginx
  qr -->|REST| nginx
  native -->|REST| nginx
  desk -->|REST| nginx
  nginx --> svc
  commerce -.->|SSE| bff
  svc --> mq
  mq -->|impressão| desk
  proc -->|REST| sefaz
  sefaz -->|XML assinado| sefazext
  commerce --> bull
  comm --> bull
  svc --> pg
  commerce --> cache
  payment --> paygw
  subs --> paygw
  comm --> wa
  svc -.->|OTLP| obs
  class commerce,auth,payment,subs,comm,proc,sefaz,others tech-nestjs
  class pg tech-postgresql
  class mq tech-rabbitmq
  class bull,cache tech-redis
  class nginx tech-nginx

O caminho de um pedido: o pagamento é confirmado por webhook, o pedido aparece em tempo real no painel e no KDS, o cupom sai na impressora e a NFC-e é emitida.

flowchart LR
  client["Cliente na loja online"] -->|checkout| commerce["commerce"]
  commerce -->|link de pagamento| mq["RabbitMQ"]
  mq --> payment["payment"]
  payment --> gw["Gateway de pagamento"]
  gw -->|webhook| payment
  payment -->|pedido pago| mq
  mq --> commerce
  commerce -.->|SSE| panel["Painel e KDS"]
  commerce -->|impressão| mq
  mq --> printer["App de impressão"]
  commerce -->|emitir NFC-e| mq
  mq --> proc["procurement"]
  proc -->|REST| sefaz["sefaz"]
  sefaz -->|XML assinado| sefazext["SEFAZ"]
  commerce -->|aviso| comm["communication: WhatsApp"]
  class commerce,payment,proc,sefaz,comm tech-nestjs
  class mq tech-rabbitmq

Mapa do sistema

Estes são os componentes do OnDelivery, agrupados por área. Deixei de fora endereços, URLs internas e credenciais.

  • Entrada: nginx e vps-private-bridge
  • Serviços: commerce, payment, subscription, communication, evolution-api, affiliates, auth, catalog, procurement e sefaz
  • Dados: commerce-db, auth-db, scheduling-db, services-db, evolution-service, db, cache (Redis), rabbitmq e Postgres-PITR
  • Observabilidade: grafana, prometheus, loki, tempo, otel-collector, cadvisor e node-exporter

Os componentes de cada área. Dividi a visão geral em partes para caber em telas pequenas; ao ampliar a primeira, aparece o mapa inteiro com as ligações entre os componentes.

flowchart TB
  %% kind: system-map
  %% "~~~" lines are invisible layout hints, not links
  accTitle: Mapa do sistema — visão geral (parte 1)
  accDescr: Componentes do OnDelivery agrupados por área.
  subgraph aEntry["Entrada"]
    direction TB
    m1["     nginx                      (proxy · NGINX)"]
    m2["vps‑private‑    bridge      (ponte)   "]
    m1 ~~~ m2
  end
  subgraph aServices["Serviços"]
    direction TB
    m3["commerce            (serviço)  NestJS  "]
    m4[" payment            (serviço)  NestJS  "]
    m5["subscription               (serviço)      NestJS   "]
    m6["communication                 (serviço)      NestJS    "]
    m7["evolution‑api   (serviço) "]
    m8["affiliates   (serviço)"]
    m9["   auth      (serviço)"]
    m10["  catalog    (serviço)"]
    m11["procurement  (serviço)"]
    m12["   sefaz     (serviço)"]
    m3 ~~~ m5
    m4 ~~~ m6
    m5 ~~~ m7
    m6 ~~~ m8
    m7 ~~~ m9
    m8 ~~~ m10
    m9 ~~~ m11
    m10 ~~~ m12
  end
  aEntry ~~~ aServices
  class m1 tech-nginx
  class m3,m4,m5,m6 tech-nestjs
flowchart LR
  %% kind: system-map
  %% view: expanded
  accTitle: Mapa do sistema — visão geral (parte 1)
  accDescr: Componentes do OnDelivery agrupados por área, com as ligações entre eles.
  subgraph aEntry["Entrada"]
    m1["     nginx                      (proxy · NGINX)"]
    m2["vps‑private‑    bridge      (ponte)   "]
  end
  subgraph aServices["Serviços"]
    m3["commerce            (serviço)  NestJS  "]
    m4[" payment            (serviço)  NestJS  "]
    m5["subscription               (serviço)      NestJS   "]
    m6["communication                 (serviço)      NestJS    "]
    m7["evolution‑api   (serviço) "]
    m8["affiliates   (serviço)"]
    m9["   auth      (serviço)"]
    m10["  catalog    (serviço)"]
    m11["procurement  (serviço)"]
    m12["   sefaz     (serviço)"]
    aServicesAll["   Serviços    (área inteira)"]:::areaAnchor
  end
  subgraph aData["Dados"]
    m13[("commerce‑db              (banco de    dados)    PostgreSQL ")]
    m14[(" auth‑db              (banco de    dados)   PostgreSQL")]
    m15[("scheduling‑db                 (banco de      dados)      PostgreSQL  ")]
    m16[("services‑db              (banco de    dados)    PostgreSQL ")]
    m17[("evolution‑  service              (banco de    dados)   PostgreSQL")]
    m18[("    db                   (banco de    dados)    PostgreSQL")]
    m19[("     cache                      (cache · Redis)")]
    m20[" rabbitmq                 (fila)     RabbitMQ"]
    m21[(" Postgres‑PITR  (armazenamento)  ")]
    aDataAll["    Dados      (área inteira)"]:::areaAnchor
  end
  subgraph aObservability["Observabilidade"]
    m22["  grafana    (observa‑   bilidade)    Grafana"]
    m23["prometheus   (observa‑   bilidade)  Prometheus"]
    m24["   loki      (observa‑   bilidade)     Loki"]
    m25["   tempo     (observa‑   bilidade)     Tempo"]
    m26["otel‑collector   (observa‑      bilidade)    OpenTelemetry    Collector  "]
    m27[" cadvisor    (observa‑   bilidade)   cAdvisor"]
    m28["node‑exporter   (observa‑     bilidade)   Node Exporter "]
  end
  m1 ---|ligação visível| aServicesAll
  aServicesAll ---|ligação visível| aDataAll
  m6 ---|ligação visível| m7
  classDef areaAnchor fill:none,stroke-dasharray:6 4
  class m1 tech-nginx
  class m3,m4,m5,m6 tech-nestjs
  class m13,m14,m15,m16,m17,m18 tech-postgresql
  class m19 tech-redis
  class m20 tech-rabbitmq
flowchart TB
  %% kind: system-map
  %% "~~~" lines are invisible layout hints, not links
  accTitle: Mapa do sistema — visão geral (parte 2)
  accDescr: Componentes do OnDelivery agrupados por área.
  subgraph aData["Dados"]
    direction TB
    m13[("commerce‑db              (banco de    dados)    PostgreSQL ")]
    m14[(" auth‑db              (banco de    dados)   PostgreSQL")]
    m15[("scheduling‑db                 (banco de      dados)      PostgreSQL  ")]
    m16[("services‑db              (banco de    dados)    PostgreSQL ")]
    m17[("evolution‑  service              (banco de    dados)   PostgreSQL")]
    m18[("    db                   (banco de    dados)    PostgreSQL")]
    m19[("     cache                      (cache · Redis)")]
    m20[" rabbitmq                 (fila)     RabbitMQ"]
    m21[(" Postgres‑PITR  (armazenamento)  ")]
    m13 ~~~ m15
    m14 ~~~ m16
    m15 ~~~ m17
    m16 ~~~ m18
    m17 ~~~ m19
    m18 ~~~ m20
    m19 ~~~ m21
  end
  class m13,m14,m15,m16,m17,m18 tech-postgresql
  class m19 tech-redis
  class m20 tech-rabbitmq
flowchart TB
  %% kind: system-map
  %% "~~~" lines are invisible layout hints, not links
  accTitle: Mapa do sistema — visão geral (parte 3)
  accDescr: Componentes do OnDelivery agrupados por área.
  subgraph aObservability["Observabilidade"]
    direction TB
    m22["  grafana    (observa‑   bilidade)    Grafana"]
    m23["prometheus   (observa‑   bilidade)  Prometheus"]
    m24["   loki      (observa‑   bilidade)     Loki"]
    m25["   tempo     (observa‑   bilidade)     Tempo"]
    m26["otel‑collector   (observa‑      bilidade)    OpenTelemetry    Collector  "]
    m27[" cadvisor    (observa‑   bilidade)   cAdvisor"]
    m28["node‑exporter   (observa‑     bilidade)   Node Exporter "]
    m22 ~~~ m24
    m23 ~~~ m25
    m24 ~~~ m26
    m25 ~~~ m27
    m26 ~~~ m28
  end

Detalhes por área

Entrada

Por onde as requisições entram e com o que cada componente se conecta. Uma caixa tracejada é a outra ponta da ligação, com a área entre parênteses; (área inteira) é uma ligação com a área toda.

flowchart LR
  %% kind: system-map-detail entry
  accTitle: Mapa do sistema — Entrada
  accDescr: Componentes do OnDelivery agrupados por área.
  m1["     nginx                      (proxy · NGINX)"]
  m2["vps‑private‑    bridge      (ponte)   "]
  r1["   Serviços    (área inteira)"]:::ref
  m1 --- r1
  classDef ref fill:none,stroke-dasharray:4 3
  class m1 tech-nginx
flowchart LR
  %% kind: system-map-detail entry
  %% view: expanded
  accTitle: Mapa do sistema — Entrada
  accDescr: Componentes do OnDelivery agrupados por área, com as ligações entre eles.
  m1["     nginx                      (proxy · NGINX)"]
  m2["vps‑private‑    bridge      (ponte)   "]
  r1["   Serviços    (área inteira)"]:::ref
  m1 ---|ligação visível| r1
  classDef ref fill:none,stroke-dasharray:4 3
  class m1 tech-nginx

Serviços

Os serviços e as suas ligações. Uma caixa tracejada é a outra ponta da ligação, com a área entre parênteses; (área inteira) é uma ligação com a área toda e (nesta área) marca outro serviço da mesma área. Um serviço com muitas ligações pode aparecer em mais de uma parte.

flowchart LR
  %% kind: system-map-detail services
  accTitle: Mapa do sistema — Serviços (parte 1)
  accDescr: Componentes do OnDelivery agrupados por área.
  m3["commerce            (serviço)  NestJS  "]
  m4[" payment            (serviço)  NestJS  "]
  m5["subscription               (serviço)      NestJS   "]
  class m3,m4,m5 tech-nestjs
flowchart LR
  %% kind: system-map-detail services
  accTitle: Mapa do sistema — Serviços (parte 2)
  accDescr: Componentes do OnDelivery agrupados por área.
  m6["communication                 (serviço)      NestJS    "]
  m7["evolution‑api   (serviço) "]
  m8["affiliates   (serviço)"]
  r1["evolution‑api (nesta área) "]:::ref
  m6 --- r1
  classDef ref fill:none,stroke-dasharray:4 3
  class m6 tech-nestjs
flowchart LR
  %% kind: system-map-detail services
  %% view: expanded
  accTitle: Mapa do sistema — Serviços (parte 2)
  accDescr: Componentes do OnDelivery agrupados por área, com as ligações entre eles.
  m6["communication                 (serviço)      NestJS    "]
  m7["evolution‑api   (serviço) "]
  m8["affiliates   (serviço)"]
  r1["evolution‑api (nesta área) "]:::ref
  m6 ---|ligação visível| r1
  classDef ref fill:none,stroke-dasharray:4 3
  class m6 tech-nestjs
flowchart LR
  %% kind: system-map-detail services
  accTitle: Mapa do sistema — Serviços (parte 3)
  accDescr: Componentes do OnDelivery agrupados por área.
  m9["   auth      (serviço)"]
  m10["  catalog    (serviço)"]
  m11["procurement  (serviço)"]
flowchart LR
  %% kind: system-map-detail services
  accTitle: Mapa do sistema — Serviços (parte 4)
  accDescr: Componentes do OnDelivery agrupados por área.
  m12["   sefaz     (serviço)"]
  aSelf["   Serviços    (área inteira)"]:::wholeArea
  r1["  nginx   (Entrada)"]:::ref
  r2["    Dados      (área inteira)"]:::ref
  aSelf --- r1
  aSelf --- r2
  classDef ref fill:none,stroke-dasharray:4 3
  classDef wholeArea font-weight:bold
flowchart LR
  %% kind: system-map-detail services
  %% view: expanded
  accTitle: Mapa do sistema — Serviços (parte 4)
  accDescr: Componentes do OnDelivery agrupados por área, com as ligações entre eles.
  m12["   sefaz     (serviço)"]
  aSelf["   Serviços    (área inteira)"]:::wholeArea
  r1["  nginx   (Entrada)"]:::ref
  r2["    Dados      (área inteira)"]:::ref
  aSelf ---|ligação visível| r1
  aSelf ---|ligação visível| r2
  classDef ref fill:none,stroke-dasharray:4 3
  classDef wholeArea font-weight:bold

Dados

Bancos, cache, fila e armazenamento, e com o que cada um se conecta. Uma caixa tracejada é a outra ponta da ligação, com a área entre parênteses; (área inteira) é uma ligação com a área toda.

flowchart LR
  %% kind: system-map-detail data
  accTitle: Mapa do sistema — Dados (parte 1)
  accDescr: Componentes do OnDelivery agrupados por área.
  m13[("commerce‑db              (banco de    dados)    PostgreSQL ")]
  m14[(" auth‑db              (banco de    dados)   PostgreSQL")]
  class m13,m14 tech-postgresql
flowchart LR
  %% kind: system-map-detail data
  accTitle: Mapa do sistema — Dados (parte 2)
  accDescr: Componentes do OnDelivery agrupados por área.
  m15[("scheduling‑db                 (banco de      dados)      PostgreSQL  ")]
  m16[("services‑db              (banco de    dados)    PostgreSQL ")]
  class m15,m16 tech-postgresql
flowchart LR
  %% kind: system-map-detail data
  accTitle: Mapa do sistema — Dados (parte 3)
  accDescr: Componentes do OnDelivery agrupados por área.
  m17[("evolution‑  service              (banco de    dados)   PostgreSQL")]
  m18[("    db                   (banco de    dados)    PostgreSQL")]
  class m17,m18 tech-postgresql
flowchart LR
  %% kind: system-map-detail data
  accTitle: Mapa do sistema — Dados (parte 4)
  accDescr: Componentes do OnDelivery agrupados por área.
  m19[("     cache                      (cache · Redis)")]
  m20[" rabbitmq                 (fila)     RabbitMQ"]
  class m19 tech-redis
  class m20 tech-rabbitmq
flowchart LR
  %% kind: system-map-detail data
  accTitle: Mapa do sistema — Dados (parte 5)
  accDescr: Componentes do OnDelivery agrupados por área.
  m21[(" Postgres‑PITR  (armazenamento)  ")]
  aSelf["    Dados      (área inteira)"]:::wholeArea
  r1["   Serviços    (área inteira)"]:::ref
  aSelf --- r1
  classDef ref fill:none,stroke-dasharray:4 3
  classDef wholeArea font-weight:bold
flowchart LR
  %% kind: system-map-detail data
  %% view: expanded
  accTitle: Mapa do sistema — Dados (parte 5)
  accDescr: Componentes do OnDelivery agrupados por área, com as ligações entre eles.
  m21[(" Postgres‑PITR  (armazenamento)  ")]
  aSelf["    Dados      (área inteira)"]:::wholeArea
  r1["   Serviços    (área inteira)"]:::ref
  aSelf ---|ligação visível| r1
  classDef ref fill:none,stroke-dasharray:4 3
  classDef wholeArea font-weight:bold

Observabilidade

A stack de observabilidade: métricas, logs e traces.

flowchart LR
  %% kind: system-map-detail observability
  accTitle: Mapa do sistema — Observabilidade (parte 1)
  accDescr: Componentes do OnDelivery agrupados por área.
  m22["  grafana    (observa‑   bilidade)    Grafana"]
  m23["prometheus   (observa‑   bilidade)  Prometheus"]
flowchart LR
  %% kind: system-map-detail observability
  accTitle: Mapa do sistema — Observabilidade (parte 2)
  accDescr: Componentes do OnDelivery agrupados por área.
  m24["   loki      (observa‑   bilidade)     Loki"]
  m25["   tempo     (observa‑   bilidade)     Tempo"]
flowchart LR
  %% kind: system-map-detail observability
  accTitle: Mapa do sistema — Observabilidade (parte 3)
  accDescr: Componentes do OnDelivery agrupados por área.
  m26["otel‑collector   (observa‑      bilidade)    OpenTelemetry    Collector  "]
  m27[" cadvisor    (observa‑   bilidade)   cAdvisor"]
flowchart LR
  %% kind: system-map-detail observability
  accTitle: Mapa do sistema — Observabilidade (parte 4)
  accDescr: Componentes do OnDelivery agrupados por área.
  m28["node‑exporter   (observa‑     bilidade)   Node Exporter "]

Entrega e qualidade

O repositório tem testes com Jest, Vitest e Playwright, CI no GitHub Actions, Dockerfiles e um Docker Compose.

Onde ficam os workflows de CI, os Dockerfiles e o Docker Compose no repositório.

flowchart TB
  nRepo["Repositório Git"]
  subgraph sgAssets["Artefatos de entrega"]
    direction LR
    nCi(["Configuração de CI"])
    nDocker["Definições de contêiner"]
    nCompose["Arquivo de orquestração de contêineres"]
  end
  nRepo -->|contém| sgAssets

O que essa arquitetura cobra

  • Monorepo: facilita mudanças atômicas entre pacotes e o tooling compartilhado, mas exige cuidado com o pipeline e com o acoplamento acidental entre pacotes.
  • Vários serviços: cada serviço sobe e escala sozinho, ao custo de mais operação, observabilidade distribuída e consistência eventual entre eles.
  • Mensageria assíncrona: desacopla os serviços no tempo e absorve picos de carga, mas a depuração fica mais difícil e os consumidores precisam ser idempotentes.
  • Contêineres: garantem paridade entre ambientes e deploy previsível, com o custo de manter as imagens base atualizadas.
Artigo de autoria própria
Éverton Carvalho|Software Engineer | Software Architect | CTO

Construído com Next.js 15, NestJS 11, PostgreSQL e Prisma. Arquitetura desacoplada e pragmática.

© 2026 Éverton Carvalho. Todos os direitos reservados.

Portfolio V2 · Zero Shared Runtimes