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:
- 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. - 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. - A role do banco usada pela aplicação é
NOBYPASSRLSe 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.