Clean Architecture y Patrón Repository en ASP.NET Core

¿Cuántas veces has abierto un proyecto de hace seis meses y no has sabido por dónde empezar? ¿O has querido cambiar la base de datos y has terminado tocando diez archivos distintos porque todo estaba mezclado? Ese es exactamente el problema que resuelve Clean Architecture: separar las responsabilidades de tu aplicación de forma que cada parte viva donde le corresponde, y que el núcleo de tu lógica de negocio no dependa de ningún framework externo.

En este artículo construimos desde cero una API de gestión de tareas llamada CleanTodo usando ASP.NET Core, MediatR, EF Core y SQLite, aplicando Clean Architecture y el Patrón Repository. Si sigues los pasos al pie de la letra, tendrás un proyecto funcionando con Swagger al final.


Índice

  1. El problema: aplicaciones sin estructura
  2. La solución: Clean Architecture
  3. Las cuatro capas del proyecto
  4. Paso a paso: creando CleanTodo desde cero
  5. Problemas encontrados y cómo resolverlos
  6. Recomendaciones y debate abierto

1. El problema: aplicaciones sin estructura

Cuando empezamos a programar, la tendencia natural es poner todo en el controlador: la lógica de negocio, las consultas a base de datos, las validaciones. Funciona. Al principio.

El problema llega cuando el proyecto crece. Cambiar SQLite por PostgreSQL implica tocar el controlador. Añadir tests unitarios es casi imposible porque todo depende de EF Core. Un compañero nuevo tarda días en entender dónde vive cada cosa.

Los puntos clave del problema son:

  • Acoplamiento entre capas: la lógica de negocio conoce los detalles de infraestructura.
  • Dificultad para testear: no se puede probar la lógica sin levantar la base de datos.
  • Fragilidad ante cambios: tocar una pieza rompe otras que no deberían verse afectadas.

Clean Architecture, popularizada por Robert C. Martin (Uncle Bob) en su libro Clean Architecture: A Craftsman’s Guide, propone invertir esta situación: el dominio no depende de nadie, todos dependen del dominio.


2. La solución: Clean Architecture

La regla de dependencia es simple: las dependencias solo pueden apuntar hacia adentro.

API → Application → Domain ✅
Infrastructure → Domain    ✅
Domain → Infrastructure    ❌ nunca
Domain → Application       ❌ nunca

Esto se consigue a través de interfaces. El dominio define un contrato (ITaskRepository). La infraestructura lo implementa (TaskRepository con EF Core). El dominio jamás sabe que EF Core existe.

El Patrón Repository actúa como intermediario entre la lógica de negocio y el acceso a datos. En lugar de hacer queries directamente en los handlers o controladores, todo pasa por una interfaz que abstrae el origen de los datos.

Para más contexto sobre este patrón, la documentación oficial de Microsoft lo explica en detalle: Repository pattern – .NET docs.


3. Las cuatro capas del proyecto

CapaResponsabilidadNuGet
DomainLógica de negocio puraninguno
ApplicationCasos de uso, orquestaciónMediatR, FluentValidation
InfrastructureAcceso a datosEF Core, SQLite
APIEntrada HTTPSwashbuckle

4. Paso a paso: creando CleanTodo desde cero

Requisitos previos

  • .NET 10 SDK instalado
  • Un editor de código (Visual Studio Code o Visual Studio)
  • PowerShell o terminal

Paso 1 — Crear la solución y los proyectos

Añadimos los proyectos a la solución:

Paso 2 — Establecer referencias entre proyectos

Aquí es donde se refleja la regla de dependencia:

Paso 3 — Instalar paquetes NuGet

⚠️ Importante: No instales MediatR.Extensions.Microsoft.DependencyInjection — ese paquete es de versiones antiguas y entra en conflicto con MediatR 14. En la versión actual, el registro de DI ya está incluido en el paquete principal.

Paso 4 — Crear las carpetas

Paso 5 — La capa Domain

Esta es la capa más importante. Abre el CleanTodo.Domain.csproj cuando termines y verás que no tiene ninguna referencia a NuGet. Eso es exactamente lo que buscamos.

TodoTask.cs — La entidad tiene lógica propia. No es un simple contenedor de datos:

ITaskRepository.cs — El contrato. Vive en Domain, lo implementa Infrastructure:

TaskNotFoundException.cs:

Paso 6 — La capa Application

Aquí usamos CQRS con MediatR: los Commands modifican estado, las Queries solo leen. Para profundizar en CQRS, Jimmy Bogard tiene una serie excelente sobre este enfoque.

CreateTaskCommand.cs y su Handler:

El momento clave del proyecto — CompleteTaskHandler.cs:

El handler no sabe qué hace Complete() por dentro. Solo coordina. Esa separación es la esencia del dominio rico.

Paso 7 — La capa Infrastructure

TaskRepository.cs — Aquí sí usamos EF Core. Solo aquí:

DependencyInjection.cs — El punto de conexión entre la interfaz y la implementación:

Paso 8 — La capa API

El controlador es deliberadamente delgado. Su único trabajo es recibir HTTP y delegar:

Program.cs — El punto de entrada registra todo:

Paso 9 — Arrancar y probar

Abre el navegador en http://localhost:5217/swagger. Puedes probar en este orden para el video:

  1. POST /api/tasks — Crea una tarea
  2. GET /api/tasks — Lista todas
  3. PUT /api/tasks/{id}/complete — Completa la tarea
  4. PUT /api/tasks/{id}/complete — Vuelve a intentarlo → 409 Conflict ← aquí se ve la regla de negocio

5. Problemas encontrados

Conflicto de versiones con MediatR. Al instalar MediatR.Extensions.Microsoft.DependencyInjection junto con MediatR 14, el sistema de restauración de paquetes devuelve un error NU1107. La solución es no instalar ese paquete: desde MediatR 12 en adelante, el registro de DI está integrado. Solo necesitas MediatR y registrar con cfg.RegisterServicesFromAssembly(...).

EF Core 10 y las propiedades con setter privado. EF Core necesita un constructor privado sin parámetros para poder materializar las entidades. Sin él, al ejecutar una query obtendrás una excepción en tiempo de ejecución. El constructor privado vacío en TodoTask soluciona esto.

Puerto de la API. dotnet new webapi en .NET 10 ya no genera WeatherForecastController, y el puerto por defecto puede variar. Revisa el archivo launchSettings.json en Properties/ si Swagger no aparece donde esperas.


6. Recomendaciones y debate abierto

Si vas a usar esto en producción, añade un ValidationBehaviour de MediatR que intercepte los commands antes de llegar al handler y ejecute FluentValidation automáticamente. Es el pipeline behavior estándar en proyectos Clean Architecture y evita repetir la lógica de validación en cada handler.

¿Cuándo NO usar Clean Architecture? Es una pregunta legítima. Para una API pequeña con tres endpoints y un solo desarrollador, cuatro proyectos y veinte archivos puede ser sobredimensionado. El propio Uncle Bob advierte que la arquitectura debe adaptarse al contexto. Para proyectos grandes, en equipo o con expectativa de crecer, la inversión merece la pena.

Puntos para debatir:

  • ¿Tiene sentido usar CQRS en un proyecto sin alta concurrencia o sin necesidad de separar modelos de lectura y escritura?
  • ¿El Patrón Repository añade una capa de abstracción útil cuando ya tienes EF Core, que de por sí es una abstracción?
  • ¿Merece la pena la complejidad inicial de Clean Architecture en proyectos de corta duración?

Hay posiciones encontradas en la comunidad. Mark Seemann escribió sobre los tradeoffs y vale la pena leerlo antes de adoptar cualquier patrón como dogma.


Conclusiones

La clave de este proyecto no está en los archivos en sí, sino en lo que no ocurre: el dominio no referencia a EF Core, el controlador no tiene lógica de negocio, y si mañana decides cambiar SQLite por PostgreSQL solo tienes que modificar DependencyInjection.cs y el connection string.

Eso es exactamente lo que busca Clean Architecture: que los cambios sean locales, predecibles y baratos.


Autora: Heily Madelay Ajila Tandazo Máster: Desarrollo Full Stack + Arquitecturas Cloud Centro: Tajamar Tech Año académico: 2025-2026 Código / recursos utilizados:

Leave a Comment

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.