Modular Monolith en .NET 8: La arquitectura olvidada que todo dev debería considerar

Cuando hablamos de arquitecturas en .NET, el debate siempre se va al extremo: o tienes un monolito gigante imposible de mantener, o te lanzas de cabeza a los microservicios, con todo lo que eso implica (infraestructura, autenticación distribuida, resiliencia, logging centralizado...).

Pero hay una opción intermedia que muchas veces se subestima: el Monolito Modular.
Y con .NET 8, ¡es más fácil que nunca implementarlo!

🔍 ¿Qué es un Monolito Modular?

Un Modular Monolith es una única aplicación desplegable, pero organizada internamente en módulos bien definidos. Cada módulo representa una funcionalidad o dominio del negocio, y se comunica con otros usando interfaces o eventos internos. No hay dependencias cruzadas, y cada módulo puede evolucionar de forma independiente.

Es decir: estructura limpia, despliegue simple.

🧰 ¿Cómo implementarlo en .NET 8?

Aquí algunos pilares para organizar un Monolito Modular con herramientas modernas:

1. 🗂 Estructura de solución

Organiza tu solución en proyectos por módulo:

/src
  /Modules
    /Customers
      Customers.API.csproj
      Customers.Application.csproj
      Customers.Domain.csproj
      Customers.Infrastructure.csproj
    /Orders
      Orders.API.csproj
      Orders.Application.csproj
      Orders.Domain.csproj
      Orders.Infrastructure.csproj
  WebGateway.csproj  (el host principal)

Cada módulo se estructura con un enfoque DDD + Clean Architecture, y el proyecto principal (WebGateway) referencia a los módulos mediante ProjectReference.

2. ✉️ Comunicación entre módulos

Usa MediatR para manejar la comunicación desacoplada entre capas (o incluso entre módulos, si lo encapsulas bien).

// Customers.Application
public record GetCustomerByIdQuery(Guid Id) : IRequest<CustomerDto>;

// Customers.Application.Handlers
public class GetCustomerByIdHandler : IRequestHandler<GetCustomerByIdQuery, CustomerDto>
{
    // lógica para obtener cliente
}

Y desde otro módulo (como Orders), puedes inyectar IMediator y lanzar el query, sin acoplarte directamente.

3. 🚪 Exposición de Endpoints

Cada módulo puede tener sus propios endpoints definidos como Minimal APIs y registrados en el Program.cs principal:

// Customers.API
public static class CustomerEndpoints
{
    public static IEndpointRouteBuilder MapCustomerEndpoints(this IEndpointRouteBuilder endpoints)
    {
        endpoints.MapGet("/customers/{id}", async (Guid id, IMediator mediator) =>
        {
            var customer = await mediator.Send(new GetCustomerByIdQuery(id));
            return Results.Ok(customer);
        });

        return endpoints;
    }
}

Y en Program.cs:

app.MapCustomerEndpoints();
app.MapOrderEndpoints();

PlantUML diagram

4. 🧪 Testing más fácil

Cada módulo puede tener su propio proyecto de pruebas unitarias y de integración, enfocado en su lógica de negocio. Esto te da alta cohesión y bajo acoplamiento.

✅ Beneficios reales en .NET

  • Un solo despliegue (ideal para Azure App Services, contenedores, IIS, etc.)

  • Modularidad para equipos grandes

  • Facilidad de testing, mantenimiento y refactorización

  • Puedes migrar módulos individuales a microservicios sin reescribir todo

🚀 ¿Cuándo usar esta arquitectura?

Un Monolito Modular en .NET 8 es ideal cuando:

  • Estás construyendo una app empresarial (ERP, CRM, Portales internos, etc.)

  • Quieres mantener la simplicidad del monolito pero sin comprometer la escalabilidad del código.

  • Tienes un equipo que trabaja por módulos funcionales.

  • Planeas evolucionar gradualmente hacia microservicios.

🧩 Recursos adicionales