Modular Monolith: talvez seus microserviços possam esperar
Tem uma sequência de decisão que se repete com uma frequência suspeita: o time acha que o monólito “está bagunçado”, conclui que a solução é microsserviços, e um ano depois está gerenciando dez serviços distribuídos, cada um com seu próprio deploy, sua própria observabilidade e sua própria dor de cabeça de rede, sem ter resolvido o problema original, que nunca foi sobre processos separados. Era sobre acoplamento interno mal organizado.
O que monólito modular realmente propõe
Monólito modular é uma solução .NET organizada em módulos com fronteiras bem definidas, cada um responsável por um domínio específico do negócio, rodando dentro do mesmo processo. A diferença para um monólito tradicional “bagunçado” não é o número de processos. É a disciplina de acoplamento: cada módulo expõe uma interface clara para os demais, evita acessar diretamente o banco de dados ou as entidades internas de outro módulo, e se comunica através de contratos explícitos.
Na prática, isso costuma se traduzir em projetos (assemblies) separados dentro da mesma solução .NET, um por módulo, com dependências entre eles controladas de propósito: um módulo de Pedidos pode depender de uma interface pública exposta pelo módulo de Estoque, mas não deveria depender das classes internas ou do schema de banco de dados desse módulo.
Comunicação interna sem virar chamada de rede
Um dos pontos que confunde quem está migrando de um monólito desorganizado para essa estrutura é como os módulos deveriam conversar entre si sem reintroduzir o mesmo acoplamento direto que se está tentando evitar.
A resposta comum é comunicação em memória baseada em eventos ou mensagens, usando algo como MediatR para publicar um evento de domínio (PedidoCriado, por exemplo) que outros módulos escutam e reagem, sem que o módulo que publicou precise conhecer quem está do outro lado. Para casos mais simples de comunicação assíncrona dentro do próprio processo, System.Threading.Channels também resolve, sem trazer a complexidade de um broker de mensagens externo para um cenário que ainda não precisa disso.
public record PedidoCriado(Guid PedidoId, Guid ClienteId) : INotification;
public class ReservarEstoqueAoPedidoCriado
: INotificationHandler<PedidoCriado>
{
public async Task Handle(PedidoCriado notificacao, CancellationToken cancellationToken)
{
await _servicoEstoque.ReservarAsync(notificacao.PedidoId, cancellationToken);
}
}
O módulo de Pedidos publica o evento e não sabe, nem precisa saber, que o módulo de Estoque está reagindo a ele.
Isolamento de dados é a regra que mais costuma ser quebrada
A parte mais fácil de negligenciar é a separação de dados. Não adianta ter módulos separados no código se todos continuam lendo e escrevendo diretamente nas mesmas tabelas sem passar pelo módulo dono daquele dado. Isso recria o acoplamento que a separação em módulos deveria eliminar, só que escondido atrás de uma estrutura de pastas que parece organizada.
Uma prática comum é dar a cada módulo seu próprio schema dentro do mesmo banco de dados, com regra explícita de que nenhum módulo acessa tabela de outro schema diretamente. Isso preserva a possibilidade real de, no futuro, extrair um módulo específico para um serviço separado, porque as fronteiras de dado já existem. Sem esse cuidado, mesmo separando pastas e projetos, a extração futura de um módulo esbarraria imediatamente em consultas cruzadas espalhadas pelo código inteiro.
O que isso resolve e o que continua exigindo microsserviço de verdade
Monólito modular entrega boa parte da disciplina de separação de responsabilidades que motiva a ida para microsserviços, sem o custo operacional de deploy independente, rede entre serviços, consistência eventual e observabilidade distribuída. Ele não resolve a necessidade real de escalar módulos de forma independente, usar linguagens ou runtimes diferentes por módulo, ou isolar completamente falhas entre times grandes o suficiente para justificar fronteiras organizacionais rígidas.
Se o problema é organização de código, monólito modular resolve boa parte, mais cedo e mais barato. Se o problema é escala de equipe ou de infraestrutura que o processo único genuinamente não aguenta mais, aí sim a conversa sobre microsserviços volta a fazer sentido, agora com fronteiras de domínio já bem definidas para dividir.