en
· 4 min de leitura

O monólito modular na prática

Uma unidade de deploy, fronteiras internas rígidas. Como fica numa base TypeScript real, e onde os times trapaceiam.

architecture

O monólito modular é a arquitetura que mais recomendo e a que mais vejo feita errado. A ideia é simples: um único deploy, dividido internamente em módulos com fronteiras explícitas, para ter a disciplina de design dos serviços sem a rede. A falha também é simples: as fronteiras existem num diagrama e em nenhum outro lugar.

É assim que fica quando funciona, no tipo de base Next.js e TypeScript em que passo a maior parte do meu tempo.

A forma

Um repositório, uma unidade de deploy. Dentro dele, um diretório por módulo, nomeado por uma capacidade de negócio, não por uma camada técnica: cobrança, catálogo, agendamento, identidade. Não controllers, services, repositories. Camadas podem existir dentro de um módulo; elas não organizam o nível de cima.

Cada módulo tem uma superfície pública, um único arquivo index que exporta o que os outros módulos podem usar: um punhado de funções, alguns tipos, talvez um tipo de evento. Todo o resto do módulo é privado. Os outros módulos importam do index e de nada mais fundo.

Cada módulo é dono dos próprios dados. Suas tabelas, suas migrações, suas consultas. Nenhum outro módulo escreve nessas tabelas, e idealmente nenhum outro módulo as lê diretamente. Se o catálogo precisa saber se um cliente está ativo, ele pergunta à identidade pela superfície pública, não com um join.

Os módulos conversam de dois jeitos: chamadas diretas pela superfície pública, para o que precisa ser síncrono e consistente, e eventos em processo, para o que é reação. Quando um pedido é pago, a cobrança emite um evento e a expedição assina. Mesmo processo, mesma fronteira de transação se você quiser, sem broker.

Como a fronteira é imposta

Uma fronteira que não é imposta por ferramenta é uma sugestão. A imposição que eu monto é barata.

  • Uma regra de lint que proíbe imports dos internos de outro módulo, com o arquivo index como único ponto de entrada permitido.
  • Um schema de banco ou prefixo de tabela separado por módulo, e uma checagem no CI de que nenhuma migração toca as tabelas de outro módulo.
  • Um teste do grafo de dependências que falha o build quando surge um ciclo entre módulos.
  • Um teste de que o index público de cada módulo exporta apenas o que uma lista curta de permitidos diz.

Essas quatro regras levam um dia para configurar e pegam a maior parte das trapaças que eu encontraria numa auditoria dois anos depois.

Onde os times trapaceiam

A primeira trapaça é o módulo compartilhado. Começa como tipos e helpers, vira o lugar para onde vai toda regra que toca dois módulos, e termina como o monólito de verdade com os outros como cascas finas. A correção é tratar o compartilhado como uma biblioteca rígida: pura, sem I/O, sem lógica de negócio.

A segunda trapaça é o join. Sob prazo, alguém escreve uma consulta que lê tabelas de dois módulos numa única instrução porque é mais rápido que duas chamadas. É mais rápido mesmo. Também significa que esses módulos nunca poderão ser separados, e a consulta é invisível para os dois donos. Se o join é realmente necessário por desempenho, ele pertence a um read model que um módulo explicitamente possui e alimenta.

A terceira trapaça é a transação que atravessa módulos. Um handler abre uma transação, chama a cobrança, depois o agendamento, depois faz commit. Funciona e é conveniente. Também significa que os dois módulos compartilham um modo de falha e um escopo de lock, e quando um é extraído a atomicidade desaparece em silêncio. Minha regra: uma transação pertence a um módulo. Efeitos entre módulos acontecem por eventos, com a mesma disciplina de pelo menos uma entrega que você usaria através de uma rede.

A quarta trapaça é o vazamento do framework. No Next.js é o server action ou o route handler que faz a lógica de negócio inline, com o módulo como detalhe. O handler deveria ter dez linhas: interpretar, chamar o módulo, formatar. Se tem cem, a fronteira do módulo é ficção.

Por que vale a disciplina

Um monólito modular bem cuidado entrega quase tudo pelo que as pessoas extraem serviços: propriedade clara, contratos entre as partes, a capacidade de raciocinar sobre uma capacidade isoladamente. Entrega isso sem rede, sem transação distribuída, sem uma frota de pipelines.

E mantém a opção aberta. Quando um módulo realmente precisa escalar ou fazer deploy sozinho, a extração é mecânica: a superfície pública vira uma API, os eventos em processo viram um tópico, as tabelas se movem. Já vi times fazerem isso em semanas porque a fronteira já existia, e já vi outros gastarem um ano porque ela não existia.

Comece com uma unidade de deploy e fronteiras rígidas. Imponha-as com ferramentas, não com intenções. Depois extraia só o que mereceu.