Este artículo se basa por completo en el BoricuaCoder Clean Template (Without Dogma), un proyecto .NET real y listo para producción. Sin pseudocódigo. Sin capas imaginarias. Sin teoría sin contexto.
Por qué existe este proyecto
Clean Architecture suele presentarse como un ideal.
En la práctica, muchos equipos la viven así:
- demasiadas capas
- demasiada abstracción
- frameworks que deciden tu diseño
- código que se siente académico en lugar de útil
Este template existe para responder una pregunta simple:
¿Cuál es la mínima cantidad de estructura necesaria para construir de forma segura un sistema .NET de larga vida?
Todo lo que hay en este repositorio, y en este artículo, es consecuencia directa de esa pregunta.
Qué resuelve realmente Clean Architecture (y qué no)
Clean Architecture no trata de ser limpia.
Trata de controlar el cambio.
En concreto, ayuda cuando:
- las reglas de negocio evolucionan con el tiempo
- las decisiones de infraestructura no son estables
- varios desarrolladores tocan el mismo código
- la capacidad de probar el código importa
No ayuda cuando:
- el dominio es trivial
- la aplicación va a durar poco
- la velocidad importa más que la corrección
Por eso el template es mínimo a propósito.
Cuándo NO deberías usar Clean Architecture
No uses este template si:
- estás construyendo un panel de administración que es puro CRUD
- estás prototipando algo desechable
- no vas a escribir pruebas automatizadas
Clean Architecture introduce fricción.
Este template existe para que esa fricción valga la pena, no para esconderla.
Estructura del proyecto (real, no teórica)
Tomado del repositorio:
src/
BoricuaCoder.CleanTemplate.Api -> Presentation (Controllers, DTOs)
BoricuaCoder.CleanTemplate.Application -> Use cases (Commands, Queries, Handlers, Validation)
BoricuaCoder.CleanTemplate.Domain -> Entities, Value Objects, Domain rules
BoricuaCoder.CleanTemplate.Infrastructure -> Dapper repositories, DB access, External services
tests/
BoricuaCoder.CleanTemplate.UnitTests -> NUnit + Moq
Flujo de dependencias:
Api → Application → Domain
↑
Infrastructure
Esta única regla explica el 90% del diseño.
Por qué CQRS (ligero, sin MediatR)
CQRS en este proyecto no tiene que ver con escala.
Existe para:
- hacer explícita la intención
- separar lecturas de escrituras
- mantener los casos de uso pequeños y fáciles de probar
Un command representa algo que el sistema quiere hacer. Un query representa algo que el sistema quiere saber.
Evitamos MediatR a propósito.
¿Por qué?
- las dependencias explícitas son más fáciles de razonar
- depurar es directo
- no hay pipelines ocultos
CQRS es un patrón, no un requisito de ningún framework.
Explicación capa por capa (con ejemplos reales)
1. API: capa de presentación
Ubicada en:
BoricuaCoder.CleanTemplate.Api
Responsabilidades:
- definir los endpoints HTTP
- recibir las peticiones
- traducir la entrada a commands / queries
- devolver respuestas HTTP
Los controllers son delgados a propósito.
Flujo de ejemplo:
[HttpPost]
public async Task<IActionResult> Create(
CreateMovieRequest request,
ICreateMovieHandler handler)
{
var result = await handler.Handle(request.ToCommand());
return result.ToHttpResult();
}
Aquí no vive ninguna regla de negocio.
2. Application: casos de uso
Ubicada en:
BoricuaCoder.CleanTemplate.Application
Este es el centro de gravedad.
Contiene:
- commands y queries
- handlers
- validación
- interfaces de repositorio (ports)
Command de ejemplo:
public record CreateMovieCommand(
string Title,
int Year,
string? ImdbId
);
Handler de ejemplo:
public sealed class CreateMovieHandler : ICommandHandler<CreateMovieCommand, Result<Guid>>
{
private readonly IMovieRepository _repository;
public async Task<Result<Guid>> Handle(CreateMovieCommand command)
{
var movie = Movie.Create(command.Title, command.Year, command.ImdbId);
await _repository.Add(movie);
return Result.Success(movie.Id);
}
}
El handler coordina.
No sabe cómo funciona la persistencia.
3. Domain: reglas de negocio
Ubicada en:
BoricuaCoder.CleanTemplate.Domain
El dominio no tiene dependencias.
Define qué es válido, qué está permitido y qué no.
Entidad de ejemplo:
public sealed class Movie
{
public Guid Id { get; }
public string Title { get; }
public int Year { get; }
public static Movie Create(string title, int year, string? imdbId)
{
if (string.IsNullOrWhiteSpace(title))
throw new DomainException("Title is required");
if (year < 1888)
throw new DomainException("Invalid movie year");
return new Movie(Guid.NewGuid(), title, year, imdbId);
}
}
Aquí no existe ninguna preocupación de infraestructura.
4. Infrastructure: los detalles
Ubicada en:
BoricuaCoder.CleanTemplate.Infrastructure
Infrastructure implementa los ports que define Application.
Repositorio de ejemplo:
public sealed class MovieRepository : IMovieRepository
{
private readonly IDbConnectionFactory _factory;
public async Task Add(Movie movie)
{
using var conn = _factory.Create();
await conn.ExecuteAsync(Sql.InsertMovie, movie);
}
}
Dapper y SQL se quedan aquí.
El dominio nunca los ve.
Agregar una funcionalidad nueva (paso a paso, con el proyecto real)
Vamos a agregar una funcionalidad nueva a este proyecto:
Get Movie by Id
Paso 1: Domain
No cambia nada.
Esta funcionalidad no introduce reglas nuevas.
Paso 2: Application
Crea el query:
public record GetMovieByIdQuery(Guid Id);
Define el port:
public interface IMovieReadRepository
{
Task<MovieResponse?> GetById(Guid id);
}
Implementa el handler.
Paso 3: Infrastructure
Implementa el repositorio con Dapper.
El SQL se queda en:
Infrastructure/Database
Paso 4: API
Agrega el endpoint al controller:
GET /movies/{id}
Mapea request → query → response.
Paso 5: Pruebas
Agrega pruebas unitarias para el handler.
No hace falta base de datos.
Lo que evitamos a propósito (Without Dogma)
Este proyecto evita:
- MediatR
- repositorios genéricos
- EF Core
- capas de abstracción extra
- patrones DDD prematuros
No porque sean malos.
Sino porque todavía no hacían falta.
Lo que viene: DDD
Clean Architecture define dónde vive el código.
DDD define cómo se modela el negocio.
Lo siguiente en esta serie:
- aggregates
- invariantes
- value objects
- lenguaje ubicuo
Sobre este mismo template.
Sin reescrituras.
Solo evolución.
Reflexión final
Clean Architecture es una herramienta.
Usada a ciegas, frena a los equipos.
Usada con intención, genera palanca.
Este template vive justo en esa frontera, a propósito.
Proyecto
👉🏻 GitHub Repo ⭐️
