The Broad Way

[ Sharp Mind · Sharp Blade · Sharp Spirit ]

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

NestJS em Escala: 42 Modulos e Contando

O pessoal vive me perguntando como o TOPO Contabil roda 42 modulos NestJS sem virar espaguete. Resposta honesta: disciplina, padrao, e saber quando quebrar tuas proprias regras. Deixa eu mostrar como a gente chegou aqui e o que aprendeu. TOPO eh plataforma de contabilidade empresarial. Legislacao tributaria brasileira eh INSANAMENTE complexa -- SPED, EFD, nota fiscal eletronica, DARF, DEFIS -- entao nosso dominio eh massivo. A gente tem modulo pra documento fiscal, calculo tributario, livro contabil, demonstracao financeira, integracao com sistema do governo (SPED, EFD, NFe), gestao de cliente, isolamento multi-tenant, trilha de auditoria, e mais umas 30 coisas. Cada um eh modulo NestJS completo com seus services, controllers, repositories, DTOs e use cases. ORGANIZACAO DE MODULO A gente segue Clean Architecture a risca. Cada modulo tem essa estrutura: Camada de dominio tem entidade e value object. Camada de aplicacao tem use case -- um por arquivo, um metodo publico por classe. Camada de infra tem repository, adapter de servico externo, e codigo especifico do framework. Camada de apresentacao tem controller e DTO. Terminamos com 252 use cases no sistema. Cada um segue o MESMO padrao: recebe DTO de comando ou query, valida, executa logica de negocio pelo dominio, persiste pela interface do repository, retorna resultado. Sem excecao. O insight principal: CHATO EH BOM. Quando dev novo entra, ele abre qualquer modulo e na hora sabe onde tudo ta. A estrutura eh identica em todo lugar. Parece cerimonia quando tu tem 3 modulos. Com 42 eh sobrevivencia. INJECAO DE DEPENDENCIA EM ESCALA DI do NestJS eh poderoso mas pode virar pesadelo. Nossas regras: Modulo so expoe servico pela API publica. Nada de enfiar a mao nas entranhas de outro modulo. Nunca. A gente define interface de modulo explicita usando classe abstrata. Modulo A depende da abstracao, nao da implementacao concreta do Modulo B. Isso significa que da pra trocar implementacao, mockar pra teste, e mais importante, rastrear toda dependencia cross-module. Buildamos um decorator custom chamado ModuleDependency que documenta e valida relacionamento cross-module no startup. Se o Modulo A tentar injetar algo do Modulo B sem declarar a dependencia, o app CRASHA no boot. Falhar rapido, falhar alto. Dependencia circular foi nosso maior inimigo no comeco. A solucao foi arquitetural: extrair conceito compartilhado em modulo dedicado. Em vez de Fiscal depender de Tax e Tax depender de Fiscal, os dois dependem de um modulo TaxRules que eh dono da logica compartilhada. A MAQUINA 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 nao vou te entediar. Cada transicao tem pre-condicao, efeito colateral, e requisito de auditoria. A gente buildou uma maquina de estado configurada declarativamente. Tu define estado, transicao, guard e efeito. O motor cuida de execucao, rollback em caso de falha, emissao de evento e logging de auditoria. Sao umas 800 linhas de codigo e eh a peca mais importante de infra do sistema. Cada transicao de estado eh transacao de banco. Guard roda antes da transicao e pode abortar. Efeito roda depois e eh idempotente pra poder ser retentado. O negocio todo eh event-sourced pra auditoria porque o fisco brasileiro pode te pedir pra provar a sequencia EXATA de operacao que levou ao estado atual de um documento. Receita Federal nao brinca. O QUE NAO FUNCIONA A cerimonia eh real. Criar use case novo = criar 4-5 arquivos minimo: classe do use case, DTO de input, DTO de output, metodo no controller, e geralmente metodo no repository. Pra CRUD simples eh absurdo. Ja falamos de code generation mas honestamente, com o Claude Code eu so descrevo o que preciso e ele monta tudo em segundos. A IA eh o code generator. Performance eh complicado. Middleware, pipe, guard e interceptor do NestJS rodam em todo request. Com 42 modulos carregados, o grafo de dependencia eh grande. Cold start em serverless ia ser sofrimento. A gente roda em infra dedicada entao pra nos eh de boa, mas eh restricao real. Teste nessa escala requer estrategia. A gente tem unitario pros use cases (rapido, isolado), integracao pros repositories (precisa de banco), e E2E pros fluxos criticos (lento, fragil). Proporcao mais ou menos 70/20/10. Rodar tudo leva 4 minutos. So unitario leva 18 segundos. O QUE EU FARIA DIFERENTE Comecava com a maquina de estado mais cedo. Acoplamos ela no modulo 15 e tivemos que migrar um monte de logica de estado inline. Dor pura. Forcava limite de modulo desde o dia um com check automatizado, nao so convencao. Adicionamos o validador de ModuleDependency no modulo 25. Nessa altura ja tinhamos desembaranhado tres dependencias circulares na mao. Usava mais value object. A gente foi preguicoso no comeco e ficou passando primitivo pra todo lado. CNPJ como string eh bug esperando acontecer. Value object CNPJ que valida na construcao eh seguranca. USARIA NESTJS DE NOVO? Sim. Eh opinionated o suficiente pra manter 42 modulos consistentes mas flexivel o suficiente pra deixar a gente buildar infra custom onde precisa. O sistema de DI eh o melhor do ecossistema Node. TypeScript support eh first-class. O sistema de modulo mapeia naturalmente pra limite de dominio. Nao eh o framework mais rapido. Nao eh o mais simples. Mas nessa escala, manutenibilidade ganha de todo resto. E NestJS com Clean Architecture eh o setup Node.js mais mantenivel que eu achei ate hoje. 42 modulos. 252 use cases. Zero arrependimento. Bom, talvez uns. Mas zero arrependimento sobre a escolha do framework.
The Broad Way | Kinho.dev