Pedido Certo — portal de pedidos para distribuidoras

SaaS multi-tenant onde o lojista compra da distribuidora pelo celular. Cada cliente atende no domínio próprio, com certificado emitido sob demanda, e o isolamento entre tenants é garantido até na camada do banco.

NestJSTypeScriptPrisma 7PostgreSQLNext.jsRedisDocker

O problema

Distribuidora vende para lojista, e o pedido normalmente chega por WhatsApp ou telefone. Isso produz erro de digitação, preço divergente do combinado, pedido fora do múltiplo de caixa fechada e nenhum histórico consultável. Do outro lado, o faturista redigita tudo no ERP.

E cada distribuidora quer o portal com a marca dela, no endereço dela — não numa página genérica com o logo de um fornecedor de software.

Multi-tenancy com o banco como última linha

Schema compartilhado, tenant_id em toda tabela de negócio e Row Level Security no PostgreSQL (ADR-0003). Três camadas, e aqui as três estão ativas:

  1. Um interceptador resolve o tenant do subdomínio e o guarda em AsyncLocalStorage, para não ter que passar o identificador de mão em mão por toda a pilha.
  2. Uma extensão do Prisma Client aplica o filtro — o código de aplicação não escreve tenantId à mão, então não existe consulta que possa esquecê-lo.
  3. A role do banco usada pela aplicação é NOBYPASSRLS e não tem DDL. Se as duas camadas acima falharem por um defeito, o banco devolve zero linha em vez do dado do vizinho.

Todo repositório novo exige teste de isolamento provando que o tenant B não alcança o dado do tenant A. Não é convenção de revisão, é requisito.

Cada cliente no domínio dele, com certificado automático

Uma distribuidora atende em bompreco.b2bflow.com.br, outra em pedidos.suadistribuidora.com.br. Nenhuma lista fixa de certificado cobre os dois formatos: curinga resolve o primeiro e não alcança o segundo, e emitir um a um transformaria cada cliente novo em tarefa de infraestrutura.

O certificado é emitido sob demanda, no primeiro acesso a um host novo, com uma trava: antes de pedir, o proxy consulta a API se aquele host pertence a alguma distribuidora ativa. Sem essa consulta, qualquer pessoa apontando um domínio para o IP faria a plataforma pedir certificado por ele, e a autoridade certificadora bloquearia a conta por exceder a cota de emissão.

A mesma imagem do frontend serve todos os domínios, porque a API é servida no mesmo host de cada portal — o navegador chama a própria origem, e CORS deixa de existir em produção. É a classe de erro que mais custa tempo em implantação multi-tenant, justamente porque só aparece no domínio do cliente.

Arquitetura verificada, não combinada

Clean Architecture com DDD e CQRS (ADR-0001), em seis módulos: catalog, pricing, ordering, identity, tenancy, platform.

A regra de dependência é checada na integração contínua, não no code review: a camada de domínio não importa biblioteca externa nenhuma — nem framework, nem ORM, nem validador. Regra combinada e não verificada é regra que se perde na terceira sprint.

Dinheiro e quantidade

Valor monetário é inteiro de centavos em bigint, nunca ponto flutuante. Quantidade respeita o múltiplo de caixa fechada do produto, e isso é invariante do agregado — não validação de formulário, que se contorna chamando a API direto.

Uma decisão que teve prazo de validade

O ADR-0002 registrava dois repositórios separados para API e frontend, e listava o gatilho que justificaria juntá-los. Quando esse gatilho apareceu, o ADR-0005 consolidou tudo num monorepo citando o critério que o próprio ADR-0002 havia escrito.

Decisão de arquitetura tem prazo. O que evita a discussão circular meses depois é ter registrado, junto da decisão, o que faria você mudar de ideia.

Stack

NestJS e TypeScript estrito na API, Prisma 7 e PostgreSQL, Redis com BullMQ para trabalho assíncrono, Next.js App Router como PWA para o lojista comprar do celular em rede instável, Docker Compose e Caddy na entrega.

197 testes unitários na API, com fronteira de camada verificada em CI.