$mermaidjs
|
Clean Architecture Demo
|
A continuación describimos la arquitectura de esta demo de Clean Architecture utilizando modelos C4.
Recuerda que en Clean Architecture el código se organiza en círculos concéntricos, donde cada círculo representa diferentes áreas del software. Los elementos de código de un círculo no pueden tener referencias a elementos en un círculo exterior, o dicho de otra forma, las dependencias pueden ir sólo de afuera hacia adentro.

El primer diagrama C4 es el del sistema de software. Muestra los sistemas adyacentes que en este caso son el Usuario que gestiona las tareas y un Email Service que envía correos electrónicos para notificar cambios en las tareas.
El segundo diagrama C4 es el de contenedores. Muestra la aplicación ASP .NET Core que expone una API REST TaskManagement.API y las librerías TaskManagement.Domain, TaskManagement.Application y TaskManagement.Infrastructure que implementan las capas del dominio, aplicación e infraestructura, respectivamente, de acuerdo a los círculos de Clean Architecture mostrados más arriba. También se muestra la base de datos utilizada por la capa de infraestructura para almacenamiento de datos.
Traduciendo la terminología C4 a .NET, los contenedores son los ensamblados que contienen el código de las librería -cada ensamblado se genera a partir de un proyecto .csproj con el mismo nombre; las propiedades de la librería se especifican en ese proyecto-.
Puedes ver claramente en el diagrama que las dependencias van "de arriba hacia abajo", lo que equivale a "afuera hacia adentro" cuando las capas se muestran en círculos.
El siguiente diagrama C4 es el de componentes. Muestra las funcionalidades de la aplicación encapsuladas detrás de interfaces -tipos- bien definidos.
Traduciendo la terminología de C4 a .NET, los componentes son las clases -las clases definen tipos-.
Puedes ver claramente como las clases en los contenedores mantienen las referencias del diagrama de contenedores, es decir, "de arriba hacia abajo".
El último diagrama C4 es el de código. Muestra cómo los componentes son implementados en código.
En este caso usamos un diagrama de clases -parcial- mostrando algunas de las clases involucradas en la creación de una tarea.
La arquitectura en este demo está organizada en proyectos de C# independientes, donde cada proyecto corresponde a un círculo:
Application Core e incluye lo que en la documentación original de Clean Architecture son los círculos Entities y Use Cases. A pesar de ser una demo en .NET, usamos la terminología original.TaskItem.DomainEvents y el método TaskItem.Create en TaskItem-; por esto, esta aplicación también utiliza una arquitectura dirigida por eventos. La capa del dominio tiene la responsabilidad de generar eventos, pero es la capa de aplicación la que tienen la responsabilidad de procesarlos -ver por ejemplo CreateTaskCommand.Handle en CreateTaskCommand-. El proyecto TaskManagement.Domain no referencia ningún otro proyecto, es el centro de los círculos concéntricos.builder.services… .AddScoped<IUnitOfWork>(…), .AddScoped<ITaskReadRepository, TaskReadRepository>(), y .AddScoped<IDomainEventDispatcher, MediatRDomainEventDispatcher>() en Program-. Los eventos creados en la capa del dominio son procesados en la capa de aplicación usando una abstracción -ver la interfaz IDomainEventDispatcher-; y esa abstracción también está implementada en una clase definida en la capa de infraestructura -ver la clase MediatRDomainEventDispatcher-. El proyecto TaskManagement.Application referencia solamente el proyecto TaskManagement.Domain, la dependencia es de un círculo externo al centro de los círculos concéntricos.builder.services...AddScoped<ITaskRepository, TaskRepository>() y builder.services...AddScoped<ITaskReadRepository, TaskReadRepository>()-. Como ya fue mencionado antes, la abstracción IDomainEventDispatcher definida en la capa de aplicación se implementa con la clase MediatRDomainEventDispatcher de esta capa de infraestructura y la instancia se crea en tiempo de ejecución también con injección de dependencias en la capa de interfaz API en Program -ver builder.services...AddScoped<IDomainEventDispatcher, MediatRDomainEventDispatcher>()-. El proyecto TaskManagement.Infrastructure referencia solamente el proyecto TaskManagement.Application, la dependencia es de un círculo externo a un círculo interno. Esta capa de infraestructura tiene también la configuración de los frameworks de acceso a datos.TaskManagement.Application como al proyecto TaskManagement.Infrastructure, ambos en círculos internos. Como toda aplicación web en .NET la carpeta contiene los controladores que implementan los endpoint de la API REST -ver por ejemplo TasksController-. El archivo TaskManagement.http tiene ejemplos para invocar la API REST.[^3]: Algunos autores definen las abstracciones relacionadas con el dominio en la capa del dominio, aunque no sean utilizadas en esa capa. Siguiendo esos autores, interfaces como ITaskRepository se definirían en la capa de dominio. En esta demo, interfaces como esa son definidas en la capa de aplicación, porque es allí donde se usan.
La aplicación utiliza los siguientes frameworks.
FluentValidation es una librería de .NET para constuir reglas de validación fuertemente tipadas.
MediatR es una librería de .NET para mensajería en proceso -sin persistencia- sin dependencias. Admite solicitudes y respuestas, comandos, consultas, notificaciones y eventos, tanto síncronos como asíncronos, con despacho inteligente mediante tipos genéricos en C#.
Dapper es una librería de mapeo objeto-relacional -ORM- de código abierto para aplicaciones .NET. Permite acceder de forma rápida y sencilla a los datos de las bases de datos sin necesidad de escribir código complejo.
La capa del dominio está definida aquí.
| Componente | Archivos | Propósito |
|---|---|---|
| Entidades | Entities/TaskItem.cs, Entities/TaskStatus.cs, Entities/TaskPriority.cs | Clases de modelo de negocio representando conceptos del dominio |
| Objetos valor | ValueObjects/Email.cs, Shared/ValueObject.cs | Objetos inmutables representando valores del dominio |
| Eventos del dominio | Events/DomainEvent.cs | Eventos representando ocurrencias importantes del dominio |
| Interfaces | Ver nota [^3] | Ver nota [^3] |
| Excepciones | Exceptions/DomainException.cs | Excepciones específicas del dominio |
| Tipos compartidos | Shared/Result.cs, Shared/TaskErrors.cs | Tipos comunes compartidos internamente en el dominio |
Principio clave: La capa del dominio nunca depende de las capas de aplicación o infraestructura y contiene solo datos y reglas del negocio.
La entidad TaskItem es un aggregate root que define y gestiona sus propios datos y reglas del negocio. Está definida así:
La capa del dominio utiliza el patrón Result para el resultado de las operaciones. La clase Result representa tanto resultados exitosos -en cuyo caso incluye también el valor del resultado- como errores -en cuyo caso incluye la lista de errores-. Esto permite que un método pueda retornar tanto un resultado como un error.
La capa de aplicación está definida aquí.
| Componente | Archivos | Propósito |
|---|---|---|
| Comandos | Commands/CreateTaskCommand.cs, Commands/CompleteTaskCommand.cs | Objetos para casos de uso que cambian estado (CQRS) |
| Consultas | Queries/GetTaskByIdQuery.cs | Objetos para casos de uso de lecturas de datos (CQRS) |
| DTO | Queries/GetTaskByIdQuery.cs (TaskDto), Requests/CreateTaskRequest.cs | Objetos de transferencia de datos para entrada/salida |
| Interfaces | Interfaces/IUnitOfWork.cs, Interfaces/ITaskRepository.cs, Interfaces/ITaskReadRepository.cs, Interfaces/IEmailService.cs, Interfaces/IDomainEventDispatcher.cs | Abstracciones para servicios de infraestructura y despacho de eventos |
| Comportamientos | Behaviors/ValidationBehavior.cs | Comportamientos de pipeline MediatR |
| Excepciones | Exceptions/ValidationException.cs, Exceptions/NotFoundException.cs | Excepciones específicas de la capa de aplicación |
| Compartido | Shared/PagedResult.cs | DTOs y tipos comunes |
Principio clave: La capa de aplicación implementa casos de uso de negocio pero delega la lógica de negocio a la capa del dominio. Básicamente es un orquestador de objetos del dominio, sin lógica del negocio.
El siguiente diagrama muestra una versión simplificada de un comando de la capa de aplicación y su interacción con la capa del dominio.
La clase CreateTaskCommandHandler implementa un procesador de MediatR para el comando CreateTaskCommand.
En ese método, las variables _taskRepository de tipo ITaskRepository -definido en la capa del dominio-, _unitOfWork de tipo IUnitOfWork e _eventDispatcher de tipo IDomainEventDispatcher -definidos en la propia capa de aplicación- son asignadas mediante injección de dependencias con clases implementadas en la capa de infraestructura.
La capa de infraestructura está definida aquí.
| Componente | Archivos | Propósito |
|---|---|---|
| DbContext | Persistence/TaskDbContext.cs | Contexto de base de datos de Entity Framework |
| Repositorios | Persistence/Repositories/TaskRepository.cs, TaskReadRepository.cs | Implementaciones de acceso a datos con Entity Framework y Dapper respectivamente |
| Configuraciones | Persistence/Configuration/TaskConfiguration.cs | Configuraciones de Entity Framework |
| Despacho de eventos | EventDispatching/MediatRDomainEventDispatcher.cs | Implementación de publicación de eventos del dominio con MediatR |
Principio clave: La capa de infraestructura implementa interfaces definidas en las capas del dominio y aplicación usando inyección de dependencias.
Esta capa implementa el patrón Repository. La interfaz ITaskRepository definida en la capa de aplicación es implementada por la clase TaskRepository definida en esta capa de infraestructura.
En esta demo la interfaz es una API web. La capa de interfaz está definida aquí.
| Componente | Archivos | Propósito |
|---|---|---|
| Controladores | Controllers/TasksController.cs | Endpoints HTTP |
| Middleware | Middleware/ExceptionHandlingMiddleware.cs | Comportamientos de pipeline transversales |
| Extensiones | Extensions/ClaimsPrincipalExtensions.cs | Métodos de extensión auxiliares |
| Solicitudes | Requests/CreateTaskRequest.cs | DTOs de entrada para solicitudes HTTP |
| Inicio | Program.cs | Raíz de composición de inyección de dependencias |
Principio clave: La capa de UI traduce HTTP a comandos o consultas de aplicación. No tiene lógica de negocio y delega todo a la capa de aplicación.
A continuación un fragmento de la clase TaskController con la implementación del POST de HTTP para crear un cliente -hay ejemplos para probar todos los endpoints aquí-.
En el programa principal en Program se realiza la inyección de dependencias.
Esta demo permite entender los siguientes beneficios de Clean Architecture: