root@construct:~/logs/nestjs-em-escala-42-modulos-e-contando$
<-- voltar para /logs
2025-12-08//LOG

NestJS em Escala: 42 Módulos e Contando

O pessoal vive me perguntando como o TOPO Contábil roda 42 módulos NestJS sem virar espaguete. A gente precisou seguir um padrão com disciplina e aprender em quais situações fazia sentido abrir exceção.

TOPO é uma plataforma de contabilidade empresarial. A legislação tributária brasileira, com SPED, EFD, nota fiscal eletrônica, DARF e DEFIS, deixa nosso domínio enorme. Temos módulos pra documento fiscal, cálculo tributário, livro contábil, demonstração financeira, integração com o governo (SPED, EFD, NFe), gestão de cliente, isolamento multi-tenant, trilha de auditoria e mais umas 30 coisas. Cada módulo NestJS tem seus services, controllers, repositories, DTOs e use cases.

Organização de módulo

A gente segue Clean Architecture à risca. Cada módulo tem essa estrutura:

Na camada de domínio ficam as entidades e value objects. A aplicação tem use cases, um por arquivo, com um método público por classe. Infra guarda repositories, adapters de serviços externos e o código do framework. Controllers e DTOs ficam na apresentação.

Terminamos com 252 use cases no sistema. Cada um segue o mesmo padrão: recebe DTO de comando ou query, valida, executa lógica de negócio pelo domínio, persiste pela interface do repository, retorna resultado. Sem exceção.

A parte boa dessa estrutura é ser chata e previsível. Um dev novo abre qualquer módulo e sabe onde tudo tá, porque a organização se repete. Com 3 módulos parece cerimônia demais; com 42, ajuda a gente a sobreviver.

Injeção de dependência em escala

DI do NestJS é poderoso mas pode virar pesadelo. Nossas regras:

Módulo só expõe serviço pela API pública. Definimos a interface explicitamente com classe abstrata, e o Módulo A depende dessa abstração do B. Assim dá pra trocar implementação, mockar testes e rastrear todas as dependências cross-module sem acessar as entranhas de outro módulo.

Buildamos o decorator ModuleDependency pra documentar e validar relacionamentos cross-module no startup. Se o Módulo A tenta injetar algo do B sem declarar a dependência, o app crasha no boot e deixa o problema visível na hora.

Dependência circular foi nosso maior inimigo no começo. A solução foi arquitetural: extrair conceito compartilhado em módulo dedicado. Em vez de Fiscal depender de Tax e Tax depender de Fiscal, os dois dependem de um módulo TaxRules que é dono da lógica compartilhada.

A máquina de estados de 16 estados

Documento fiscal no Brasil passa por ciclo de vida complexo. Rascunho, validado, assinado, transmitido, autorizado, rejeitado, cancelado, corrigido, e mais uns 8 estados que não vou te entediar. Cada transição tem pré-condição, efeito colateral, e requisito de auditoria.

A gente buildou uma máquina de estado configurada declarativamente. Tu define estado, transição, guard e efeito. O motor cuida de execução, rollback em caso de falha, emissão de evento e logging de auditoria. São umas 800 linhas de código e é a peça mais importante de infra do sistema.

Cada transição de estado é transação de banco. Guard roda antes da transição e pode abortar. Efeito roda depois e é idempotente pra poder ser retentado. O negócio todo é event-sourced pra auditoria porque o fisco brasileiro pode te pedir pra provar a sequência exata de operação que levou ao estado atual de um documento. Receita Federal não brinca.

O que não funciona

A cerimônia é real. Criar use case novo = criar 4-5 arquivos mínimo: classe do use case, DTO de input, DTO de output, método no controller, e geralmente método no repository. Pra CRUD simples é absurdo. Já falamos de code generation mas honestamente, com o Claude Code eu só descrevo o que preciso e ele monta tudo em segundos. A IA é o code generator.

Performance é complicado. Middleware, pipe, guard e interceptor do NestJS rodam em todo request. Com 42 módulos carregados, o grafo de dependência é grande. Cold start em serverless ia ser sofrimento. A gente roda em infra dedicada então pra nós é de boa, mas é restrição real.

Teste nessa escala requer estratégia. A gente tem unitário pros use cases (rápido, isolado), integração pros repositories (precisa de banco), e E2E pros fluxos críticos (lento, frágil). Proporção mais ou menos 70/20/10. Rodar tudo leva 4 minutos. Só unitário leva 18 segundos.

O que eu faria diferente

Começava com a máquina de estado mais cedo. Acoplamos ela no módulo 15 e tivemos que migrar um monte de lógica de estado inline. Dor pura.

Forçava limite de módulo desde o dia um com check automatizado, não só convenção. Adicionamos o validador de ModuleDependency no módulo 25. Nessa altura já tínhamos desembaranhado três dependências circulares na mão.

Usava mais value object. A gente foi preguiçoso no começo e ficou passando primitivo pra todo lado. CNPJ como string é bug esperando acontecer. Value object CNPJ que valida na construção é segurança.

Usaria NestJS de novo?

Sim. É opinionated o suficiente pra manter 42 módulos consistentes mas flexível o suficiente pra deixar a gente buildar infra custom onde precisa. O sistema de DI é o melhor do ecossistema Node. TypeScript support é first-class. O sistema de módulo mapeia naturalmente pra limite de domínio.

O framework pesa mais e é mais complexo que outras opções, mas nessa escala a manutenibilidade pesa mais pra mim. NestJS com Clean Architecture é o setup Node.js mais fácil de manter que achei até hoje.

Tenho uns arrependimentos depois desses 42 módulos e 252 use cases. A escolha do framework não é um deles.

The Broad Way | Kinho.dev