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.
Recommended book
Want to dive deeper into this topic? Check out the book that inspired this post.
View book →References
2 comments
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