Spring Modulith na Prática: Construindo Monolitos Modulares Escaláveis
A indústria de software vive um dilema constante: começar com um monolito e correr o risco de criar um "Big Ball of Mud" ou começar com microsserviços e ser engolido pela complexidade operacional logo no primeiro dia É aqui que brilha o Monolito Modular e o Spring Modulith é a ferramenta definitiva do ecossistema Java para garantir que essa arquitetura seja respeitada. Ele nos permite aplicar conceitos de Domain-Driven Design (DDD) e manter o código isolado e testável.
1. Estruturação por Domínios (Pacotes como Módulos)
No Spring Modulith, um pacote de primeiro nível (logo abaixo da sua classe principal Application) é considerado um módulo lógico independente.
A regra de ouro é: apenas as classes na raiz do módulo são públicas para outros módulos. Tudo o que estiver em subpacotes (como internal) deve ser encapsulado.
src/main/java/com/alexsousadev/app
├── Application.java
├── order <-- Módulo de Pedidos
│ ├── OrderService.java (Público: API do módulo)
│ ├── OrderCompletedEvent.java
│ └── internal <-- Detalhes de implementação
│ ├── Order.java
│ └── OrderRepository.java
└── payment <-- Módulo de Pagamentos
├── PaymentService.java
└── internal
└── PaymentProviderClient.java
Se o PaymentService tentar importar diretamente o OrderRepository (que está no pacote internal), o Spring Modulith vai barrar isso nos testes.
2. Comunicação Desacoplada Orientada a Eventos
Em um monolito tradicional, o OrderService injetaria o PaymentService direto pelo construtor. Isso cria um acoplamento forte. No Spring Modulith, a melhor prática é a comunicação baseada em eventos.
Módulo de Pedidos publicando um evento:
package com.alexsousadev.app.order;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class OrderService {
private final ApplicationEventPublisher events;
public OrderService(ApplicationEventPublisher events) {
this.events = events;
}
@Transactional
public void checkout(String orderId) {
// Lógica para salvar o pedido no banco...
// Publica o evento para quem quiser ouvir
events.publishEvent(new OrderCompletedEvent(orderId));
}
}
Módulo de Pagamentos escutando o evento: Em vez de usar @EventListener, usamos @ApplicationModuleListener. Essa anotação mágica executa o evento de forma assíncrona e garante que ele só seja disparado após a transação do banco de dados do pedido ser concluída com sucesso.
package com.alexsousadev.app.payment;
import com.alexsousadev.app.order.OrderCompletedEvent;
import org.springframework.modulith.events.ApplicationModuleListener;
import org.springframework.stereotype.Service;
@Service
public class PaymentService {
@ApplicationModuleListener
void on(OrderCompletedEvent event) {
System.out.println("Processando pagamento para o pedido: " + event.orderId());
// Lógica de cobrança...
}
}
3. O Guardião das Fronteiras (Testes de Arquitetura)
De nada adianta desenhar uma arquitetura limpa se um desenvolvedor do time quebrar as regras em um novo Pull Request. O Spring Modulith permite criar um teste automatizado que falha a esteira de CI/CD se alguém furar o encapsulamento dos módulos ou criar dependências circulares.
Basta adicionar um simples teste unitário:
package com.alexsousadev.app;
import org.junit.jupiter.api.Test;
import org.springframework.modulith.core.ApplicationModules;
class ArchitectureTests {
@Test
void verifiesModularStructure() {
ApplicationModules modules = ApplicationModules.of(Application.class);
// Valida se as regras de encapsulamento e ciclo de dependências foram respeitadas
modules.verify();
}
}
Dica Extra: Você pode usar o método
modules.createDocumenter().writeModulesAsPlantUml()neste mesmo teste para gerar diagramas de arquitetura (C4 Model) atualizados automaticamente a cada build!
Conclusão
Usar o Spring Modulith é o passo natural para quem já estuda Clean Architecture e DDD. Ele te força a pensar em contextos delimitados (Bounded Contexts) e protege as fronteiras da sua aplicação, deixando o código pronto para, no futuro, ser quebrado em microsserviços apenas se (e quando) for realmente necessário.
Referências
2 comentários
Muitooo bom o conteúdo meu amigo!!! Segue um repositório para voce olhar e dizer o que acha da arquitetura implementanda (multi modulos): https://github.com/Marcklen/plataforma-fly
Cara, muito interessante. Parabéns