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.
