Modulith (Modular Monolith)
Un único deploy, pero módulos internos con fronteras estrictas y comunicación explícita.
Contexto
Quieres la simplicidad del monolito y la autonomía modular de microservicios — sin la operativa distribuida.
Solución
- Cada módulo (Sales, Billing, Shipping) es un bounded context.
- Comunicación por interfaces públicas o eventos in-process.
- Cada módulo es dueño de sus propias tablas; los demás no las leen.
- Reglas de dependencia obligadas con análisis estático (NetArchTest, ArchUnitNET).
#Ejemplo en C#
CSHARP
src/
├── Modules/
│ ├── Sales/
│ │ ├── Sales.Public/ ← contratos hacia fuera
│ │ ├── Sales.Application/
│ │ ├── Sales.Domain/
│ │ └── Sales.Infrastructure/
│ ├── Billing/
│ │ └── …
│ └── Shipping/
│ └── …
└── Bootstrap/ ← compone DI, expone HTTP
// Comunicación entre módulos: eventos in-process
public record OrderPlacedIntegrationEvent(Guid OrderId, decimal Total);
// Sales publica:
await _events.PublishAsync(new OrderPlacedIntegrationEvent(order.Id, order.Total));
// Billing escucha (mismo proceso, sin red):
public class CreateInvoiceOnOrderPlaced : INotificationHandler<OrderPlacedIntegrationEvent> { /* ... */ }#Por qué importa
Si mañana necesitas extraer Billing a microservicio, basta con cambiar el bus in-process por uno real (RabbitMQ/Kafka). El refactor existe dentro del módulo.
Cuándo NO aplicarlo
- Cuando ya tienes equipos completamente independientes con stacks distintos.
- Cuando un módulo necesita escalar 100× más que el resto.
Tradeoffs
| Pro | Contra |
|---|---|
| Simplicidad operativa de monolito | Disciplina de equipo necesaria |
| Camino claro hacia microservicios | Tooling para enforcement de límites |
| Transacciones reales entre módulos (si lo permites) | Tentación de saltarse las fronteras |
#architecture #modulith