[go: up one dir, main page]

100% encontró este documento útil (3 votos)
2K vistas261 páginas

Clean Arch NET

Este documento presenta una introducción a la arquitectura limpia con .NET. Explica los principios básicos de la arquitectura limpia como la separación de intereses, la inversión de dependencias y el principio SOLID. Luego describe arquitecturas comunes para aplicaciones web como la monolítica y la de capas múltiples, introduciendo la arquitectura limpia. Finalmente, organiza el código en la arquitectura limpia en un núcleo de aplicación, infraestructura y capa de interfaz de usuario.

Cargado por

Santiago Patiño
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
100% encontró este documento útil (3 votos)
2K vistas261 páginas

Clean Arch NET

Este documento presenta una introducción a la arquitectura limpia con .NET. Explica los principios básicos de la arquitectura limpia como la separación de intereses, la inversión de dependencias y el principio SOLID. Luego describe arquitecturas comunes para aplicaciones web como la monolítica y la de capas múltiples, introduciendo la arquitectura limpia. Finalmente, organiza el código en la arquitectura limpia en un núcleo de aplicación, infraestructura y capa de interfaz de usuario.

Cargado por

Santiago Patiño
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
Está en la página 1/ 261

Introducción a Clean Architecture con .

NET

Introducción a
Clean Architecture
con .NET
Manual de estudiante

Primera edición.
Septiembre de 2021

Soporte técnico:
soporte@mail.ticapacitacion.com

Autor
Miguel Muñoz Serafín
https://mvp.microsoft.com/es-es/publicprofile/21053

Este manual fue creado por TI Capacitación para uso personal de:

Akin Ramirez
akin.ramirez@hotmail.com

1 https://ticapacitacion.com/curso/cleanarch
Introducción a Clean Architecture con .NET

Contenido

Introducción a Clean Architecture con .NET .................................................................................. 1


Contenido .................................................................................................................................... 2
Acerca del entrenamiento............................................................................................................. 8
Objetivos ..............................................................................................................................................8
Audiencia ..............................................................................................................................................9
Requisitos .............................................................................................................................................9
Lección 1: Introducción .............................................................................................................. 10
Principios de arquitectura ................................................................................................................. 11
Separación de intereses (Separation of concerns)........................................................................ 11
Encapsulación (Encapsulation) ...................................................................................................... 12
Inversión de dependencias (Dependency inversion) .................................................................... 12
Dependencias explícitas (Explicit dependencies) .......................................................................... 15
Responsabilidad única (Single responsibility) ............................................................................... 15
Una vez y solo una (Don't repeat yourself - DRY) ......................................................................... 16
Omisión de persistencia (Persistence ignorance) ......................................................................... 16
Contextos delimitados (Bounded contexts) .................................................................................. 17
Principios SOLID ................................................................................................................................ 18
Principio de Responsabilidad Única (Single responsability) .......................................................... 18
Principio de Abierto/Cerrado (Open-closed) ................................................................................ 20
Principio de Sustitución de Liskov (Liskov substitution) ............................................................... 22
Principio de Segregación de Interface (Interface segregation) ..................................................... 25
Principio de Inversión de Dependencias (Dependency inversion) ................................................ 28
Resumen ........................................................................................................................................ 30
Arquitecturas de aplicaciones web comunes.................................................................................... 32
¿Qué es una aplicación monolítica? .............................................................................................. 32
Aplicaciones todo en uno .............................................................................................................. 32
¿Qué son las capas? ...................................................................................................................... 34
Aplicaciones de arquitectura tradicional N-Capas ........................................................................ 35

2 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Clean architecture (Arquitectura limpia) ...................................................................................... 38


Organización del código en la Arquitectura Limpia ...................................................................... 45
Núcleo de aplicación (Application Core) ................................................................................... 45
Infraestructura (Infrastructure)................................................................................................. 45
Capa de interfaz de usuario (UI Layer) ...................................................................................... 46
Lección 2: El patrón Mediator..................................................................................................... 48
Introducción ...................................................................................................................................... 49
Implementando el patrón Mediator para enviar mensajes .............................................................. 50
Crear la solución en Visual Studio ............................................................................................. 50
Implementar el patrón Mediator .............................................................................................. 53
Utilizar Inyección de Dependencias .......................................................................................... 55
Implementando el patrón Mediator para realizar operaciones ....................................................... 59
Crear una biblioteca de clases....................................................................................................... 59
Agregar los tipos de peticiones ..................................................................................................... 59
Agregar los tipos de manejadores de peticiones .......................................................................... 59
Agregar la Interface del Mediador ................................................................................................ 60
Implementar la Interface del Mediador ........................................................................................ 60
Agregar la clase para facilitar la inyección de dependencias ........................................................ 63
Probar la funcionalidad del Mediator ............................................................................................... 64
Agregar la biblioteca de clases de peticiones y sus manejadores ................................................ 64
Agregar los tipos de peticiones ..................................................................................................... 64
Agregar los tipos de manejadores de peticiones .......................................................................... 64
Agregar un nuevo proyecto Web API ............................................................................................ 65
Registrar el servicio IMediator ...................................................................................................... 66
Probar la funcionalidad ................................................................................................................. 67
Introducción a MediatR..................................................................................................................... 70
Mensajes enviados por MediatR ................................................................................................... 70
Mensajes Petición/Respuesta ....................................................................................................... 71
Mensajes de Notificación .............................................................................................................. 74
Comportamientos (Behaviors) en MediatR ...................................................................................... 77

3 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Lección 3: El patrón CQRS........................................................................................................... 83


Introducción ...................................................................................................................................... 84
Implementando el patrón CQRS ....................................................................................................... 86
Crear la solución en Visual Studio ................................................................................................. 86
Agregar el modelo Product ........................................................................................................... 86
Agregar el contexto de datos ........................................................................................................ 86
Registrar el servicio IProductContext ............................................................................................ 92
Definir la cadena de conexión ....................................................................................................... 92
Generar la base de datos .............................................................................................................. 93
El Patrón Mediator ........................................................................................................................ 95
Implementar las operaciones CRUD ............................................................................................. 96
Crear los Comandos .................................................................................................................. 96
Crear las Consultas .................................................................................................................... 99
Crear el controlador Product ...................................................................................................... 100
Probar la funcionalidad ............................................................................................................... 102
Lección 4: Manejo de excepciones ............................................................................................ 109
Introducción .................................................................................................................................... 110
Implementar excepciones de notificación ...................................................................................... 111
Preparar la solución inicial .......................................................................................................... 111
Crear las excepciones personalizadas ......................................................................................... 111
Lanzar las excepciones ................................................................................................................ 112
Modificar el controlador ProductController ............................................................................... 115
Crear los manejadores de las excepciones ................................................................................. 116
Crear el Filtro ............................................................................................................................... 118
Registrar el filtro.......................................................................................................................... 120
Probar la funcionalidad ............................................................................................................... 120
Lección 5: Validación Fluida...................................................................................................... 125
Introducción .................................................................................................................................... 126
Iniciando con la biblioteca FluentValidation ................................................................................... 127
Crear un proyecto Web API......................................................................................................... 127

4 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Instalación ................................................................................................................................... 127


Crear el primer validador ............................................................................................................ 127
Encadenando validadores ........................................................................................................... 131
Lanzando excepciones................................................................................................................. 131
Propiedades complejas ............................................................................................................... 132
Colecciones.................................................................................................................................. 140
Colecciones de tipos simples................................................................................................... 140
Colecciones de tipos complejos .............................................................................................. 143
Validación de herencia ................................................................................................................ 151
Conjunto de reglas (RuleSets) ..................................................................................................... 155
Incluyendo reglas ........................................................................................................................ 159
Inyección de dependencias ......................................................................................................... 161
Registro automático ................................................................................................................ 163
FluentValidation con MediatR......................................................................................................... 166
Crear el proyecto inicial .............................................................................................................. 166
Instalar el paquete FluentValidation ........................................................................................... 166
Crear el validador ........................................................................................................................ 166
Registrar el validador .................................................................................................................. 167
Agregar el validador al comportamiento de canalización........................................................... 167
Registrar el comportamiento de canalización ............................................................................ 168
Lección 6: El patrón Specification ............................................................................................. 172
Introducción .................................................................................................................................... 173
Entendiendo el patrón Specification ............................................................................................... 174
Crear un proyecto Web API......................................................................................................... 174
Crear un modelo.......................................................................................................................... 174
Crear un repositorio de datos ..................................................................................................... 174
Implementar reglas de negocio................................................................................................... 175
Probar la funcionalidad ............................................................................................................... 179
Implementar el patrón Specification utilizando Expresiones ......................................................... 181
Utilizando Expresiones ................................................................................................................ 181

5 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Especificaciones fuertemente tipadas ............................................................................................ 186


Combinando especificaciones ..................................................................................................... 188
Resumen.......................................................................................................................................... 191
Lección 7: El patrón Repository ................................................................................................ 192
Introducción .................................................................................................................................... 193
Beneficios del patrón Repository ................................................................................................ 193
Reduce la duplicidad de las consultas ..................................................................................... 193
Desacoplamiento de la aplicación y la capa de acceso a datos .............................................. 193
¿En la actualidad, vale la pena implementar un Repositorio? ................................................ 194
Implementando el patrón Repository ............................................................................................. 195
Crear un proyecto Web API......................................................................................................... 195
Crear el proyecto para agregar las entidades e interfaces del dominio ..................................... 195
Crear el proyecto para la implementación del Repositorio ........................................................ 195
Agregar las entidades .................................................................................................................. 195
Instalar Entity Framework Core .................................................................................................. 196
Crear el contexto de datos .......................................................................................................... 197
Registrar el contexto de datos .................................................................................................... 197
Crear la base de datos ................................................................................................................. 199
Crear un repositorio genérico ..................................................................................................... 201
Extender el repositorio genérico ................................................................................................. 202
Implementar el patrón Unit Of Work .......................................................................................... 204
Probar la funcionalidad ............................................................................................................... 207
Implementar un repositorio orientado al dominio ......................................................................... 212
Mejorar el patrón Repository con el patrón Specification.............................................................. 215
Implementar el patrón CQRS .......................................................................................................... 220
Crear la estructura de directorios ............................................................................................... 220
Instalar MediatR .......................................................................................................................... 220
Crear los comandos, consultas y manejadores .......................................................................... 220
Registrar los comandos, consultas y manejadores ..................................................................... 225
Modificar los controladores ........................................................................................................ 225

6 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Lección 8: Implementando una Arquitectura Limpia ................................................................. 228


Introducción .................................................................................................................................... 229
La Regla de la Dependencia......................................................................................................... 230
Reglas de negocio empresariales (Enterprise Business Rules): Entidades (Entities) ................. 230
Reglas de negocio de la aplicación (Application Business Rules): Casos de Uso (Use Cases) .... 231
Adaptadores de Interfaz (Interface Adapters) ............................................................................ 232
Frameworks y Drivers.................................................................................................................. 233
¿Solo 4 círculos? .......................................................................................................................... 233
Cruzando los limites .................................................................................................................... 233
¿Qué datos cruzan los límites?.................................................................................................... 234
Conclusión ................................................................................................................................... 234
Implementando Clean Architecture con .NET................................................................................. 235
El Caso de Uso ............................................................................................................................. 235
Creación de la estructura de la solución ..................................................................................... 235
Agregar los elementos de la capa Enterprise Business Rules ..................................................... 236
Agregar el repositorio a la capa Interface Adapters ................................................................... 238
Agregar los elementos de la capa Application Business Rules.................................................... 238
Realizar la Inversión de Control .................................................................................................. 241
Agregar el ViewModel ................................................................................................................. 243
Agregar el Presentador ............................................................................................................... 243
Agregar la Vista ........................................................................................................................... 244
Agregar el Controlador ................................................................................................................ 245
Agregar la aplicación de Consola ................................................................................................ 246
Probar la funcionalidad ............................................................................................................... 247
Agregar un cliente Web API ............................................................................................................ 249
Agregar un presentador .............................................................................................................. 251
Agregar un cliente Blazor Web Assembly ....................................................................................... 254
Resumen.......................................................................................................................................... 260

7 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Acerca del entrenamiento


El crecimiento acelerado de las tecnologías y frameworks web, así como las demandas actuales de los
usuarios, han cambiado el enfoque para la creación de aplicaciones empresariales. Existen muchos
desafíos, tan solo empezar por elegir una arquitectura de software apropiada para resolver
necesidades específicas puede ser algo abrumador.
Clean Architecture (Arquitectura Limpia) es un concepto popularizado por Robert Cecil Martin
conocido como “Uncle Bob”. Clean Architecture, se compone de un conjunto de patrones, prácticas y
principios para crear una arquitectura de software que sea simple, comprensible, flexible,
comprobable y mantenible.
El propósito principal de la arquitectura es respaldar el ciclo de vida del sistema. Una buena
arquitectura hace que el sistema sea fácil de entender, fácil de desarrollar, fácil de mantener y fácil de
implementar. El objetivo final es minimizar el costo de vida útil del sistema y maximizar la
productividad del programador.
Este entrenamiento, proporciona una guía práctica con recomendaciones para implementar patrones,
buenas prácticas, principios y frameworks que nos permitan construir una arquitectura limpia en
nuestros desarrollos de software con .NET.
Durante el entrenamiento implementaremos estrategias para organizar el código de nuestros
proyectos, directorios y archivos con el fin de diseñar sistemas que sean simples de construir y
mantener ahora y en el futuro.
En la parte final del entrenamiento desarrollaremos una aplicación .NET que ejemplifique cada uno de
los elementos descritos de Clean Architecture.

Objetivos
Al finalizar este entrenamiento los participantes serán capaces de:

• Describir los principales Principios de Arquitectura de Software.


• Describir y aplicar los Principios SOLID para crear código limpio.
• Describir las Arquitecturas de aplicaciones web más comunes incluyendo Clean Architecture.
• Implementar el patrón Mediator para desarrollar aplicaciones que sigan la estrategia
petición/respuesta y obtener una inversión de dependencia entre los componentes.
• Implementar el patrón CQRS para lograr una separación de responsabilidades entre el código
encargado de la escritura de datos y el código para realizar consultas.
• Implementar una estrategia de manejo de excepciones centralizada para devolver respuestas
apropiadas.
• Utilizar la biblioteca FluentValidation para implementar una validación fluida en lugar de la
validación a través de anotaciones de datos en las clases modelo, logrando con esto una
separación de responsabilidades.

8 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

• Implementar el patrón Specification para evitar la duplicidad de código al implementar reglas


de negocio.
• Implementar el patrón Repository para hacer independiente el código de acceso a datos del
código que implementa las reglas de negocio.
• Iniciar el desarrollo de aplicaciones .NET implementando principios, patrones y buenas
prácticas para cumplir las recomendaciones de Clean Architecture.

Audiencia
Este curso está dirigido a todas las personas que se encuentren interesadas en conocer los principios,
patrones y buenas prácticas para empezar a desarrollar aplicaciones implementando una Arquitectura
de software limpia (Clean Architecture).

Como curso introductorio, no hay requisitos previos para este entrenamiento. Sin embargo, para
poder aprovechar al máximo este entrenamiento, se recomienda que los participantes tengan
conocimientos básicos del lenguaje de programación C# y de ASP.NET Core.

Requisitos
Para poder practicar los conceptos y realizar los ejercicios del curso, se recomienda trabajar con un
equipo de desarrollo con Windows 10 y Visual Studio 2019 versión 16.10 o posteriores.

9 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Lección 1:

Introducción
Antes de iniciar nuestro recorrido por la ruta del desarrollo de una Arquitectura de Software Limpia,
empecemos por conocer algunos de los principios fundamentales que debemos tomar en cuenta para
facilitar nuestro camino.

Objetivos

Al finalizar esta lección, los participantes podrán:

• Describir los principales Principios de Arquitectura de Software.


• Describir los Principios SOLID para crear código limpio.
• Describir las Arquitecturas de aplicaciones web más comunes incluyendo Clean Architecture.

10 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Principios de arquitectura
El científico informático Gerald Weinberg dijo una vez que si los constructores construyeran los
edificios de la misma forma en que los programadores escriben los programas, el primer pájaro
carpintero que apareciera destruiría la civilización.

Debemos crear la arquitectura y el diseño de soluciones de software teniendo en mente la facilidad de


mantenimiento.

En el E-Book “Architect Modern Web Applications with ASP.NET Core and Azure” que podemos
encontrar en el sitio de Microsoft (https://docs.microsoft.com/en-us/dotnet/architecture/modern-
web-apps-azure/), se describen los principios de arquitectura que nos pueden ayudar a tomar
decisiones arquitectónicas que darán como resultado aplicaciones limpias y fácil de mantener.

Los principios mencionados en el e-book “Architect Modern Web Applications with ASP.NET Core and
Azure” que describiremos a continuación, nos servirán de guía para crear aplicaciones a partir de
componentes discretos que no estén estrechamente acoplados con otras partes de la aplicación, sino
que se comuniquen a través de interfaces explícitas o sistemas de mensajería.

Separación de intereses (Separation of concerns)


Un principio fundamental del desarrollo de software es la Separación de intereses. Este principio
afirma que el software debe ser separado en función de los tipos de trabajo que realiza. Por ejemplo,
consideremos una aplicación que incluye lógica para identificar los elementos de interés que se van a
mostrar al usuario y que da formato a estos elementos de una manera determinada para hacerlos más
notables. El comportamiento responsable de elegir a qué elementos dar formato se debe mantener
separado del comportamiento responsable de dar formato a los elementos, ya que estos son intereses
independientes que solo se relacionan entre sí de manera casual.

Desde el punto de vista de la arquitectura, las aplicaciones pueden ser creadas lógicamente para seguir
este principio separando la lógica principal de negocio (¿qué elementos deben ser mostrados al
usuario?) de la lógica de la interfaz de usuario (dar formato a los datos a mostrar) y la infraestructura
(¿Cómo obtener los datos?).

Idealmente, la lógica y las reglas de negocio deben residir en un proyecto independiente, que no
debería depender de otros proyectos de la aplicación. Esta separación ayuda a garantizar que el
modelo de negocio sea fácil de probar y pueda evolucionar sin que esté estrechamente unido a detalles
de implementación de bajo nivel. La separación de intereses es una consideración clave detrás del uso
de capas en arquitecturas de aplicación.

11 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Encapsulación (Encapsulation)
Las diferentes partes de una aplicación deben usar la encapsulación para aislarse de otras partes de la
aplicación. Las capas y los componentes de la aplicación deben ser capaces de ajustar su
implementación interna sin interrumpir a sus colaboradores mientras no se infrinjan los contratos
externos. El uso correcto de la encapsulación contribuye a lograr el acoplamiento flexible y
modularidad en los diseños de aplicaciones, ya que los objetos y paquetes pueden ser remplazados
con implementaciones alternativas, siempre y cuando se mantenga la misma interfaz.

En las clases, la encapsulación se logra mediante la limitación del acceso externo al estado interno de
la clase. Si un actor externo quiere manipular el estado del objeto, deberá hacerlo a través de una
función bien definida (o un descriptor de acceso de propiedad), en lugar de tener acceso directo al
estado privado del objeto. Del mismo modo, los componentes de aplicación y las propias aplicaciones
deben exponer interfaces bien definidas para que sus colaboradores las usen, en lugar de permitir que
su estado se modifique directamente. Este enfoque libera el diseño interno de la aplicación para que
evolucione con el tiempo sin tener que preocuparse de sí, al hacerlo, interrumpirá a los colaboradores,
siempre y cuando se mantengan los contratos públicos.

En C#, la encapsulación en la clase se logra declarando variables privadas para almacenar el estado de
un objeto y declarando métodos o propiedades públicas para permitir el acceso indirecto a esas
variables.

Inversión de dependencias (Dependency inversion)


La dirección de dependencia dentro de la aplicación debe estar en la dirección de la abstracción, no de
los detalles de implementación. La mayoría de las aplicaciones se escriben de manera que la
dependencia del tiempo de compilación fluya en la dirección del tiempo de ejecución, lo que produce
un gráfico de dependencias directa. Es decir, si la clase A invoca a un método de la clase B y la clase B
invoca a un método de la clase C, en tiempo de compilación la clase A dependerá de la clase B y la clase
B de la clase C, como se muestra en la siguiente figura.

12 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Aplicando el principio de inversión de dependencias permite que la clase A invoque a métodos de una
abstracción que B implementa, lo que hace posible que A invoque métodos de B en tiempo de
ejecución, pero que B dependa de una Interface controlada por A en tiempo de compilación (por tanto,
se invierte la típica dependencia de tiempo de compilación). En tiempo de ejecución, el flujo de
ejecución del programa no cambia, pero la introducción de interfaces hace posible que se puedan
conectar fácilmente otras implementaciones de estas interfaces.

La Inversión de dependencias es una parte fundamental de la creación de aplicaciones de


acoplamiento flexible, ya que se pueden escribir detalles de implementación de los que depender e
implementar abstracciones de nivel superior, en lugar de hacerlo al revés. Como resultado, las
aplicaciones son modulares y más fáciles de probar y mantener. La práctica de la inyección de
dependencias (Dependency injection) es posible si se sigue el principio de Inversión de dependencias.

13 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

El siguiente ejemplo de una tradicional arquitectura N-Capas, ilustra una gráfica de dependencia
directa entre la capa de lógica de negocios (BL), la capa de acceso a datos (Dal) y una capa que
implementa un repositorio de datos (Repository). En tiempo de compilación, BL agrega una referencia
de Dal y Dal agrega una referencia de Repository. En tiempo de ejecución, BL invoca métodos de Dal y
Dal invoca métodos de Repository. Hay una dependencia directa entre las distintas capas.

El siguiente ejemplo, ilustra una gráfica de dependencia invertida donde se han agregado 2 interfaces.
BL especifica lo que necesita consumir a través de la Interface IDal. Dal implementa la Interface IDal.
Dal especifica lo que necesita consumir a través de la Interface IRepository. Repository implementa la
Interface IRepository.

14 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

En tiempo de compilación, BL agrega una referencia de IDal o incluso, la Interface IDal podría haber
sido definida dentro del proyecto BL. Dal debe agregar una referencia del proyecto donde está definida
la Interface IDal haciéndose evidente la inversión de dependencias que se vería mucho más claro si
IDal se encontrara definida en el proyecto BL. Dal agrega una referencia de IRepository y Repository
agrega una referencia de IRepository, otra inversión de dependencia.

En tiempo de ejecución, BL puede consumir cualquier objeto que implemente IDal mientras que Dal
puede consumir cualquier objeto que implemente IRepository. A través de inyección de dependencias
podemos hacer que BL consuma una instancia de Dal y podemos hacer que Dal consuma una instancia
de Repository.

Dependencias explícitas (Explicit dependencies)


Los métodos y las clases deben requerir explícitamente todos los objetos de colaboración que
necesiten para funcionar correctamente. Los constructores de clases proporcionan una oportunidad
para que las clases identifiquen lo que necesitan para poder tener un estado válido y funcionar
correctamente. Si se definen clases que se pueden construir y a las que se puede invocar, pero que
solo funcionarán correctamente si existen determinados componentes globales o de infraestructura,
estas clases no estarán siendo honestas con sus clientes. El contrato de constructor estará indicando
al cliente que solo necesita los elementos especificados (posiblemente ninguno si la clase solo usa un
constructor sin parámetros), pero después, en tiempo de ejecución, en realidad el objeto necesitará
algo más.

Al seguir el principio de dependencias explícitas, las clases y métodos estarán siendo honestos con sus
clientes con respecto a lo que necesitan para poder funcionar. Seguir este principio hace que el código
sea más autoexplicativo y los contratos de codificación más fáciles de usar, puesto que los usuarios
confiarán en que siempre que proporcionen lo que se necesita en forma de parámetros de método o
constructor, los objetos con los trabajan se comportarán correctamente en tiempo de ejecución.

Responsabilidad única (Single responsibility)


El principio de responsabilidad única se aplica al diseño orientado a objetos, pero también se puede
considerar como un principio de arquitectura similar a la separación de intereses. Indica que los
objetos solo deben tener una responsabilidad y solo una razón para cambiar. En concreto, la única
situación en la que el objeto debe cambiar es si hay que actualizar la manera en la que lleva a cabo su
única responsabilidad. El hecho de seguir este principio ayuda a generar sistemas más modulares y de
acoplamiento flexible, dado que muchos tipos de comportamientos nuevos se pueden implementar
como clases nuevas, en lugar de mediante la incorporación de responsabilidad adicional a las clases
existentes. Agregar clases nuevas siempre es más seguro que cambiar las existentes, puesto que
todavía no hay código que dependa de las clases nuevas.

15 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

En una aplicación monolítica, se puede aplicar el principio de responsabilidad única en un nivel general
a las capas de la aplicación. La responsabilidad de la presentación debe mantenerse en el proyecto de
la interfaz de usuario, mientras que la responsabilidad de acceso a los datos se debe mantener en un
proyecto de infraestructura. La lógica de negocios se debe mantener en el proyecto principal de la
aplicación, donde se puede probar fácilmente y puede evolucionar con independencia de otras
responsabilidades.

Cuando este principio se aplica a la arquitectura de la aplicación y se lleva a su punto de conexión


lógico, se obtienen microservicios. Un microservicio determinado debe tener una sola responsabilidad.
Si es necesario extender el comportamiento de un sistema, es mejor hacerlo agregando otros
microservicios, en lugar de agregar responsabilidad a uno ya existente.

Una vez y solo una (Don't repeat yourself - DRY)


La aplicación debe evitar especificar el comportamiento relacionado con un determinado concepto en
varios lugares, ya que esta práctica es una fuente de errores frecuente. En algún momento, un cambio
en los requisitos requerirá cambiar este comportamiento. Es probable que al menos una instancia del
comportamiento no se pueda actualizar, y que el sistema se comporte de manera incoherente.

En lugar de duplicar la lógica, se puede encapsular en una construcción de programación. Debemos


convertir esta construcción en la única autoridad sobre este comportamiento y hacer que cualquier
otro elemento de la aplicación que requiera este comportamiento use la nueva construcción.

Una nota importante respecto a este principio es evitar el enlace conjunto del comportamiento que
solo se repita de forma casual (por coincidencia). Por ejemplo, solo porque dos constantes diferentes
tengan el mismo valor no significa que deberíamos tener solo una constante si conceptualmente se
refieren a cosas diferentes.

Omisión de persistencia (Persistence ignorance)


La Omisión de persistencia (PI) hace referencia a los tipos que se deben conservar, pero cuyo código
no se ve afectado por la elección de la tecnología de persistencia. En .NET, estos tipos a veces se
denominan Objeto CRL estándar (Plain Old CLR Objects - POCO), ya que no necesitan heredar de una
clase base concreta o implementar una interfaz determinada. La omisión de persistencia es útil porque
permite conservar el mismo modelo de negocio de varias formas, lo que ofrece flexibilidad adicional a
la aplicación. Es posible que las opciones de persistencia cambien con el tiempo, de una tecnología de
base de datos a otra, o bien que se necesiten otras formas de persistencia además de las iniciales de
la aplicación (por ejemplo, el uso de una caché en Redis o Azure Cosmos DB además de un base de
datos relacional).

Algunos ejemplos de las violaciones de este principio son:

16 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

• Una clase base requerida.


• Una implementación de Interface requerida.
• Clases responsables de guardarse a sí mismas (por ejemplo, el patrón de Active Record).
• Constructor sin parámetros requerido.
• Propiedades que requieren la palabra clave virtual.
• Atributos requeridos específicos de la persistencia.

El requisito de que las clases tengan cualquiera de las características o comportamientos anteriores
agrega acoplamiento entre los tipos que se deben conservar y la elección de la tecnología de
persistencia, lo que dificulta la adopción de nuevas estrategias de acceso de datos en el futuro.

Contextos delimitados (Bounded contexts)


Los Contextos delimitados son un patrón esencial en el diseño controlado por dominios (Domain-
Driven Design). Proporcionan una manera de abordar la complejidad de organizaciones o aplicaciones
de gran tamaño dividiéndola en módulos conceptuales independientes. Después, cada módulo
conceptual representa un contexto que está separado de otros contextos (por tanto, delimitado) y que
puede evolucionar independientemente. Idealmente, cada contexto delimitado debería poder elegir
sus propios nombres para los conceptos que contiene y tener acceso exclusivo a su propio almacén de
persistencia.

Como mínimo, las aplicaciones web individuales deberían intentar ser su propio contexto delimitado,
con su propio almacén de persistencia para su modelo de negocios, en lugar de compartir una base de
datos con otras aplicaciones. La comunicación entre los contextos delimitados se realiza a través de
interfaces de programación, en lugar de una base de datos compartida, lo que permite que la lógica
de negocios y los eventos se produzcan en respuesta a los cambios que tienen lugar. Los contextos
delimitados se asignan estrechamente a los microservicios que, idealmente, también se implementan
como sus propios contextos delimitados individuales.

17 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Principios SOLID
Al hablar de diseño y desarrollo de software, no podemos dejar de mencionar los principios SOLID
como uno de los fundamentos de la arquitectura y desarrollo de software.

SOLID es un acrónimo que acuñó Michael Feathers basándose en los principios de la programación
orientada a objetos que Robert C. Martin había recopilado a principios de la década del 2000 en su
documento “Design Principles and Design Patterns”.

SOLID representa 5 principios básicos de la programación orientada objetos:

• S - Single responsability (SRP)


• O - Open-closed (OCP)
• L - Liskov substitution (LSP)
• I - Interface segregation (ISP)
• D - Dependency inversion (DIP)

Aplicar estos principios en conjunto nos permitirán crear sistemas que sean fáciles de mantener y
ampliar con el tiempo, contribuyendo a la creación de una Arquitectura Limpia.

Principio de Responsabilidad Única (Single responsability)


Según este principio una clase debería tener una, y solo una, razón para cambiar. “razón para cambiar”
es lo que Robert C. Martin identifica como “responsabilidad”.

Esto quiere decir que una clase debería estar destinada a una única responsabilidad y no mezclar otras
que no le incumben a su dominio.

Para lograr este principio Robert C. Martin nos sugiere reunir las cosas que cambian por las mismas
razones y separar aquellas que cambian por razones diferentes.

Examinemos la siguiente definición de la clase ProductService que nos permite crear un producto y
registrar esa actividad para llevar un historial de acciones.

public class ProductService


{
public void Create(Product product)
{
// Código para crear el producto
// .....
//

// Registrar la actividad
var Activity = new Activity
{
Message = $"Producto agregado {product.Id}",
Module = nameof(Create)

18 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

};
WriteLog(Activity);
}

public void WriteLog(Activity activity)


{
// Código para registrar la actividad
Console.WriteLine("{0}, {1}, {2}",
activity.CreatedDate, activity.Module, activity.Message);
}
}

Por simplicidad, podemos utilizar el siguiente código para define las clases Product y Activity.

public class Product


{
public int Id { get; set; }
}

public class Activity


{
public string Message { get; set; }
public string Module { get; set; }
public DateTime CreatedDate { get; set; } = DateTime.Now;
}

El problema con el código de la clase ProductService es que además de crear productos, que es la
responsabilidad principal por la que fue creada, registra actividades que no son propiamente su
responsabilidad.

Respetando el principio de Responsabilidad única, podemos separar el código de la clase en 2 clases


de la siguiente manera.

public class ProductService


{
private readonly LogService LogService;

public ProductService(LogService logService) =>


LogService = logService;

public void Create(Product product)


{
// Código para crear el producto
// .....
//

// Registrar la actividad
var Activity = new Activity
{
Message = $"Producto agregado {product.Id}",
Module = nameof(Create)
};
LogService.Write(Activity);

19 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

}
}

public class LogService


{
public void Write(Activity activity)
{
// Código para registrar la actividad
Console.WriteLine("{0}, {1}, {2}",
activity.CreatedDate, activity.Module, activity.Message);
}
}

Con esta separación, el código es más fácil de mantener al tener las responsabilidades separadas en
distintas clases.

Principio de Abierto/Cerrado (Open-closed)


Este principio fue formulado por Bertrand Meyer en 1988 en su libro “Object Oriented Software
Construction” y nos dice que cada entidad de software debe estar abierta para extensión, pero cerrada
para modificación. En otras palabras, las clases que diseñemos deben estar abiertas para poder
extenderse y cerradas para modificarse. Una clase debe poder ser extendida sin tener que modificarse
internamente.

Robert C. Martin defendió este principio que puede parecer una paradoja. Es importante tener en
cuenta este principio a la hora de desarrollar clases, librerías o frameworks.

Examinemos la siguiente definición de la clase LogService que nos permite registrar actividades
realizadas en una aplicación. Un requisito que se nos ha pedido sobre esta clase es que pueda registrar
actividades hacia la consola, hacia un archivo y en un futuro a otros destinos, por ejemplo, el log de
Windows o a través de correo electrónico.

public class LogService


{
public void Write(Activity activity, string target)
{
switch(target)
{
case "console":
WriteToConsole(activity);
break;
case "file":
WriteToFile(activity);
break;
}
}
void WriteToConsole(Activity activity)
{
// Código para escribir a consola
}

20 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

void WriteToFile(Activity activity)


{
// Código para escribir a archivo
}
}

El código anterior rompe el principio de Abierto/Cerrado ya que si queremos agregar en un futuro la


capacidad de registrar actividades al log de Windows tendremos que agregar un case adicional con su
método respectivo.

Refactoricemos el código de la clase LogService agregando una Interface y clases que implementen esa
Interface.

public interface ILogger


{
void Write(Activity activity);
}

public class ConsoleLogger : ILogger


{
public void Write(Activity activity)
{
// Lógica para registrar la actividad
}
}
public class FileLogger : ILogger
{
public void Write(Activity activity)
{
// Lógica para registrar la actividad
}
}

La clase LogService puede quedar de la siguiente forma.

public class LogService


{
private readonly ILogger Logger;

public LogService(ILogger logger) =>


Logger = logger;

public void Write(Activity activity)


{
Logger.Write(activity);
}
}

Ahora la clase LogService ya no necesitará ser modificada si se agregan nuevas formas de registrar
actividades.

21 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Principio de Sustitución de Liskov (Liskov substitution)


Este principio fue formulado por Barbara Liskov y la L de SOLID hace referencia al apellido de quien lo
creó. Este principio nos dice que los objetos que utilicemos en un programa deberían poder ser
reemplazables con instancias de sus subclases sin alterar el correcto funcionamiento del programa. En
otras palabras, una clase derivada puede ser utilizada como si fuera la clase base sin alterar la
funcionalidad.

Según Robert C. Martin incumplir este principio implica violar también el principio de Abierto/Cerrado.

Examinemos el siguiente código que define una clase que implementa las operaciones Create, Update
y Delete.

public class Entity


{
public virtual void Create()
{
// Código de la lógica predeterminada
}
public virtual void Update()
{
// Código de la lógica predeterminada
}
public virtual void Delete()
{
// Código de la lógica predeterminada
}
}

La clase Product hereda de la clase Entity e implementa los 3 métodos.

public class Product:Entity


{
public override void Create()
{
// Código de la implementación
}

public override void Update()


{
// Código de la implementación
}
public override void Delete()
{
// Código de la implementación
}
}

Ahora deseamos implementar la clase Category que herede de Entity pero que solo exponga la
funcionalidad Create. Al heredar de la clase Entity, los métodos que no deseamos implementar en la

22 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

clase Category quedan expuestos por herencia. Una solución simple es sobrescribirlos y hacer que
lancen una excepción o dejarlos vacíos. Esto rompe con el principio.

public class Category : Entity


{
public override void Create()
{
// Código de la implementación
}

// Esta clase no implementa esta funcionalidad


public override void Update()
{
throw new NotImplementedException();
}

// Esta clase no implmenta esta funcionalidad


public override void Delete()
{
throw new NotImplementedException();
}
}

El siguiente código que utiliza las clases anteriores, muestra que no obtenemos el mismo
comportamiento al remplazar instancias de clases base por cualquiera de sus clases derivadas.

public class TestClass


{
public void Test()
{
Entity E = new Product();
E.Create(); // Funciona correctamente.
E.Update(); // Funciona correctamente.
E.Delete(); // Funciona correctamente.

E = new Category();
E.Create(); // Funciona correctamente.
E.Update(); // Lanza una excepción.
E.Delete(); // Lanza una excepción.
}
}

Una posible solución a este problema es definir Interfaces creando de esta manera un sistema más
modular.

El siguiente código muestra 3 interfaces sugeridas.

public interface ICreateable


{
void Create();
}

public interface IUpdateable

23 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

{
void Update();
}

public interface IDeleteable


{
void Delete();
}

El siguiente código muestra la clase Product implementando las 3 operaciones.

public class Product : ICreateable, IUpdateable, IDeleteable


{
public void Create()
{
// Código de la implementación
}

public void Update()


{
// Código de la implementación
}

public void Delete()


{
// Código de la implementación
}
}

El siguiente código muestra la clase Category implementando solo una operación.

public class Category : ICreateable


{
public void Create()
{
// Código de la implementación
}
}

El siguiente código muestra la forma en que podemos utilizar las clases anteriores.

public class TestClass


{
public void Test()
{
ICreateable C = new Product();
C.Create(); // Funciona correctamente.

C = new Category();
C.Create(); // Funciona correctamente.
}
}

Ahora ya podemos trabajar únicamente con clases que puedan crear, modificar o eliminar.

24 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Principio de Segregación de Interface (Interface segregation)


Este principio nos dice que las clases no deberían ser forzadas a depender de interfaces que no utilicen.
El principio nos indica que es preferible contar con muchas interfaces que definan pocos métodos en
lugar de tener una interface que obligue a implementar muchos métodos que no serán utilizados.
Muchas Interfaces son mejores que una sola Interface.

Examinemos el siguiente código que define una Interface para implementar repositorios para realizar
operaciones CRUD básicas.

public interface IRepository<T>


{
void Create(T Entity);
T RetrieveById(int id);
IEnumerable<T> RetrieveAll();
void Update(T Entity);
void Delete(int id);
}

Si deseamos un repositorio que implemente toda la funcionalidad podríamos tener un código similar
al siguiente.

public class ProductRepository : IRepository<Product>


{
public void Create(Product product)
{
// Código de la implementación
}

public Product RetrieveById(int id)


{
Product Result = default;

// Código de la implementación

return Result;
}

public IEnumerable<Product> RetrieveAll()


{
List<Product> Result = default;

// Código de la implementación

return Result;
}

public void Update(Product product)


{
// Código de la implementación
}

25 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

public void Delete(int id)


{
// Código de la implementación
}
}

¿Qué pasa ahora si queremos implementar un repositorio que solo implemente la funcionalidad de
recuperación de datos? La clase estaría obligada a implementar cada una de las operaciones y lanzar
excepciones en las operaciones no soportadas.

public class CategoryRepository : IRepository<Category>


{
public void Create(Category category)
{
throw new NotImplementedException();
}

public Category RetrieveById(int id)


{
Category Result = default;

// Código de la implementación

return Result;
}

public IEnumerable<Category> RetrieveAll()


{
List<Category> Result = default;

// Código de la implementación

return Result;
}

public void Update(Category category)


{
throw new NotImplementedException();
}

public void Delete(int id)


{
throw new NotImplementedException();
}
}

Siguiendo el principio de segregación de Interface, la solución sería definir interfaces pequeñas para
construir componentes más modulares.

public interface IReadable<T>


{
T RetrieveById(int id);
IEnumerable<T> RetrieveAll();
}

26 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

public interface IWriteable<T>


{
void Create(T Entity);
void Update(T Entity);
}

public interface IDeleteable


{
void Delete(int id);
}

El código de la clase ProductRepository podría ser similar al siguiente.

public class ProductRepository :


IReadable<Product>, IWriteable<Product>, IDeleteable
{
public void Create(Product product)
{
// Código de la implementación
}

public Product RetrieveById(int id)


{
Product Result = default;

// Código de la implementación

return Result;
}

public IEnumerable<Product> RetrieveAll()


{
List<Product> Result = default;

// Código de la implementación

return Result;
}

public void Update(Product product)


{
// Código de la implementación
}

public void Delete(int id)


{
// Código de la implementación
}
}

Si queremos implementar un repositorio que solo implemente la funcionalidad de recuperación de


datos podríamos tener el siguiente código.

27 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

public class CategoryRepository : IReadable<Category>


{
public Category RetrieveById(int id)
{
Category Result = default;

// Código de la implementación

return Result;
}

public IEnumerable<Category> RetrieveAll()


{
List<Category> Result = default;

// Código de la implementación

return Result;
}
}

Principio de Inversión de Dependencias (Dependency inversion)


Este principio nos dice que los módulos de alto nivel no deben depender de módulos de bajo nivel,
ambos deben depender de una abstracción. Además, la abstracción no debe depender de los detalles,
los detalles deben depender de las abstracciones.

El objetivo de este principio consiste en reducir las dependencias entre los módulos del código, es
decir, alcanzar un bajo acoplamiento de las clases. Construir software con demasiadas dependencias
puede ocasionar que, en un futuro, al querer modificar una pieza de código, debamos refactorizar todo
el proyecto, lo cual no debe ser asi.

Examinemos el siguiente código que registra una actividad después de crear un producto. La clase
ConsoleLogger nos permite registrar actividades hacia la Consola y es invocado desde la clase
ProductService.

public class ConsoleLogger


{
public void Write(Activity activity)
{
// Código de la implementación
}
}

public class ProductService


{
readonly ConsoleLogger Logger;
public ProductService(ConsoleLogger logger) =>
Logger = logger;

28 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

public void Create(Product product)


{
// Lógica para crear el producto
// ....

// Escribir al log...
var Activity = new Activity
{
Message = $"Producto agregado {product.Id}",
Module = nameof(Create)
};
Logger.Write(Activity);
}
}

El código anterior funciona muy bien, sin embargo, ¿qué pasará cuando queramos registra actividades
hacia un archivo o hacia el log de Windows? Tendremos que cambiar todas las referencias que
hacemos de ConsoleLogger.

La solución es invertir las dependencias y trabajar con abstracciones en vez de la implementación.

El siguiente código representa una abstracción que nos permitirá escribir hacia un servicio de Log.

public interface ILogger


{
void Write(Activity activity);
}

Podemos ahora implementar los servicios de Log.

public class ConsoleLogger : ILogger


{
public void Write(Activity activity)
{
// Código de la implementación
}
}

public class FileLogger : ILogger


{
public void Write(Activity activity)
{
// Código de la implementación
}
}

La clase ProductService será similar a la siguiente.

public class ProductService


{
readonly ILogger Logger;
public ProductService(ILogger logger) =>
Logger = logger;

29 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

public void Create(Product product)


{
// Lógica para crear el producto…

// Escribir al log...
var Activity = new Activity
{
Message = $"Producto agregado {product.Id}",
Module = nameof(Create)
};
Logger.Write(Activity);
}
}

Ahora ProductService podrá trabajar con cualquier proveedor de servicios de Log ya que solo hace uso
de la Interface y no de la clase que la implementa.

Cambiar de proveedor de servicios ya no es un problema debido a que ya no tenemos una fuerte


dependencia hacia la clase al realizar una inversión de dependencia.

En una aplicación ASP.NET Core, utilizando el servicio de inyección de dependencias podemos hacer
que ProductService consuma una instancia de ConsoleLogger o de cualquier otro proveedor.

public void ConfigureServices(IServiceCollection services)


{

// . . .

// Registrar el servicio en el contenedor de dependencias


services.AddScoped<ILogger, ConsoleLogger>();
}

Resumen
Los principios SOLID son buenas prácticas que nos pueden ayudar a escribir un mejor código, más
limpio, mantenible y escalable. Como indica el propio Robert C. Martin en su artículo “Getting a SOLID
start”, no se tratan de reglas, ni leyes, ni verdades absolutas, sino más bien soluciones de sentido
común a problemas comunes basados en la experiencia, funcionan en muchos casos, pero no hay
pruebas de que siempre funcionen, ni de que siempre se deban seguir.

En los siguientes enlaces se puede encontrar más información relacionada con los
principios SOLID.

Getting a SOLID start


https://sites.google.com/site/unclebobconsultingllc/getting-a-solid-start

30 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Design Principles and Design Patterns


http://www.objectmentor.com/resources/articles/Principles_and_Patterns.pdf

31 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Arquitecturas de aplicaciones web comunes


La mayoría de las aplicaciones .NET tradicionales son implementadas como unidades únicas que
corresponden a un archivo ejecutable o a una sola aplicación web que se ejecuta dentro de un único
dominio de aplicación de IIS. Este enfoque es el modelo de implementación más sencillo, y sirve muy
bien a muchas aplicaciones internas y públicas más pequeñas. Pero incluso con esta única unidad de
implementación, la mayoría de las aplicaciones de negocio importantes se benefician de cierta
separación lógica en varias capas.

¿Qué es una aplicación monolítica?


Una aplicación monolítica es aquella completamente autónoma e independiente en términos de su
funcionalidad. Una aplicación monolítica está diseñada sin modularidad, por ejemplo, la lógica de
acceso a datos, la lógica de presentación y la lógica de negocios están combinadas en un mismo
programa. Puede interactuar con otros servicios o almacenes de datos en el transcurso de sus
operaciones, pero el núcleo de su funcionalidad se ejecuta dentro de su propio proceso y toda la
aplicación normalmente se implementa como una única unidad. Si es necesario escalar
horizontalmente este tipo de aplicación, normalmente la aplicación completa se duplica en varios
servidores o máquinas virtuales.

Aplicaciones todo en uno


El menor número posible de proyectos para una arquitectura de aplicación es uno. En esta
arquitectura, toda la lógica de la aplicación está contenida en un solo proyecto, se compila en un único
ensamblado y se implementa como una sola unidad.

Un proyecto nuevo de ASP.NET Core, independientemente de que se cree en Visual Studio o desde la
línea de comandos, empieza como un simple monolito "todo en uno". Contiene todo el
comportamiento de la aplicación, incluida la lógica de presentación, de negocios y de acceso a datos.

La siguiente figura muestra la estructura de archivos de una aplicación monolítica, todo en un


proyecto.

32 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

En un escenario de un solo proyecto, la separación de intereses se logra mediante el uso de carpetas.


La plantilla predeterminada incluye carpetas independientes para las responsabilidades del patrón
MVC de Modelos, Vistas y Controladores, así como carpetas adicionales para los datos y servicios. En
esta disposición, los detalles de presentación deben estar limitados tanto como sea posible a la carpeta
Views y los detalles de implementación del acceso a datos se deben limitar a las clases de la carpeta
Data. La lógica de negocios debe residir en las clases de la carpeta Services y Models.

Aunque es simple, la solución monolítica de un solo proyecto tiene algunas desventajas. A medida que
aumenta el tamaño y la complejidad del proyecto, el número de archivos y carpetas también seguirá
creciendo. Los intereses de la interfaz de usuario (IU) (modelos, vistas, controladores) residen en varias
carpetas, que no están agrupadas alfabéticamente. Este problema empeora cuando se agregan otras
construcciones de nivel de la interfaz de usuario, como filtros o enlazadores de modelos
(ModelBinders) en sus propias carpetas. La lógica de negocios se distribuye entre las carpetas Models

33 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

y Services, y no hay ninguna indicación clara de qué clases de qué carpetas deben depender de otras.
Esta falta de organización en el nivel del proyecto suele dar lugar a código espagueti.

Para resolver estos problemas, las aplicaciones suelen evolucionar a soluciones de varios proyectos,
donde se considera que cada proyecto reside en una determinada capa de la aplicación.

¿Qué son las capas?


Cuando aumenta la complejidad de las aplicaciones, una manera de administrarla consiste en dividir
la aplicación según sus responsabilidades o intereses. Este enfoque sigue el principio de separación de
intereses y puede ayudar a mantener organizado un código base creciente para que los desarrolladores
puedan encontrar fácilmente dónde se implementa una función determinada. Sin embargo, la
arquitectura en capas ofrece una serie de ventajas que van más allá de la simple organización del
código.

Al organizar el código en capas, la funcionalidad común de bajo nivel se puede reutilizar en toda la
aplicación. Esta reutilización es beneficiosa ya que significa escribir menos código y puede permitir que
la aplicación se estandarice en una sola implementación, siguiendo el principio DRY.

Con una arquitectura en capas, las aplicaciones pueden aplicar restricciones sobre qué capas se
pueden comunicar con otras capas. Esta arquitectura permite lograr la encapsulación. Cuando se
cambia o reemplaza una capa, solo deberían verse afectadas aquellas capas que funcionan con ella.
Mediante la limitación de qué capas dependen de otras, se puede mitigar el impacto de los cambios
para que un único cambio no afecte a toda la aplicación.

Las capas (y la encapsulación) facilitan considerablemente el reemplazo de funcionalidad dentro de la


aplicación. Por ejemplo, es posible que una aplicación use inicialmente su propia base de datos de SQL
Server para la persistencia, pero más adelante podría optar por usar una estrategia de persistencia
basada en la nube, o situada detrás de una API web. Si la aplicación ha encapsulado correctamente su
implementación de persistencia dentro de una capa lógica, esa capa específica de SQL Server se podría
reemplazar por una nueva que implementara la misma interfaz pública.

Además de la posibilidad de intercambiar las implementaciones en respuesta a cambios futuros en los


requisitos, las capas de aplicación también facilitan el intercambio de implementaciones con fines de
prueba. En lugar de tener que escribir pruebas que utilicen la capa de datos reales o de la interfaz de
usuario de la aplicación, estas capas pueden ser remplazadas en tiempo de prueba con
implementaciones falsas que proporcionen respuestas conocidas a las solicitudes. Este enfoque
normalmente hace que las pruebas sean mucho más fáciles de escribir y mucho más rápidas de
ejecutar en comparación con la ejecución de pruebas sobre la infraestructura real de la aplicación.

El diseño en capas es una técnica común para mejorar la organización del código en las aplicaciones
de software empresarial, y hay varias formas de organizar el código en capas.

34 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Las capas (layers) representan una separación lógica dentro de la aplicación. En caso de que la lógica
de la aplicación se distribuya físicamente en servidores o procesos independientes, estos destinos de
implementación físicos independientes se conocen como niveles (tiers). Es posible y bastante habitual,
tener una aplicación de N-Capas (N-Layers) que se implemente en un solo nivel.

Aplicaciones de arquitectura tradicional N-Capas


La siguiente imagen muestra la organización más común de lógica de aplicación dividida en capas.

Estas capas se suelen abreviar frecuentemente como UI (User Interface), BLL (Business Logic Layer) y
DAL (Data Access Layer). Con esta arquitectura, los usuarios realizan solicitudes a través de la capa de
interfaz de usuario que interactúa con la capa BLL. BLL, a su vez, puede invocar a DAL para las
solicitudes de acceso de datos. La capa de interfaz de usuario no debe realizar solicitudes directamente
a DAL, ni debe interactuar con la persistencia de forma directa a través de otros medios. Del mismo
modo, BLL solo debe interactuar con la persistencia a través de DAL. De este modo, cada capa tiene su
propia responsabilidad bien conocida.

Una desventaja de este enfoque de distribución en capas tradicional es que las dependencias de
tiempo de compilación se ejecutan desde la parte superior a la inferior. Es decir, la capa de interfaz de
usuario depende de BLL, que a su vez depende de DAL. Esto significa que BLL, que normalmente
contiene la lógica más importante de la aplicación, depende de los detalles de implementación del
acceso a datos (y a menudo de la existencia de una base de datos). Probar la lógica de negocios en este
tipo de arquitectura suele ser difícil, y requiere una base de datos de prueba. El principio de inversión
de dependencias puede ser utilizado para resolver este problema, tal y como lo veremos más adelante.

35 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

La siguiente imagen muestra un ejemplo de una solución en la que se divide una aplicación monolítica
en tres proyectos por responsabilidad (o capas).

Aunque en esta aplicación se usan varios proyectos por motivos organizativos, se sigue
implementando como una sola unidad y sus clientes interactúan con ella como una sola aplicación
web. Esto permite que el proceso de implementación sea muy simple.

La siguiente imagen muestra la forma en que se podría hospedar en Azure este tipo de aplicaciones.

36 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Conforme las necesidades de la aplicación aumenten, podrían ser requeridas soluciones de


implementación más robustas y complejas.

La siguiente figura muestra un ejemplo de un plan de implementación más complejo que soporta
capacidades adicionales.

37 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Internamente, la organización de esta solución en varios proyectos en función de su responsabilidad


mejora la facilidad de mantenimiento de la aplicación.

Esta unidad puede ser escalada de forma vertical (Scale Up) o de forma horizontal (Scale Out) para
aprovechar la escalabilidad sobre demanda basada en la nube. El escalado vertical significa agregar
más CPU, memoria, espacio en disco u otros recursos al servidor en el que se hospeda la aplicación. El
escalado horizontal significa agregar instancias adicionales de estos servidores, con independencia de
que sean servidores físicos, máquinas virtuales o contenedores. Cuando la aplicación es hospedada en
varias instancias, un equilibrador de carga de trabajo es utilizado para asignar solicitudes a instancias
individuales de la aplicación.

Clean architecture (Arquitectura limpia)


Las aplicaciones que siguen el Principio de Inversión de Dependencias, así como los principios de Diseño
Controlado por Dominios (Domain-Driven Design - DDD), tienden a llegar a una arquitectura similar.

38 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Esta arquitectura ha pasado por muchos nombres con los años. Uno de los primeros nombres fue
Arquitectura hexagonal (Hexagonal Architecture), seguido por Puertos y adaptadores (Ports-and-
Adapters). Más recientemente, se ha citado como Arquitectura de Cebolla (Onion Architecture) o
Arquitectura Limpia (Clean Architecture).

La arquitectura limpia coloca el modelo de lógica de negocios y aplicación en el centro de la aplicación.


En lugar de tener lógica de negocios que dependa del acceso a datos o de otros aspectos de
infraestructura, esta dependencia es invertida: los detalles de la infraestructura y la implementación
dependen del Núcleo de la Aplicación (Application Core). Esta funcionalidad es lograda mediante la
definición de abstracciones o interfaces en el Núcleo de la Aplicación que después se implementan
mediante tipos definidos en la capa de Infraestructura. Una forma habitual de visualizar esta
arquitectura es usar una serie de círculos concéntricos, similares a una cebolla. La siguiente imagen
muestra un ejemplo de este estilo de representación de la arquitectura.

En la imagen anterior, las dependencias fluyen hacia el círculo más interno. El Núcleo de la Aplicación
toma su nombre de su posición en el núcleo de este diagrama. Podemos ver en la imagen que el Núcleo

39 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

de la Aplicación no tiene dependencias de otras capas de la aplicación. Las entidades e interfaces de la


aplicación se encuentran justo en el centro. En el exterior, pero todavía en el Núcleo de la Aplicación,
están los Servicios de Dominio, que normalmente implementan interfaces definidas en el círculo
interior. Fuera del Núcleo de la Aplicación, las capas de la Interfaz de Usuario y la Infraestructura
dependen del Núcleo de la Aplicación, pero no una de la otra (necesariamente).

La siguiente imagen muestra un diagrama de capas horizontal más tradicional que refleja mejor la
dependencia entre la Interfaz de usuario y otras capas.

En el diagrama anterior, las flechas sólidas representan las dependencias de tiempo de compilación,
mientras que la flecha discontinua representa una dependencia solo de tiempo de ejecución (y
opcionalmente en tiempo de compilación). Con la Arquitectura Limpia, la capa de Interfaz de Usuario
trabaja con las interfaces definidas en el Núcleo de la Aplicación en tiempo de compilación y lo ideal
es que no conozca los tipos de implementación definidos en la capa de Infraestructura. Pero en tiempo
de ejecución, estos tipos de implementación son necesarios para que la aplicación se ejecute, por lo
que deben estar presentes y conectados a las interfaces del Núcleo de la Aplicación a través de la
Inyección de Dependencias.

La siguiente imagen muestra una vista más detallada de la arquitectura de una aplicación ASP.NET Core
cuando se construye siguiendo estas recomendaciones.

40 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

El diagrama anterior muestra una solución con 3 proyectos, el proyecto con la plantilla ASP.NET Core
Web App (Model-View-Controller), el proyecto biblioteca de clases Application Core y el proyecto
biblioteca de clases Infrastructure. Las flechas sólidas representan las dependencias de tiempo de
compilación, mientras que las flechas discontinuas representan dependencias solo de tiempo de
ejecución (y opcionalmente en tiempo de compilación).

El proyecto ASP.NET Core del diagrama desplegado en una Web App de Azure App Service, contiene
los elementos que componen la capa de la Interfaz de Usuario tales como Controladores, Vistas,
ViewModels, Filtros de acción (respuesta cache, validación, etc.) y el framework de autenticación
ASP.NET Core Identity.

41 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

El proyecto Application Core del diagrama, contiene el modelo de negocio que incluye:

• Definición de interfaces.
• Entidades que representan elementos únicos (por ejemplo, un Producto de la tabla Products).
• Servicios para implementar la lógica de negocios.
• Eventos para comunicar los sucesos que suceden en el dominio (el dominio representa el
problema que se quiere resolver). Por ejemplo, cuando se crea un nuevo Pedido se puede
notificar este suceso para que algún componente pueda realizar una acción en respuesta.
• Excepciones originadas en la aplicación.
• Objetos de Valor (Value Object) que, a diferencia de las entidades, los Objectos de Valor no
tienen una identificación específica, solo describen características como un Color o una
Dirección de envío.
• Objetos Agregados que representan un grupo de objetos que deben ser manejados juntos,
por ejemplo, un Pedido con sus Productos solicitados.
• Especificaciones de lo que se puede hacer con una entidad dejando a otros componentes la
realización de esta.

El proyecto Infrastructure del diagrama, contiene implementaciones para manejo de cache, el contexto
de datos de Entity Framework Core, manejo de cache de Redis, manejo de mensajería de Azure Service
Bus, manejos de servicio SMS, manejo de servicio de correo y otras implementaciones de clientes Web
API.

El proyecto Infrastructure consume servicios como el de servidores de bases de datos o de APIS de


terceros.

42 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

En el diagrama, podemos ver que, en tiempo de compilación, el proyecto ASP.NET Core hace una
referencia al proyecto Application Core, aunque también podría hacer referencia al proyecto
Infrastructure. El proyecto Infrastructure también hace referencia al proyecto Application Core. En
tiempo de ejecución el ensamblado del proyecto ASP.NET Core requiere la presencia de los
ensamblados de los proyectos Application Core e Infrastructure para que la aplicación pueda ser
ejecutada.

Debido a que el Núcleo de la Application no depende de la Infraestructura, es muy fácil escribir pruebas
unitarias automatizadas para esta capa.

43 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Como la capa de Interfaz de Usuario no tiene ninguna dependencia directa de los tipos definidos en el
proyecto de Infraestructura, también es muy fácil intercambiar las implementaciones, ya sea para
facilitar las pruebas o en respuesta a los requisitos cambiantes de la aplicación. El uso y el soporte
integrados de ASP.NET Core con la Inyección de Dependencias hace que esta arquitectura sea la forma
más adecuada de estructurar aplicaciones monolíticas no triviales.

Para las aplicaciones monolíticas, los proyectos del Núcleo de la aplicación, Infraestructura e Interfaz
de Usuario se ejecutan como una sola aplicación. La arquitectura de la aplicación en tiempo de
ejecución podría ser similar a la siguiente.

La imagen no muestra los servicios de bases de datos u otros servicios externos.

La aplicación eShopOnWeb presentada en el e-book “Architecting Modern Web


Applications with ASP.NET Core and Azure” utiliza el enfoque de Arquitectura Limpia para
organizar su código en proyectos. Podemos encontrar una plantilla de solución que
podemos utilizar como punto de partida para nuestros desarrollos con ASP.NET Core en el
repositorio de GitHub https://github.com/ardalis/cleanarchitecture.

44 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Durante este entrenamiento, construiremos una solución con una estructura de proyectos más
simple con enfoque de Arquitectura Limpia.

Organización del código en la Arquitectura Limpia


En una solución de Arquitectura Limpia, cada proyecto tiene responsabilidades claras. Por lo tanto,
frecuentemente encontraremos tipos organizados en carpetas dentro del proyecto adecuado.

Núcleo de aplicación (Application Core)

El Núcleo de la Aplicación contiene el modelo de negocio, que incluye Entidades, Servicios e Interfaces.
Estas Interfaces incluyen abstracciones para las operaciones que se llevarán a cabo mediante la
Infraestructura, como el acceso a datos, el acceso al sistema de archivos, las llamadas de red, etc. En
ocasiones los servicios o interfaces definidas en este nivel tendrán que trabajar con tipos sin entidad
que no tienen dependencias en la Interfaz de Usuario o la Infraestructura. Estos se pueden definir
como Objetos de Transferencia de Datos (DTO) simples.

Tipos de Núcleo de la aplicación

• Entidades (Entities). Clases de modelo de negocio que son persistidas.


• Interfaces.
• Servicios.
• DTOs

Infraestructura (Infrastructure)

El proyecto de infraestructura incluye normalmente las implementaciones de acceso a datos. En una


aplicación web ASP.NET Core típica, estas implementaciones incluyen el DbContext de Entity
Framework Core (EF Core), todos los objetos Migration de EF Core que se hayan definido y las clases
de implementación de acceso a datos. La manera más común de abstraer el código de implementación
de acceso a datos consiste en usar el patrón de diseño Repository.

Además de las implementaciones de acceso a datos, el proyecto de Infraestructura debe contener las
implementaciones de los servicios que tienen que interactuar con los intereses de infraestructura.
Estos servicios deben implementar interfaces definidas en el Núcleo de la Aplicación, por lo que el
proyecto de infraestructura deberá tener una referencia del proyecto del Núcleo de la Aplicación.

Tipos de la capa Infraestructura

45 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

• Tipos de EF Core (DbContext, Migration)


• Tipos de implementación de acceso a datos (Repositorios)
• Servicios específicos de la infraestructura (por ejemplo, FileLogger o SendGridClient)

Capa de interfaz de usuario (UI Layer)

La capa de Interfaz de Usuario en una aplicación ASP.NET Core MVC es el punto de entrada para la
aplicación. Este proyecto debe hacer referencia al proyecto Application Core y sus tipos deben
interactuar con la infraestructura estrictamente a través de las interfaces definidas en Application
Core. En la capa de Interfaz de Usuario no se debe permitir la creación de instancias directas o llamadas
estáticas a los tipos de la capa de Infraestructura.

Tipos de la capa de interfaz de usuario

• Controladores
• Filtros
• Vistas
• ViewModels
• Inicio (Startup)

La clase Startup es responsable de configurar la aplicación y de conectar los tipos de implementación


a las interfaces, lo que permite que la inyección de dependencias funcione correctamente en tiempo
de ejecución.

Para conectar la inyección de dependencias en el método ConfigureServices del archivo Startup.cs del
proyecto de Interfaz de Usuario, el proyecto tiene que hacer referencia al proyecto de Infraestructura.
Esta dependencia se puede eliminar con facilidad mediante un contenedor de inyección de
dependencias personalizado. El enfoque más sencillo consiste en permitir que el proyecto de Interfaz
de Usuario haga referencia al proyecto de Infraestructura.

Para obtener más información sobre los temas relacionados con Clean Architecture, se
recomienda visitar los siguientes enlaces:

The Clean Architecture


https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html

The Onion Architecture


https://jeffreypalermo.com/2008/07/the-onion-architecture-part-1/

46 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Clean Architecture Solution Template


https://github.com/ardalis/cleanarchitecture

DDD (Domain-Driven Design)


https://martinfowler.com/bliki/DomainDrivenDesign.html

47 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Lección 2:

El patrón Mediator
Clean Architecture es un conjunto de patrones, prácticas y principios para crear una arquitectura de
software que sea simple, comprensible, flexible, comprobable y mantenible.
En esta lección, empezaremos por conocer uno de los patrones utilizados comúnmente para
implementar una Arquitectura Limpia: el patrón Mediator.

Objetivos

Al finalizar esta lección, los participantes contarán con las habilidades y conocimientos para:

• Describir el propósito del patrón Mediator.


• Implementar el patrón Mediator.
• Utilizar la biblioteca MediatR para resolver escenarios de uso de comandos, consultas y
eventos.
• Utilizar comportamientos con MediatR para ejecutar lógica de validación.

48 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Introducción
El patrón Mediator es un patrón de diseño de comportamiento que ayuda a reducir las dependencias
entre objetos. El patrón Mediator se remonta a 1994 en el famoso libro "Design Patterns: Elements of
Reusable Object-Oriented Software" y propone la definición de un objeto que encapsule la forma en
que interactúa un conjunto de objetos. Mediator promueve acoplamiento flexible al evitar que los
objetos se refieran explícitamente entre sí, y nos permite variar su interacción de forma
independiente.

El objetivo principal es no permitir la comunicación directa entre los objetos y, en cambio, obligarlos a
comunicarse solo a través del mediador. Implementar el patrón Mediator nos ayuda a diseñar objetos
que son más fáciles de implementar, remplazar, probar y reutilizar.

Podemos imaginarnos a un objeto Mediator como un concentrador de mensajes. Cuando enviamos un


mensaje a través de un concentrador de mensajes no sabemos quién estará recibiendo ese mensaje,
solo sabemos que el concentrador lo sabe y que él hará llegar el mensaje por nosotros.

Esta es probablemente una versión simplificada porque un patrón Mediator permite la comunicación
bidireccional, no es solo una transmisión unidireccional.

49 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Implementando el patrón Mediator para enviar mensajes


Supongamos que tenemos un escenario donde necesitamos enviar notificaciones a distintos
componentes de una aplicación para que estos puedan realizar una determinada acción cada vez que
los cambios de un usuario sean almacenados en una base de datos.

Los actores de este escenario son el servicio de persistencia y los manejadores de las notificaciones.

Para dar solución a este escenario, crearemos una aplicación ASP.NET Core Web API y empezaremos
con una solución simple que no implemente el patrón Mediator. Posteriormente, plantearemos una
solución implementando el patrón Mediator.

Crear la solución en Visual Studio

1. Abre Visual Studio bajo el contexto del administrador.

2. Crea una nueva solución llamada MediatorDemo utilizando la plantilla Blank Solution.

3. Agrega a la solución un nuevo proyecto llamado Notifications utilizando la plantilla ASP.NET


Core Web API y con soporte a OpenAPI.

Veamos primeramente una solución al escenario planteado sin utilizar un Mediador para
posteriormente ver la forma en que el Mediador entra en nuestra ayuda.

4. Agrega un archivo de clase llamado NotificationHandlerBase.cs con el siguiente código.

public abstract class NotificationHandlerBase


{
public virtual void Handle(string message)
{
Debug.WriteLine($"{this.GetType()} => {message}");
}
}

50 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

El código requerirá importar el espacio de nombres System.Diagnostics.

La clase NotificationHandlerBase contiene el código base que los manejadores de


notificaciones de nuestro ejemplo tendrán, en este caso, la escritura de un mensaje que
incluye el tipo del manejador junto con el mensaje recibido.

5. Agrega un archivo de clase llamado NotificationHandler1.cs con el siguiente código.

public class NotificationHandler1 : NotificationHandlerBase


{
}

NotificationHandler1 será nuestro primer manejador de notificaciones.

6. Agrega un archivo de clase llamado NotificationHandler2.cs para definir el segundo manejador


de notificaciones con el siguiente código.

public class NotificationHandler2 : NotificationHandlerBase


{
}

7. Agrega un archivo de clase llamado NotificationHandler3.cs para definir un tercer manejador


de notificaciones con el siguiente código.

public class NotificationHandler3 : NotificationHandlerBase


{
}

8. Agrega un nuevo archivo de clase llamado PersistenceService.cs con el siguiente código.

public class PersistenceService


{
// Declarar las variables para almacenar las
// instancias de los manejadores de notificaciones.
readonly NotificationHandler1 Handler1;
readonly NotificationHandler2 Handler2;
readonly NotificationHandler3 Handler3;

// Obtener las instancias de los manejadores a través del constructor


public PersistenceService(NotificationHandler1 handler1,
NotificationHandler2 handler2, NotificationHandler3 handler3) =>
(Handler1, Handler2, Handler3) = (handler1, handler2, handler3);

// Método para guardar los cambios del usuario.


public void SaveChanges()
{
// Lógica para guardar cambios
// ...

// Notificar a los manejadores


Handler1.Handle("SaveChanges");

51 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Handler2.Handle("SaveChanges");
Handler3.Handle("SaveChanges");
}
}

El papel de PersistenceService es realizar algo de lógica y después notificar a los manejadores


de notificaciones de nuestra aplicación. En el código anterior, PersistenceService hace
referencia a cada uno de los manejadores que recibirán la notificación.

9. Agrega un nuevo archivo de clase llamado NotificationsController.cs en el directorio


Controllers utilizando la plantilla API Controller – Empty con el siguiente código.

[Route("api/[controller]")]
[ApiController]
public class NotificationsController : ControllerBase
{
[HttpGet]
public IActionResult SaveChanges()
{
PersistenceService Service = new PersistenceService(
new NotificationHandler1(),
new NotificationHandler2(), new NotificationHandler3());

Service.SaveChanges();

return Ok("Saved!!!");
}
}

10. Ejecuta la aplicación y prueba la funcionalidad desde la página index.html.

52 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

En la ventana Output de Visual Studio podrás ver un resultado similar al siguiente.

Puedes notar que hemos dado una solución al escenario planteado. Cada uno de los
manejadores de notificaciones fue notificado como era esperado. Sin embargo, ¿qué pasará
cuando agreguemos más manejadores? ¿o cuándo eliminemos manejadores? Nuestra clase
PersistenceService tendrá que ser modificada.

Modifiquemos el código para implementar el patrón Mediator.

Implementar el patrón Mediator

Implementemos el patrón Mediator de una forma simple para posteriormente hacerlo más funcional.
Si lo deseas, antes de iniciar con los siguientes pasos puedes realizar una copia de respaldo del
directorio de la solución, llámala por ejemplo MediatorDemoV1.

1. Agrega un nuevo archivo de clase llamado Mediator.cs con el siguiente código.

public class Mediator


{
// Declarar las variables para almacenar las
// instancias de los manejadores de notificaciones.
readonly NotificationHandler1 Handler1;
readonly NotificationHandler2 Handler2;
readonly NotificationHandler3 Handler3;

// Obtener las instancias de los manejadores a través del constructor


public Mediator(NotificationHandler1 handler1,
NotificationHandler2 handler2, NotificationHandler3 handler3) =>
(Handler1, Handler2, Handler3) = (handler1, handler2, handler3);

// Publicar la notificación
public void Publish(string message)
{
Handler1.Handle(message);
Handler2.Handle(message);
Handler3.Handle(message);
}
}

Puedes notar que hemos colocado el código relacionado con la notificación a la clase Mediator.

53 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

2. Modifica el código de la clase PersistenceService para que sea similar al siguiente.

public class PersistenceService


{
// Declarar la variable para almacenar la instancia de Mediator
readonly Mediator Mediator;

// Obtener la instancia de Mediator


public PersistenceService(Mediator mediator) =>
Mediator = mediator;

// Método para guardar los cambios del usuario.


public void SaveChanges()
{
// Lógica para guardar cambios
// ...

// Notificar a los manejadores


Mediator.Publish("SaveChanges");
}
}

Puedes notar que la clase PersistenceService ahora requiere de una instancia de Mediator para
realizar las notificaciones a través de él.

3. Modifica el código del método SaveChanges de la clase NotificationsController para que sea
similar al siguiente.

public IActionResult SaveChanges()


{
PersistenceService Service = new PersistenceService(
new Mediator(
new NotificationHandler1(),
new NotificationHandler2(), new NotificationHandler3()));

Service.SaveChanges();

return Ok("Saved!!!");
}

Puedes notar que ahora le estamos proporcionando a PersistenceService la instancia del


Mediator que a su vez incluye las instancias de los manejadores de notificaciones.

4. Ejecuta la aplicación y prueba la funcionalidad desde la página index.html. Podrás notar que
obtenemos el mismo resultado, pero ahora utilizando ya un mediador.

54 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Como mencionamos previamente, esta es una primera implementación simple del patrón
Mediator que podemos mejorar.

Ahora cuando agreguemos o eliminemos manejadores de notificaciones, el servicio


PersistenceService no tendrá que ser modificado, aunque también podría ser un poco
decepcionante que tal vez estemos pasando la carga al Mediador.

El trabajo del Mediador será administrar a los controladores y la forma en que son notificados.
¡Pero esto tiene sentido! Tener una clase cuyo único trabajo sea notificar a los clientes debe
ser capaz de cambiar dependiendo de cómo esos clientes necesiten ser notificados. Y nuestro
servicio, que realmente no se preocupa por los detalles de implementación de esos
manejadores de notificación, puede continuar con su trabajo.

Veamos cómo nos puede ayudar el servicio de inyección de dependencias para mejorar la
carga de trabajo del Mediador.

Utilizar Inyección de Dependencias

Refactoricemos ahora la implementación del patrón Mediator para aprovechar las bondades del
servicio de Inyección de Dependencias. Si lo deseas, antes de iniciar con los siguientes pasos puedes
realizar una copia de respaldo del directorio de la solución, llámala por ejemplo MediatorDemoV2.

1. Agrega un nuevo archivo de clase llamado INotificationHandler.cs con el siguiente código.

public interface INotificationHandler


{
void Handle(string message);
}

La interface INotificationHandler define el único método que requieren implementar los


manejadores de notificaciones de nuestra aplicación.

2. Modifica el código de la clase NotificationHandlerBase para indicar que implementa la


interface INotificationHandler.

public abstract class NotificationHandlerBase : INotificationHandler

55 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

{
public virtual void Handle(string message)
{
Debug.WriteLine($"{this.GetType()} => {message}");
}
}

Ahora ya podemos registrar instancias de manejadores de notificación en el contenedor de


inyección de dependencias de nuestra aplicación.

3. Agrega el siguiente código al final del método ConfigureServices de la clase Startup para
registrar los manejadores de notificación.

services.AddTransient<INotificationHandler, NotificationHandler1>();
services.AddTransient<INotificationHandler, NotificationHandler2>();
services.AddTransient<INotificationHandler, NotificationHandler3>();

El servicio de inyección de dependencias de ASP.NET Core inyecta automáticamente un


IEnumerable<T> si registramos múltiples tipos a la misma interface.

Podemos también ahora crear un servicio para el Mediador.

4. Agrega un nuevo archivo de clase llamado IMediator.cs con el siguiente código.

public interface IMediator


{
void Publish(string message);
}

La Interface IMediator define un único método que permitirá notificar a los manejadores de
notificación.

5. Modifica el código de la clase Mediator para que sea similar al siguiente.

public class Mediator : IMediator


{
// Declarar la variable para almacenar las
// instancias de los manejadores de notificaciones.
readonly IEnumerable<INotificationHandler> Handlers;

// Obtener las instancias de los manejadores a través del constructor


public Mediator(IEnumerable<INotificationHandler> handlers) =>
Handlers = handlers;

// Publicar la notificación
public void Publish(string message) =>
Handlers.ToList().ForEach(h => h.Handle(message));
}

56 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Puedes notar que ahora el código es más simple gracias a que únicamente declaramos un
IEnumerable para recibir los manejadores de notificación y no nos referimos explícitamente a
manejadores específicos. ¡Estamos logrando un acoplamiento flexible!

Ahora podemos agregar o eliminar manejadores de notificación sin modificar el Mediador.

6. Agrega el siguiente código al final del método ConfigureServices de la clase Startup para
registrar el servicio IMediator.

services.AddTransient<IMediator, Mediator>();

7. Modifica el código de la clase PersistenceService para recibir una instancia del servicio
IMediator.

public class PersistenceService


{
// Declarar la variable para almacenar la instancia de Mediator
readonly IMediator Mediator;

// Obtener la instancia de Mediator


public PersistenceService(IMediator mediator) =>
Mediator = mediator;

// Método para guardar los cambios del usuario.


public void SaveChanges()
{
// Lógica para guardar cambios
// ...

// Notificar a los manejadores


Mediator.Publish("SaveChanges");
}
}

8. Finalmente, modifica el código del controlador NotificationsController para recibir mediante


inyección de dependencias una instancia del Mediator.

public class NotificationsController : ControllerBase


{
readonly IMediator Mediator;
public NotificationsController(IMediator mediator) =>
Mediator = mediator;

[HttpGet]
public IActionResult SaveChanges()
{
PersistenceService Service = new PersistenceService(
Mediator);

Service.SaveChanges();

57 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

return Ok("Saved!!!");
}
}

9. Ejecuta la aplicación y prueba la funcionalidad desde la página index.html. Podrás notar que
obtenemos el mismo resultado, pero ahora con un código más limpio.

58 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Implementando el patrón Mediator para realizar operaciones


Veamos ahora una nueva implementación del patrón Mediator que nos permita realizar operaciones
para escenarios de comandos y consultas.

Si lo deseas, antes de iniciar con los siguientes pasos puedes realizar una copia de respaldo del
directorio de la solución, llámala por ejemplo MediatorDemoV3.

Crear una biblioteca de clases


1. Agrega a la solución un nuevo proyecto llamado Mediator utilizando la plantilla Class Library.

2. Elimina el archivo Class1.cs creado con la plantilla.

Agregar los tipos de peticiones


1. Agrega un nuevo archivo de clase llamado IRequest.cs con el siguiente código.

// Representa una petición que no devuelve resultado.


public interface IRequest{}

// Representa una petición que devuelve un resultado de tipo ResponseType


public interface IRequest<ResponseType>{}

El código anterior define 2 Interfaces, la Interface no genérica IRequest representa una


petición para la realización de una operación (ejecución de un método) que no devuelve un
resultado mientras que la Interface genérica IRequest<ResponseType> representa una
petición para la realización de una operación que devuelve un resultado.

Agregar los tipos de manejadores de peticiones


1. Agrega un nuevo archivo de clase llamado IRequestHandler.cs con el siguiente código.

// Atiende una petición que no espera resultado


public interface IRequestHandler<RequestType>
where RequestType : IRequest
{
Task Handle(RequestType request,
CancellationToken cancellationToken);
}

59 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

// Atiende una petición que espera un resultado.


public interface IRequestHandler<RequestType, ResponseType>
where RequestType : IRequest<ResponseType>
{
Task<ResponseType> Handle(RequestType request,
CancellationToken cancellationToken);
}

El código requerirá importar el espacio de nombres System.Threading.

El código anterior define 2 Interfaces, la Interface IRequestHandler<RequestType> representa


un manejador que se encargará de realizar la operación de una petición IRequest mientras que
la Interface IRequestHandler<RequestType, ResponseType> representa un manejador que se
encargará de realizar la operación de una petición IRequest<ResponseType>.

Ambas Interfaces exponen el método Handle, encargado de realizar la operación solicitada. En


ambas interfaces, el valor devuelto por el método Handle es de tipo Task para permitir que las
operaciones puedan ser implementadas de forma síncrona o asíncrona.

Agregar la Interface del Mediador


1. Agrega un nuevo archivo de clase llamado IMediator.cs con el siguiente código.

public interface IMediator


{
// Enviar una petición que no espera resultado.
Task Send(IRequest request, CancellationToken cancellationToken = default);

// Enviar una petición que espera resultado.


Task<ResponseType> Send<ResponseType>(IRequest<ResponseType> request,
CancellationToken cancellationToken = default);
}

El código requerirá importar el espacio de nombres System.Threading.

El código anterior define la funcionalidad del Mediator que implementaremos para poder
enviar peticiones a los manejadores. Podemos notar que tenemos 2 métodos, uno para
realizar peticiones que no esperan un resultado y otro para realizar peticiones que esperan un
resultado.

Implementar la Interface del Mediador


1. Agrega un nuevo archivo de clase llamado Mediator.cs con el siguiente código.

public class Mediator : IMediator


{
Assembly HandlersAssembly;

60 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

public Mediator(Assembly handlersAssembly) =>


HandlersAssembly = handlersAssembly;
}

El código requerirá importar el espacio de nombres System.Reflection.

El Mediator que implementaremos utilizará Reflection para encontrar el tipo de manejador


que podrá atender un determinado tipo de petición. El constructor de la clase recibirá el
Assembly donde serán buscados los tipos de los manejadores.

2. Agrega el siguiente código a la clase Mediator para definir un método cuya responsabilidad
será buscar a través de Reflection el tipo de manejador que se encargará de atender una
petición.

ReturnType Handle<ReturnType, RequestType>(


RequestType request, CancellationToken cancellationToken)
{
}

El código requerirá importar el espacio de nombres System.Threading.

El método Handle devolverá un valor de tipo ReturnType a partir de una petición de tipo
RequestType. ReturnType podrá ser Task o Task<T> mientras que RequestType podrá ser
IRequest o IRequest<T>.

El método Handle recibirá la petición a través del parámetro request además del token de
cancelación de la tarea.

3. Agrega el siguiente código dentro del cuerpo del método Handle para definir 2 variables:

• ReturnType que almacenará el valor que devolverá el método.


• RequestHandlerType que almacenará el tipo del manejador que procesará la petición.

ReturnType Result = default;


Type RequestHandlerType;

4. Agrega el siguiente código que establecerá el valor de la variable RequestHandlerType cuando


el tipo de la respuesta que debe devolver el método sea un tipo genérico (Task<T>).

if (typeof(ReturnType).IsGenericType)
{
RequestHandlerType = typeof(IRequestHandler<,>);
}

Cuando el valor de retorno sea un tipo Task<T>, el manejador que deberá atender la petición
deberá ser de un tipo IRequestHandler<RequestType, ResponseType>.

61 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

5. Agrega el siguiente código después del código anterior para manejar el caso en que el
resultado esperado sea un tipo no genérico Task.

else
{
RequestHandlerType = typeof(IRequestHandler<>);
}

Cuando el valor de retorno sea un tipo Task, el manejador que deberá atender la petición
deberá ser de un tipo IRequestHandler<RequestType>.

6. Agrega el siguiente código para obtener mediante Reflection el tipo adecuado que manejará
la petición.

Type Handler = HandlersAssembly.GetTypes()


.FirstOrDefault(t => t.GetInterfaces()
.Any(i =>
i.IsGenericType &&
i.GetGenericTypeDefinition() == RequestHandlerType &&
i.GetGenericArguments()[0] == request.GetType()));

El tipo por buscar dentro de los tipos del Assembly:

• Debe implementar una interface genérica.


• La definición de la interface genérica debe ser igual al tipo de manejador requerido
RequestHandlerType.
• El primer argumento genérico debe ser del mismo tipo de la petición (request).

7. Agrega el siguiente código para que cuando el manejador sea encontrado, una instancia de
este sea creado y el método Handle sea ejecutado mediante Reflection. En caso contrario,
deberá lanzarse una excepción.

if (Handler != null)
{
var HandlerInstance = Activator.CreateInstance(Handler);
Result = (ReturnType)Handler.GetMethod("Handle").Invoke(
HandlerInstance, new object[] { request, cancellationToken });
}
else
{
throw new InvalidOperationException(string.Format(
"Error constructing handler for request of type {0}",
request.GetType()));
}
return Result;

8. Agrega el siguiente código a la clase Mediator para implementar el método Send que envía a
un manejador una petición que no devuelve un resultado.

62 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

public Task Send(IRequest request,


CancellationToken cancellationToken = default)
{
return Handle<Task, IRequest>(request, cancellationToken);
}

El método simplemente devuelve el resultado de la invocación al método Handle que hemos


creado.

9. Agrega el siguiente código a la clase Mediator para implementar el método Send que envía a
un manejador una petición que devuelve un resultado.

public Task<ResponseType> Send<ResponseType>(


IRequest<ResponseType> request,
CancellationToken cancellationToken = default)
{
return Handle<Task<ResponseType>, IRequest<ResponseType>>(
request, cancellationToken);
}

Agregar la clase para facilitar la inyección de dependencias

Agreguemos ahora una clase que facilite al consumidor del Mediator el registro del servicio en el
contenedor de dependencias.

1. Agrega el paquete NuGet Microsoft.Extensions.DependencyInjection.Abstractions al


proyecto Mediator.

2. Agrega ahora un archivo de clase llamado DependecyInjection.cs con el siguiente código.

using Microsoft.Extensions.DependencyInjection;
using System.Reflection;

namespace Mediator
{
public static class DependecyInjection
{
public static IServiceCollection AddMediator(
this IServiceCollection services,
Assembly handlersAssembly)
{
services.AddSingleton<IMediator>(
provider => new Mediator(handlersAssembly));
return services;
}
}
}

63 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Probar la funcionalidad del Mediator


Probemos ahora la funcionalidad del Mediador que hemos implementado.

Agregar la biblioteca de clases de peticiones y sus manejadores


1. Agrega a la solución un nuevo proyecto llamado CommandsAndHandlers utilizando la plantilla
Class Library.

2. Elimina el archivo Class1.cs creado por la plantilla.

3. Agrega al nuevo proyecto creado la referencia del proyecto Mediator.

Agregar los tipos de peticiones


1. Agrega un nuevo archivo de clase llamado CreateProduct.cs con el siguiente código.

public class CreateProduct: IRequest<int>


{
public string Name { get; set; }
}

El código requerirá importar el espacio de nombres Mediator.

La clase CreateProduct simula una petición que devuelve un valor. La clase representa una
petición para crear un producto cuyo nombre se encuentra en la propiedad Name. La petición
requiere la devolución del Id del nuevo producto creado.

2. Agrega un nuevo archivo de clase llamado DeleteProduct.cs con el siguiente código.

public class DeleteProduct: IRequest


{
public int Id { get; set; }
}

El código requerirá importar el espacio de nombres Mediator.

La clase DeleteProduct simula una petición que no devuelve un valor. La clase representa una
petición para eliminar un producto cuyo identificador se encuentra en la propiedad Id.

Agregar los tipos de manejadores de peticiones


1. Agrega un nuevo archivo de clase llamado CreateProductHandler.cs con el siguiente código.

public class CreateProductHandler :


IRequestHandler<CreateProduct, int>

64 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

{
public Task<int> Handle(CreateProduct request,
CancellationToken cancellationToken)
{
// Lógica para crear un nuevo producto...
Debug.WriteLine($"Crear el producto: {request.Name}");

// Lógica para devolver el Id...


return Task.FromResult(new Random().Next(1, 1000));
}
}

El código requerirá importar los espacios de nombres Mediator, System.Diagnostics y


System.Threading.

El código simplemente simula la creación de un producto y devuelve un número entero


aleatorio entre 1 y 1000 para simular el Id del nuevo producto.

2. Agrega un nuevo archivo de clase llamado DeleteProductHandler.cs con el siguiente código.

public class DeleteProductHandler :


IRequestHandler<DeleteProduct>
{
public Task Handle(DeleteProduct request,
CancellationToken cancellationToken)
{
// Lógica para eliminar el producto...
Debug.WriteLine($"Eliminar el producto {request.Id}");

return Task.CompletedTask;
}
}

El código requerirá importar los espacios de nombres Mediator, System.Diagnostics y


System.Threading.

El código simplemente simula la eliminación de un producto y devuelve un objeto que


representa una tarea finalizada.

Agregar un nuevo proyecto Web API


Agreguemos ahora un nuevo proyecto Web API para probar el uso del Mediador.

1. Agrega a la solución un nuevo proyecto llamado MediatorClient utilizando la plantilla ASP.NET


Core Web API y con soporte a OpenAPI.

2. Agrega al nuevo proyecto creado la referencia de los proyectos CommandsAndHandlers y


Mediator.

65 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

3. En la carpeta Controllers, agrega un nuevo archivo llamado CommandsController.cs utilizando


la plantilla API Controller – Empty con el siguiente código.

[Route("api/[controller]")]
[ApiController]
public class CommandsController : ControllerBase
{
readonly IMediator Mediator;
public CommandsController(IMediator mediator) =>
Mediator = mediator;
}

El código requerirá importar los espacios de nombres Mediator y CommandsAndHandlers.

Puedes notar que estamos inyectando el servicio IMediator a través del constructor de la clase.

4. Agrega el siguiente código que ejemplifica el uso de una petición que requiere de un valor
devuelto.

[HttpPost]
public async Task<IActionResult> CreateProduct(string name)
{
int Id = await Mediator.Send(
new CreateProduct { Name = name });
return Ok(Id);
}

5. Agrega el siguiente código que ejemplifica el uso de una petición que no requiere la devolución
de un valor.

[HttpDelete]
public async Task<IActionResult> DeleteProduct(int id)
{
await Mediator.Send(
new DeleteProduct { Id = id });
return Ok($"Producto {id} eliminado!!!");
}

Registrar el servicio IMediator


1. Abre el archivo Startup.cs.

2. Agrega el siguiente código al final del método ConfigureServices para registrar el servicio
IMediator en el contenedor de dependencias.

services.AddMediator(Assembly.Load("CommandsAndHandlers"));

El código requerirá importar los espacios de nombres Mediator y System.Reflection.

66 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Puedes notar que estamos especificando el nombre del ensamblado que contiene los tipos de
los manejadores de peticiones.

Probar la funcionalidad
1. Establece el proyecto MediatorClient como proyecto de inicio y ejecuta la aplicación.

2. En la página Index.html prueba la funcionalidad POST /api/commands.

La venta Output de Visual Studio mostrará un mensaje similar al siguiente.

3. La página Index.html mostrará el id del producto devuelto.

67 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

4. Prueba la funcionalidad DELETE /api/commands.

La venta Output de Visual Studio mostrará un mensaje similar al siguiente.

La página Index.html mostrará el mensaje devuelto por la API.

68 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

69 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Introducción a MediatR
Como su autor lo describe, MediatR es una biblioteca poco ambiciosa que intenta resolver un
problema simple: desacoplar el proceso de envío de mensajes del proceso de manejo de mensajes.
MediatR es una biblioteca multiplataforma, compatible con .NET Framework 4.6.1 y .NET Standard 2.0.

MediatR implementa el patrón Mediator y es utilizada frecuentemente en aplicaciones ASP.NET Core


para desacoplar componentes. Veamos cómo utilizar MediatR.

Si lo deseas, antes de iniciar con los siguientes pasos puedes realizar una copia de respaldo del
directorio de la solución, llámala por ejemplo MediatorDemoV4.

1. Agrega a la solución un nuevo proyecto llamado MediatRDemo utilizando la plantilla ASP.NET


Core Web API y con soporte a OpenAPI.

2. Agrega al proyecto el paquete NuGet MediatR.Extensions.Microsoft.DependencyInjection.


Este paquete incluye la biblioteca MediatR además del método de extensión
IServiceCollection.AddMediatR con diferentes sobrecargas que nos permiten registrar los
manejadores de peticiones ubicados en uno o más ensamblados especificados.

3. Abre el archivo Startup.cs.

4. Agrega el siguiente código al final del método ConfigureServices para registrar los
manejadores de peticiones ubicados en el ensamblado actual.

services.AddMediatR(Assembly.GetExecutingAssembly());

El código requerirá importar los espacios de nombres MediatR y System.Reflection.

Mensajes enviados por MediatR


MediatR tiene 2 clases de mensajes que puede enviar:

• Mensajes de Petición/Respuesta (Request/Response), enviados a un solo manejador.


• Mensajes de Notificación (Notification) enviados a múltiples manejadores.

70 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Mensajes Petición/Respuesta
Los mensajes de Petición/Respuesta sirven para manejar escenarios tanto de comandos como de
consultas. MediatR maneja 2 tipos de peticiones, una que devuelve un valor y otra que no:

• IRequest<T>. La petición devuelve un valor.


• IRequest. La petición no devuelve un valor.

Cada tipo de petición tiene su propia Interface de manejador de petición, así como clases base de
ayuda:

• IRequestHandler<T, U>. Maneja la petición de tipo T y devuelve Task<U>.


• RequestHandler<T, U>. Maneja peticiones de tipo T de forma síncrona y devuelve U.

Para peticiones sin valor de retorno:

• IRequestHandler<T>. Maneja la petición de tipo T.


• AsyncRequestHandler<T>. Maneja peticiones de tipo T de forma asíncrona.
• RequestHandler<T>. Maneja peticiones de tipo T de forma síncrona.

Veamos un ejemplo.

1. Agrega un archivo de clase llamado Ping.cs con el siguiente código para crear un mensaje.

public class Ping : IRequest<string> { }

El código requerirá importar el espacio de nombres MediatR.

2. Agrega un archivo de clase llamado PingHandler.cs con el siguiente código para crear el
manejador del mensaje.

public class PingHandler : IRequestHandler<Ping, string>


{
public Task<string> Handle(Ping request,
CancellationToken cancellationToken)
{
return Task.FromResult("Pong");
}
}

El código requerirá importar los espacios de nombres MediatR y System.Threading.

Ahora lo único que necesitamos es enviar el mensaje a través del Mediator.

71 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

3. En el directorio Controllers, agrega un nuevo archivo de clase llamado MessagesController.cs


utilizando la plantilla API Controller – Empty.

4. Agrega el siguiente código para inyectar el servicio IMediator a través del constructor de la
clase.

readonly IMediator Mediator;


public MessagesController(IMediator mediator) =>
Mediator = mediator;

El código requerirá importar el espacio de nombres MediatR.

5. Agrega el siguiente código para ejemplificar el envío del mensaje a través del Mediator.

[HttpGet("Send-And-Wait-For-Response")]
public async Task<IActionResult> SendAndWaitForResponse()
{
var Response = await Mediator.Send(new Ping());
return Ok(Response);
}

6. Establece el proyecto MediatRDemo como proyecto de inicio y ejecuta la aplicación.

7. En la página index.html prueba el funcionamiento de /api/Messages/Send-And-Wait-For-


Response.

Podrás ver el resultado similar al siguiente.

72 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

8. Regresa a Visual Studio y detén la ejecución.

9. Agrega un nuevo archivo de clase llamado OneWay.cs con el siguiente código para ejemplificar
una petición que no devuelve valor.

public class OneWay: IRequest{}

El código requerirá importar el espacio de nombres MediatR.

10. Agrega un nuevo archivo de clase llamado OneWayHandler.cs con el siguiente código para
definir el manejador del tipo de petición OneWay.

public class OneWayHandler : AsyncRequestHandler<OneWay>


{
protected override Task Handle(OneWay request,
CancellationToken cancellationToken)
{
Debug.WriteLine("Mensaje OneWay...");
return Task.CompletedTask;
}
}

El código requerirá importar los espacios de nombres MediatR y System.Diagnostics.

11. Agrega el siguiente código en la clase MessagesController para ejemplificar el envio de


mensajes que no devuelven valor.

[HttpGet("Send-Without-Response")]
public async Task<IActionResult> SendWithoutResponse()
{
await Mediator.Send(new OneWay());
return Ok("Completed!!!");
}

12. Ejecuta la aplicación y prueba la funcionalidad de /api/Messages/Send-Without-Response.

73 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

En la ventana Output de Visual Studio podrás ver un mensaje similar al siguiente.

La página index.html mostrará un resultado similar al siguiente.

Mensajes de Notificación
Los mensajes de Notificación (Notification) son mensajes que pueden ser enviados a múltiples
manejadores y pueden ser útiles para escenarios de manejo de eventos. Veamos cómo manejar
mensajes de notificación con MediatR.

1. Agrega un nuevo archivo de clase llamado PingNotification.cs con el siguiente código para
crear un mensaje de notificación.

public class PingNotification : INotification { }

El código requerirá importar el espacio de nombres MediatR.

74 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

El siguiente paso será crear uno o más manejadores para la notificación.

2. Agrega un nuevo archivo de clase llamado PingNotificationHandler1.cs con el siguiente código


para definir un manejador de la notificación PingNotification.

public class PingNotificationHandler1 :


INotificationHandler<PingNotification>
{
public Task Handle(PingNotification notification,
CancellationToken cancellationToken)
{
Debug.WriteLine("Pong 1...");
return Task.CompletedTask;
}
}

El código requerirá importar los espacios de nombres MediatR, System.Threading y


System.Diagnostics.

3. Agrega un nuevo archivo de clase llamado PingNotificationHandler2.cs con el siguiente código


para definir un segundo manejador de la notificación PingNotification.

public class PingNotificationHandler2 :


INotificationHandler<PingNotification>
{
public Task Handle(PingNotification notification,
CancellationToken cancellationToken)
{
Debug.WriteLine("Pong 2...");
return Task.CompletedTask;
}
}

El código requerirá importar los espacios de nombres MediatR, System.Threading y


System.Diagnostics.

4. Agrega el siguiente código a la clase MessagesController para ejemplificar el envío de


notificaciones.

[HttpGet("Send-Notifications")]
public async Task<IActionResult> SendNotifications()
{
await Mediator.Publish(new PingNotification());
return Ok("Completed!!!");
}

5. Ejecuta la aplicación y prueba la funcionalidad de /api/Messages/Send-Notifications.

75 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

La ventana Output de Visual Studio mostrará un resultado similar al siguiente.

La página index.html mostrará un resultado similar al siguiente.

La implementación predeterminada de Publish recorre los controladores de notificaciones y


espera a cada uno para asegurar que cada controlador se ejecute uno tras otro, sin embargo,
MediatR nos permite modificar la lógica de notificaciones y adaptarla a nuestras necesidades,
por ejemplo, para publicar todas las notificaciones en paralelo.

76 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Comportamientos (Behaviors) en MediatR


En MediatR las peticiones y respuestas viajan de ida y vuelta a través de canalizaciones (pipelines) muy
similar a la forma en que funcionan los middlewares en ASP.NET Core.

Cuando enviamos un mensaje, el mensaje de petición pasa desde el invocador hacia el manejador a
través de un canal (pipeline). Una vez procesado el mensaje, la respuesta viaja de regreso hacia el
invocador a través del canal.

La característica Behaviors de MediatR nos permite crear una canalización (pipeline). A través de esta
característica podemos, por ejemplo, colocar lógica de validación incluso antes de que los manejadores
la conozcan.

Veamos cómo utilizar la característica Behaviors de MediatR. Si lo deseas, antes de iniciar con los
siguientes pasos puedes realizar una copia de respaldo del directorio de la solución, llámala por
ejemplo MediatorDemoV5.

1. Agrega a la solución un nuevo proyecto llamado MediatRBehaviorsDemo utilizando la plantilla


ASP.NET Core Web API y con soporte a OpenAPI.

2. Agrega al proyecto el paquete NuGet MediatR.Extensions.Microsoft.DependencyInjection.


Este paquete incluye la biblioteca MediatR además del método de extensión
IServiceCollection.AddMediatR con diferentes sobrecargas que nos permiten registrar los
manejadores de peticiones ubicados en uno o más ensamblados especificados.

3. Abre el archivo Startup.cs.

4. Agrega el siguiente código al final del método ConfigureServices para registrar los
manejadores de peticiones ubicados en el ensamblado actual.

services.AddMediatR(Assembly.GetExecutingAssembly());

El código requerirá importar los espacios de nombres MediatR y System.Reflection.

5. Agrega un nuevo archivo de clase llamado CreateProduct.cs con el siguiente código para
representar una petición para crear un producto.

public class CreateProduct : IRequest<int>


{
public string Name { get; set; }

77 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

El código requerirá importar el espacio de nombres MediatR.

6. Agrega un nuevo archivo de clase llamado CreateProductHandler.cs con el siguiente código


para representar el manejador de la petición CreateProduct.

public class CreateProductHandler :


IRequestHandler<CreateProduct, int>
{
public Task<int> Handle(CreateProduct request,
CancellationToken cancellationToken)
{
Debug.WriteLine($"Crear el producto {request.Name}...");

return Task.FromResult(new Random().Next(1, 1000));


}
}

El código requerirá importar los espacios de nombres MediatR, System.Threading y


System.Diagnostics.

7. Agrega un nuevo archivo de clase llamado CreateProductValidationBehavior.cs con el


siguiente código para definir el comportamiento de canalización.

public class CreateProductValidationBehavior :


IPipelineBehavior<CreateProduct, int>
{
public Task<int> Handle(CreateProduct request,
CancellationToken cancellationToken,
RequestHandlerDelegate<int> next)
{
if (string.IsNullOrWhiteSpace(request.Name))
{
throw new ArgumentNullException("Name",
"El nombre del producto no puede ser nulo.");
}
return next();
}
}

El código requerirá importar los espacios de nombres MediatR y System.Threading.

Un comportamiento de canalización es una implementación de IPipelineBehavior<TRequest,


TResponse>. Representa un patrón similar a los filtros en ASP.NET. Podemos ver que el
comportamiento de canalización necesita implementar el método Handle.

El parámetro request es el objeto request pasado a través del método IMediator.Send,


mientras que el parámetro next es una continuación asíncrona para la siguiente acción en la

78 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

cadena de comportamiento. En el comportamiento, debemos esperar (await) o devolver la


invocación del delegado next.

En nuestro ejemplo estamos validando que el nombre del producto a crear tenga algún valor
para poder efectuar la petición. Si esta condición no se cumple, lanzamos una excepción, en
caso contrario, continuamos con la cadena de ejecución ejecutando la siguiente acción.

Los comportamientos deben ser registrados en el contenedor de inyección de dependencia.


MediatR los invocará en el orden en que se hayan registrado de tal forma que podamos realizar
algo de lógica antes y después de cada ejecución de los comandos.

8. Agrega el siguiente código al final del método ConfigureServices de la clase Startup para
registrar el comportamiento en el contenedor de inyección de dependencias.

services.AddTransient(typeof(IPipelineBehavior<CreateProduct, int>),
typeof(CreateProductValidationBehavior));

9. En la carpeta Controllers, agrega un nuevo archivo llamado BehaviorsController.cs utilizando


la plantilla API Controller – Empty con el siguiente código.

public class BehaviorsController : ControllerBase


{
readonly IMediator Mediator;
public BehaviorsController(IMediator mediator) =>
Mediator = mediator;
}

El código requerirá importar el espacio de nombres MediatR.

Puedes notar que estamos inyectando el servicio IMediator a través del constructor de la clase.

10. Agrega el siguiente código para ejemplificar el uso del comportamiento.

[HttpGet("Create-Product")]
public async Task<IActionResult> CreateProduct(string name)
{
IActionResult Result;
try
{
int Id = await Mediator.Send(new CreateProduct { Name = name });
Result = Ok(Id);
}
catch (Exception ex)
{
Result = BadRequest(ex.Message);
}
return Result;
}

79 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Puedes notar que estamos atrapando la posible excepción que pueda generarse durante el
manejo de la petición.

11. Establece el proyecto MediatRBehaviorsDemo como proyecto de inicio.

12. Ejecuta la aplicación y prueba la funcionalidad /api/Behaviors/Create-Product sin


proporcionar un nombre de producto.

Podrás notar que se ha disparado la excepción debido a que no se ha proporcionado el nombre


del producto. La excepción se ha disparado antes de invocar al manejador y por lo tanto, el
manejador no recibirá la petición.

13. Presiona F5 para continuar con la ejecución.

La ventana Output de Visual Studio mostrará un resultado similar al siguiente.

80 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

El código del manejador de la petición no fue ejecutado.

La página index.html mostrará un resultado similar al siguiente.

14. Prueba nuevamente la funcionalidad proporcionando ahora un nombre de producto.

La ventana Output de Visual Studio mostrará un resultado similar al siguiente.

81 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

La página index.html mostrará un resultado similar al siguiente.

Para obtener más información y ejemplos del uso de la biblioteca MediatR puede
consultarse el siguiente enlace:

jbogard/MediatR
https://github.com/jbogard/MediatR/wiki

82 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Lección 3:

El patrón CQRS
En las arquitecturas de software tradicionales, el mismo modelo de datos es utilizado para consultar y
actualizar una base de datos. Esto es sencillo y funciona bien para las operaciones CRUD básicas. Sin
embargo, en aplicaciones más complejas, este enfoque puede resultar difícil de manejar. Por ejemplo,
en el lado de lectura, la aplicación puede realizar muchas consultas diferentes y devolver Objetos de
Transferencia de Datos (DTO) con distintas estructuras. La asignación o mapeo de objetos puede llegar
a ser algo complicado. En el lado de escritura, el modelo puede implementar una validación y una
lógica de negocios complejas. En consecuencia, podemos acabar con un modelo excesivamente
complejo con demasiada funcionalidad violando el principio de responsabilidad única.

Las cargas de trabajo de lectura y escritura suelen ser asimétricas, con requisitos de rendimiento y
escalabilidad muy diferentes.

• A menudo hay una discordancia entre las representaciones de los datos para lectura y
escritura, tal como columnas o propiedades adicionales que se deben actualizar
correctamente como parte de una operación incluso aunque no sean necesarias.
• La contención de datos puede ocurrir cuando las operaciones son realizadas en paralelo en el
mismo conjunto de datos.
• El enfoque tradicional puede tener un impacto negativo en el rendimiento debido a la carga
en el almacén de datos y la capa de acceso a datos, y la complejidad de las consultas necesarias
para recuperar la información.
• La administración de la seguridad y los permisos pueden convertirse en algo complejo ya que
cada entidad está sujeta a operaciones de lectura y de escritura, lo cual podría exponer los
datos en el contexto equivocado.

En esta lección conoceremos la forma en que el patrón CQRS puede ayudarnos a dar solución a este
tipo de problemas y permitirnos con ello a contribuir al desarrollo de una arquitectura limpia de
software.

Objetivos

Al finalizar esta lección, los participantes contarán con las habilidades y conocimientos para:

• Describir qué es CQRS.


• Implementar el patrón CQRS.

83 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Introducción
CQRS es un acrónimo de Command and Query Responsibility Segregation (Segregación de
Responsabilidades de Comandos y Consultas). CQRS es un patrón de diseño que separa las operaciones
de lectura y actualización de un almacén de datos. La implementación de CQRS en una aplicación
puede maximizar su rendimiento, escalabilidad y seguridad. La flexibilidad creada al implementar CQRS
permite que un sistema evolucione mejor con el tiempo y evita que los comandos de actualización
provoquen conflictos de combinación en el nivel de dominio.

El patrón CQRS fue descrito en el año 2010 por Greg Young como una extensión del principio de
Separación de Comandos y Consultas (Command Query Separation - CQS) ideado por Bertrand Meyer
y descrito en su libro “Object Oriented Software Construction”.

El principio CQS afirma que cada método debe ser un comando que realiza una acción, o una consulta
que devuelve datos al invocador, pero no ambos. Los métodos deben devolver un valor sólo si son
referencialmente transparentes y, por tanto, no poseen efectos colaterales.

La idea con CQRS es permitir que una aplicación trabaje con diferentes modelos. En otras palabras,
podemos tener un modelo que tenga los datos necesarios para actualizar un registro, otro modelo
para insertar un registro y otro modelo para consultar un registro. Esto nos puede brindar flexibilidad
con escenarios variantes y complejos. No tenemos que depender de un solo DTO para todas las
operaciones CRUD mediante la implementación de CQRS.

Ventajas de la implementación del patrón CQRS:

• DTOs optimizados. Gracias al enfoque de segregación de este patrón, no necesitaremos de


clases modelo complejas en nuestra aplicación. Podremos tener un modelo por cada
operación de datos, lo que nos brinda una gran flexibilidad.

• Alta escalabilidad. Tener control sobre los modelos de acuerdo con el tipo de operaciones de
datos, hace que nuestra aplicación sea altamente escalable a largo plazo.

• Rendimiento mejorado. Normalmente, en una aplicación podemos tener más operaciones de


lectura en comparación a las operaciones de escritura. Con este patrón, podríamos
incrementar el rendimiento en las operaciones de lectura al introducir, por ejemplo, un
sistema de caché.

• Operaciones en paralelo seguras. Debido a que tenemos modelos dedicados por operación,
no hay posibilidad de pérdida de datos al realizar operaciones en paralelo.

Desventajas de la implementación del patrón CQRS:

• Agrega complejidad y más código. Algo que puede preocupar a algunos programadores es
que se trata de un patrón demandante de código. En otras palabras, necesitaremos más líneas

84 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

de código de lo que normalmente tendríamos. Esto puede ser considerado como un pequeño
precio que se tiene que pagar para obtener todas las ventajas descritas de este patrón.

Para obtener más información sobre el patrón CQRS pueden consultarse los siguientes
enlaces:

CQRS
https://martinfowler.com/bliki/CQRS.html

CQRS Documents by Greg Young


https://cqrs.files.wordpress.com/2010/11/cqrs_documents.pdf

85 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Implementando el patrón CQRS


Para ejemplificar el uso del patrón CQRS, desarrollaremos una aplicación ASP.NET Core Web API que
expondrá funcionalidad para realizar operaciones CRUD sobre la entidad Product que representará
registros de una base de datos.

Para enfocarnos únicamente en la implementación del patrón, no estaremos utilizando patrones de


arquitectura.

Crear la solución en Visual Studio


1. Abre Visual Studio bajo el contexto del administrador.

2. Crea un nuevo proyecto llamado CQRSDemo utilizando la plantilla ASP.NET Core Web API y
con soporte a OpenAPI.

Agregar el modelo Product


1. Crea un nuevo directorio llamado Models en la raíz del proyecto.

2. En el directorio Models, agrega un nuevo archivo de clase llamado Product.cs con el siguiente
código.

public class Product


{
public int Id { get; set; }
public string Name { get; set; }
public string QuantityPerUnit { get; set; }
public string Description { get; set; }
public decimal UnitPrice { get; set; }
public int UnitsInStock { get; set; }
public int UnitsOnOrder { get; set; }
public int ReorderLevel { get; set; }
public bool Discontinued { get; set; }
}

Agregar el contexto de datos


Para implementar el contexto de datos que nos permita realizar las operaciones CRUD sobre la entidad
Product, crearemos una interface y una implementación de ella utilizando ADO.NET.

86 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

1. Agrega el paquete NuGet Microsoft.Data.SqlClient al proyecto. Este paquete proporciona el


proveedor de datos ADO.NET para SQL Server.

2. Agrega un nuevo directorio llamado Context en la raíz del proyecto.

3. En el directorio Context, agrega un nuevo archivo de clase llamado IProductContext.cs con el


siguiente código.

public interface IProductContext


{
Task<int> Add(Product product);
Task<bool> Remove(int id);
Task<bool> Update(Product product);
Task<IEnumerable<Product>> GetAll();
Task<Product> GetById(int id);
}

El código requerirá importar el espacio de nombres CQRSDemo.Models.

4. En el directorio Context, agrega un nuevo archivo de clase llamado ProductContext.cs con el


siguiente código para implementar la Interface IProductContext.

public class ProductContext : IProductContext


{
readonly string ConnectionString;
public ProductContext(string connectionString) =>
ConnectionString = connectionString;

public async Task<int> Add(Product product)


{
Object CommandExecutionResult = null;
var Connection = new SqlConnection(ConnectionString);
try
{
var Command = Connection.CreateCommand();
Command.CommandType = System.Data.CommandType.Text;
Command.CommandText = "Insert into Products (" +
$"{nameof(Product.Name)}, " +
$"{nameof(Product.QuantityPerUnit)}, " +
$"{nameof(Product.Description)}, " +
$"{nameof(Product.UnitPrice)}, " +
$"{nameof(Product.UnitsInStock)}, " +
$"{nameof(Product.UnitsOnOrder)}, " +
$"{nameof(Product.ReorderLevel)}, " +
$"{nameof(Product.Discontinued)}) " +
$"Values(" +
$"@{nameof(Product.Name)}, " +

87 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

$"@{nameof(Product.QuantityPerUnit)}, "+
$"@{nameof(Product.Description)}, " +
$"@{nameof(Product.UnitPrice)}, " +
$"@{nameof(Product.UnitsInStock)}, " +
$"@{nameof(Product.UnitsOnOrder)}, " +
$"@{nameof(Product.ReorderLevel)}, " +
$"@{nameof(Product.Discontinued)});" +
"select @@Identity;";

SetSqlParameters(Command.Parameters, product);

await Connection.OpenAsync();
CommandExecutionResult = await Command.ExecuteScalarAsync();
await Command.DisposeAsync();
}
catch (Exception ex)
{
// Procesar la excepción
Debug.WriteLine(ex.Message);
}

await Connection.DisposeAsync();

return CommandExecutionResult ==
null ? -1 : Convert.ToInt32(CommandExecutionResult);
}

public async Task<bool> Update(Product product)


{
Object CommandExecutionResult = null;
var Connection = new SqlConnection(ConnectionString);
try
{
var Command = Connection.CreateCommand();
Command.CommandType = System.Data.CommandType.Text;
Command.CommandText = "Update Products set " +
$"{nameof(Product.Name)} = " +
$"@{nameof(Product.Name)}, " +
$"{nameof(Product.QuantityPerUnit)} = " +
$"@{nameof(Product.QuantityPerUnit)}, " +
$"{nameof(Product.Description)} = " +
$"@{nameof(Product.Description)}," +
$"{nameof(Product.UnitPrice)} = " +
$"@{nameof(Product.UnitPrice)}, " +
$"{nameof(Product.UnitsInStock)} = " +
$"@{nameof(Product.UnitsInStock)}, " +
$"{nameof(Product.UnitsOnOrder)} = " +
$"@{nameof(Product.UnitsOnOrder)}," +
$"{nameof(Product.ReorderLevel)} = " +
$"@{nameof(Product.ReorderLevel)}, " +
$"{nameof(Product.Discontinued)} = " +
$"@{nameof(Product.Discontinued)} " +
$"Where {nameof(Product.Id)} = " +
$"@{nameof(Product.Id)}";

88 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

SetSqlParameters(Command.Parameters, product);
Command.Parameters.AddWithValue(
$"@{nameof(Product.Id)}", product.Id);

await Connection.OpenAsync();
CommandExecutionResult = await Command.ExecuteNonQueryAsync();
await Command.DisposeAsync();
}
catch (Exception ex)
{
// Procesar la excepción
Debug.WriteLine(ex.Message);
}

await Connection.DisposeAsync();

return CommandExecutionResult ==
null ? false : ((int)CommandExecutionResult == 1);
}

public async Task<bool> Remove(int id)


{
Object CommandExecutionResult = null;
var Connection = new SqlConnection(ConnectionString);
try
{
var Command = Connection.CreateCommand();
Command.CommandType = System.Data.CommandType.Text;
Command.CommandText =
$"Delete from Products where " +
$"{nameof(Product.Id)} = @{nameof(Product.Id)};";

Command.Parameters.AddWithValue(
$"@{nameof(Product.Id)}", id);

await Connection.OpenAsync();
CommandExecutionResult = await Command.ExecuteNonQueryAsync();
await Command.DisposeAsync();
}
catch (Exception ex)
{
// Procesar la excepción
Debug.WriteLine(ex.Message);
}

await Connection.DisposeAsync();

return CommandExecutionResult ==
null ? false : ((int)CommandExecutionResult == 1);
}

public async Task<IEnumerable<Product>> GetAll()


{
List<Product> Products = null;
var Connection = new SqlConnection(ConnectionString);

89 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

try
{
var Command = Connection.CreateCommand();
Command.CommandType = System.Data.CommandType.Text;
Command.CommandText = "Select * from Products;";

await Connection.OpenAsync();
var Reader = await Command.ExecuteReaderAsync();
if (Reader != null)
{
Products = new List<Product>();
while (await Reader.ReadAsync())
{
Products.Add(GetProduct(Reader));
}
await Reader.DisposeAsync();
}
await Command.DisposeAsync();
}
catch (Exception ex)
{
// Procesar la excepción
Debug.WriteLine(ex.Message);
}

await Connection.DisposeAsync();

return Products;
}

public async Task<Product> GetById(int id)


{
Product Product = null;
var Connection = new SqlConnection(ConnectionString);
try
{
var Command = Connection.CreateCommand();
Command.CommandType = System.Data.CommandType.Text;
Command.CommandText = "Select * from Products where " +
$"{nameof(Product.Id)} = @{nameof(Product.Id)};";
Command.Parameters.AddWithValue(
$"@{nameof(Product.Id)}", id);

await Connection.OpenAsync();
var Reader = await Command.ExecuteReaderAsync();
if (Reader != null)
{
await Reader.ReadAsync();
Product = GetProduct(Reader);
await Reader.DisposeAsync();
}
await Command.DisposeAsync();
}
catch (Exception ex)
{

90 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

// Procesar la excepción
Debug.WriteLine(ex.Message);
}

await Connection.DisposeAsync();

return Product;
}

void SetSqlParameters(SqlParameterCollection parameters, Product product)


{
parameters.AddWithValue(
$"@{nameof(Product.Name)}", product.Name);
parameters.AddWithValue(
$"@{nameof(Product.QuantityPerUnit)}", product.QuantityPerUnit);
parameters.AddWithValue(
$"@{nameof(Product.Description)}", product.Description);
parameters.AddWithValue(
$"@{nameof(Product.UnitPrice)}", product.UnitPrice);
parameters.AddWithValue(
$"@{nameof(Product.UnitsInStock)}", product.UnitsInStock);
parameters.AddWithValue(
$"@{nameof(Product.UnitsOnOrder)}", product.UnitsOnOrder);
parameters.AddWithValue(
$"@{nameof(Product.ReorderLevel)}", product.ReorderLevel);
parameters.AddWithValue(
$"@{nameof(Product.Discontinued)}", product.Discontinued);
}

Product GetProduct(SqlDataReader reader)


{
return new Product
{
Id = reader.GetInt32(
reader.GetOrdinal(nameof(Product.Id))),
Name = reader.GetString(
reader.GetOrdinal(nameof(Product.Name))),
QuantityPerUnit = reader.GetString(
reader.GetOrdinal(nameof(Product.QuantityPerUnit))),
Description = reader.GetString(
reader.GetOrdinal(nameof(Product.Description))),
UnitPrice = reader.GetDecimal(
reader.GetOrdinal(nameof(Product.UnitPrice))),
UnitsInStock = reader.GetInt32(
reader.GetOrdinal(nameof(Product.UnitsInStock))),
UnitsOnOrder = reader.GetInt32(
reader.GetOrdinal(nameof(Product.UnitsOnOrder))),
ReorderLevel = reader.GetInt32(
reader.GetOrdinal(nameof(Product.ReorderLevel))),
Discontinued = reader.GetBoolean(
reader.GetOrdinal(nameof(Product.Discontinued)))
};
}
}

91 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

El código requerirá importar los espacios de nombres CQRSDemo.Models,


Microsoft.Data.SqlClient y System.Diagnostics.

Puedes notar que la clase ProductContext recibe la cadena de conexión a través del
constructor.

Registrar el servicio IProductContext


Registremos ahora el servicio IProductContext en el contenedor de inyección de dependencias de la
aplicación.

1. Abre el archivo Startup.cs.

2. Agrega el siguiente código al final del método ConfigureServices para registrar el servicio
IProductContext en el contenedor de inyección de dependencias.

services.AddSingleton<IProductContext>(
new ProductContext(Configuration.GetConnectionString("CQRSDemo")));

El código requerirá importar el espacio de nombres CQRSDemo.Context.

Definir la cadena de conexión


El código que registra el servicio hace uso de una cadena de conexión que debemos agregar en el
archivo de configuración appsettings.json.

1. Abre el archivo appsettings.json.

2. Agrega el siguiente código para definir la cadena de conexión.

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"CQRSDemo" : "Server=(localdb)\\mssqllocaldb;Database=CQRSDemo"
}
}

La cadena de conexión especifica la instancia Microsoft SQL Server Express LocalDB como
servidor de base de datos. También especifica la base de datos CQRSDemo que aún no
tenemos.

92 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Generar la base de datos


Ahora que hemos definido el modelo, el contexto de datos y la cadena de conexión, lo que sigue es
crear la base de datos.

1. Selecciona la opción View > SQL Server Object Explorer de la barra de menús de Visual Studio.

2. En la ventana SQL Server Object Explorer, selecciona la opción New Query… del menú
contextual de la instancia (localdb)\MSSQLLocalDB.

3. Copia el siguiente código en la ventana del editor de consultas para crear la base de datos y la
tabla Products.

use master
go
create database CQRSDemo
go
use CQRSDemo
go
CREATE TABLE Products(
Id int IDENTITY(1,1) NOT NULL,
Name nvarchar(40) NOT NULL,
QuantityPerUnit nvarchar(20) NULL,
Description nvarchar(max) NULL,

93 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

UnitPrice money NOT NULL,


UnitsInStock int NOT NULL,
UnitsOnOrder int NOT NULL,
ReorderLevel int NOT NULL,
Discontinued bit NOT NULL,
CONSTRAINT [PK_Products] PRIMARY KEY CLUSTERED(Id ASC)
)
GO

4. Haz clic en el botón Execute para ejecutar la consulta.

5. El panel de resultados de la consulta mostrará un mensaje indicando que el comando fue


ejecutado exitosamente.

6. En la ventana SQL Server Object Explorer, verifica que la base de datos CQRSDemo haya sido
creada junto con la tabla Products.

94 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

7. Cierra la ventana del editor de consultas. Si lo deseas, puedes guardar la consulta cuando te
sea solicitado.

La base de datos ha sido creada. Ahora, conectaremos esta base de datos a nuestra API para
realizar las operaciones CRUD.

El Patrón Mediator
Al crear aplicaciones ASP.NET Core, es muy importante tomar en cuenta que siempre debemos tener
el mínimo código posible dentro de los controladores. Los controladores son solo mecanismos de
enrutamiento que reciben una solicitud y la envían internamente a otros servicios o bibliotecas
específicas para después devolver un resultado. Lo recomendable es no tener lógica de validación de
datos o lógica de negocios dentro de los controladores.

El patrón de Mediator se adapta muy bien en la implementación de CQRS ya que reduce drásticamente
el acoplamiento entre varios componentes de una aplicación al hacer que se comuniquen
indirectamente a través de un objeto mediador.

En nuestra aplicación, utilizaremos el patrón Mediator para la implementación CQRS a través de la


biblioteca MediatR.

1. Agrega al proyecto el paquete NuGet MediatR.Extensions.Microsoft.DependencyInjection.


Este paquete incluye la biblioteca MediatR además del método de extensión
IServiceCollection.AddMediatR con diferentes sobrecargas que nos permiten registrar los
manejadores de peticiones ubicados en uno o más ensamblados especificados.

95 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

2. Abre el archivo Startup.cs.

3. Agrega el siguiente código al final del método ConfigureServices para registrar los
manejadores de peticiones ubicados en el ensamblado actual.

services.AddMediatR(Assembly.GetExecutingAssembly());

4. El código requerirá importar los espacios de nombres MediatR y System.Reflection.

Implementar las operaciones CRUD


Implementemos ahora las operaciones CRUD con el enfoque CQRS.

1. Crea un directorio llamado Application en la raíz del proyecto.

2. Crea un directorio llamado Products dentro del directorio Application.

3. Crea 3 directorios llamados Commands, Queries y Handlers dentro del directorio Products. La
estructura de archivos será similar a la siguiente.

Crear los Comandos

1. Dentro del directorio Commands, agrega un nuevo archivo de clase llamado


CreateProductCommand.cs con el siguiente código.

public class CreateProductCommand : IRequest<int>


{
public string Name { get; set; }
public string QuantityPerUnit { get; set; }
public string Description { get; set; }
public decimal UnitPrice { get; set; }
public int UnitsInStock { get; set; }
}

El código requerirá importar el espacio de nombres MediatR.

96 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

En nuestro ejemplo, la lógica de negocios que implementaremos indica que los únicos datos
disponibles para crear un nuevo producto serán el nombre (Name), la cantidad por unidad
(QuantityPerUnit), la descripción (Description), el precio unitario (UnitPrice) y las unidades
existentes del producto (UnitsInStock). Los demás datos del producto deberán ser
almacenados de la siguiente manera:

• UnitsOnOrder = 0
• ReorderLevel = 0
• Discontinued = false

Puedes notar que la clase CreateProductCommand es un tipo de petición que espera como
resultado un valor entero que representa el Id del producto creado. El Id será calculado
automáticamente por el servidor de bases de datos.

2. Agrega un nuevo archivo de clase llamado CreateProductCommandHandler.cs con el siguiente


código dentro del directorio Handlers.

public class CreateProductCommandHandler :


IRequestHandler<CreateProductCommand, int>
{
readonly IProductContext Context;
public CreateProductCommandHandler(IProductContext context) =>
Context = context;
public async Task<int> Handle(CreateProductCommand command,
CancellationToken cancellationToken)
{
var Product = new Product
{
Name = command.Name,
QuantityPerUnit = command.QuantityPerUnit,
Description = command.Description,
UnitPrice = command.UnitPrice,
UnitsInStock = command.UnitsInStock,
UnitsOnOrder = 0,
ReorderLevel = 0,
Discontinued = false
};

return await Context.Add(Product);


}
}

El código requerirá importar los espacios de nombres MediatR, System.Threading,


CQRSDemo.Application.Products.Commands, CQRSDemo.Context y CQRSDemo.Models.

Puedes notar que estamos implementando el método Handle para manejar la petición. En el
método únicamente estamos creando una instancia de Product, la llenamos con los datos que
vienen en el comando, complementamos con los valores constantes, invocamos al método

97 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Add de nuestro contexto recibido en el constructor y devolvemos el valor que nos regresa el
método Add.

3. Dentro del directorio Commands, agrega un nuevo archivo de clase llamado


DeleteProductCommand.cs con el siguiente código.

public class DeleteProductCommand :


IRequest<bool>
{
public int Id { get; set; }
}

El código requerirá importar el espacio de nombres MediatR.

La lógica de negocios que implementaremos solo requerirá del Id del producto a eliminar. Se
devolverá un valor booleano para indicar si la eliminación fue realizada exitosamente.

4. Agrega un nuevo archivo de clase llamado DeleteProductCommandHandler.cs con el siguiente


código dentro del directorio Handlers.

public class DeleteProductCommandHandler :


IRequestHandler<DeleteProductCommand, bool>
{
readonly IProductContext Context;
public DeleteProductCommandHandler(IProductContext context) =>
Context = context;

public async Task<bool> Handle(DeleteProductCommand command,


CancellationToken cancellationToken)
{
return await Context.Remove(command.Id);
}
}

El código requerirá importar los espacios de nombres MediatR, System.Threading,


CQRSDemo.Application.Products.Commands y CQRSDemo.Context.

5. Dentro del directorio Commands, agrega un nuevo archivo de clase llamado


UpdateProductCommand.cs con el siguiente código.

public class UpdateProductCommand : IRequest<bool>


{
public int Id { get; set; }
public string Name { get; set; }
public string QuantityPerUnit { get; set; }
public string Description { get; set; }
public decimal UnitPrice { get; set; }
}

El código requerirá importar el espacio de nombres MediatR.

98 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

En nuestro ejemplo, la lógica de negocios que implementaremos nos permitirá modificar solo
el nombre (Name), la cantidad por unidad (QuantityPerUnit), la descripción (Description) y el
precio unitario (UnitPrice) del producto. Se devolverá un valor booleano para indicar si la
modificación fue realizada exitosamente.

6. Agrega un nuevo archivo de clase llamado UpdateProductCommandHandler.cs con el


siguiente código dentro del directorio Handlers.

public class UpdateProductCommandHandler :


IRequestHandler<UpdateProductCommand, bool>
{
readonly IProductContext Context;
public UpdateProductCommandHandler(IProductContext context) =>
Context = context;

public async Task<bool> Handle(UpdateProductCommand command,


CancellationToken cancellationToken)
{
bool Result = false;
Product Product = await Context.GetById(command.Id);
if(Product != null)
{
Product.Name = command.Name;
Product.QuantityPerUnit = command.QuantityPerUnit;
Product.Description = command.Description;
Product.UnitPrice = command.UnitPrice;
Result = await Context.Update(Product);
}
return Result;
}
}

El código requerirá importar los espacios de nombres MediatR, System.Threading,


CQRSDemo.Application.Products.Commands, CQRSDemo.Context y CQRSDemo.Models.

Crear las Consultas

1. Dentro del directorio Queries, agrega un nuevo archivo de clase llamado


GetAllProductsQuery.cs con el siguiente código.

public class GetAllProductsQuery :


IRequest<IEnumerable<Product>>
{
}

El código requerirá importar el espacio de nombres MediatR y CQRSDemo.Models.

2. Agrega un nuevo archivo de clase llamado GetAllProductsQueryHandler.cs con el siguiente


código dentro del directorio Handlers.

99 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

public class GetAllProductsQueryHandler :


IRequestHandler<GetAllProductsQuery, IEnumerable<Product>>
{
readonly IProductContext Context;
public GetAllProductsQueryHandler(IProductContext context) =>
Context = context;

public async Task<IEnumerable<Product>> Handle(GetAllProductsQuery query,


CancellationToken cancellationToken)
{
return await Context.GetAll();
}
}

El código requerirá importar los espacios de nombres MediatR, CQRSDemo.Models,


CQRSDemo.Application.Products.Queries, CQRSDemo.Context y System.Threading.

3. Dentro del directorio Queries, agrega un nuevo archivo de clase llamado


GetProductByIdQuery.cs con el siguiente código.

public class GetProductByIdQuery : IRequest<Product>


{
public int Id { get; set; }
}

El código requerirá importar el espacio de nombres MediatR y CQRSDemo.Models.

4. Agrega un nuevo archivo de clase llamado GetProductByIdQueryHandler.cs con el siguiente


código dentro del directorio Handlers.

public class GetProductByIdQueryHandler :


IRequestHandler<GetProductByIdQuery, Product>
{
readonly IProductContext Context;
public GetProductByIdQueryHandler(IProductContext context) =>
Context = context;
public async Task<Product> Handle(GetProductByIdQuery query,
CancellationToken cancellationToken)
{
return await Context.GetById(query.Id);
}
}

El código requerirá importar los espacios de nombres MediatR, CQRSDemo.Models,


CQRSDemo.Application.Products.Queries, CQRSDemo.Context y System.Threading.

Crear el controlador Product


1. Agrega un nuevo archivo llamado ProductController.cs en el directorio Controllers utilizando
la plantilla API Controller – Empty.

100 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

2. Agrega el siguiente código para recibir una instancia de IMediator a través del constructor.

IMediator Mediator;
public ProductController(IMediator mediator) =>
Mediator = mediator;

El código requerirá importar el espacio de nombres MediatR;

3. Agrega el siguiente código que permitirá crear un nuevo producto.

[HttpPost]
public async Task<IActionResult> Create(CreateProductCommand command)
{
IActionResult Result =
BadRequest("No se ha creado el producto.");
int Id = await Mediator.Send(command);
if(Id > 0)
{
Result = Ok($"Producto {Id} creado.");
}
return Result;
}

El código requerirá el espacio de nombres CQRSDemo.Application.Products.Commands.

El código anterior agrega un poco de lógica para mostrar al cliente una respuesta apropiada
dependiendo de si se pudo crear el producto o no. En la siguiente lección implementaremos
una técnica basada en excepciones para que el código del constructor sea más limpio
encapsulando la lógica de presentación en un componente adicional. Los demás métodos de
acción implementarán de manera similar un poco de lógica de presentación que de igual forma
refactorizaremos en la siguiente lección.

4. Agrega el siguiente código que permitirá modificar un producto.

[HttpPut]
public async Task<IActionResult> Update(UpdateProductCommand command)
{
IActionResult Result =
BadRequest("No se ha podido realizar la modificación.");

bool Modified = await Mediator.Send(command);

if (Modified)
{
Result = Ok($"El producto {command.Id} ha sido modificado.");
}

return Result;
}

5. Agrega el siguiente código que permitirá eliminar un producto.

101 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

[HttpDelete]
public async Task<IActionResult> Delete(DeleteProductCommand command)
{
IActionResult Result =
BadRequest("No se ha podido eliminar el producto.");
bool Deleted =
await Mediator.Send(command);
if (Deleted)
{
Result = Ok($"Producto {command.Id} eliminado.");
}
return Result;

6. Agrega el siguiente código que permitirá obtener todos los productos.

[HttpGet]
public async Task<IActionResult> GetAll()
{
return Ok(await Mediator.Send(new GetAllProductsQuery()));
}

El código requerirá el espacio de nombres CQRSDemo.Application.Products.Queries.

7. Agrega el siguiente código que permitirá buscar un producto por su Id.

[HttpGet("{id}")]
public async Task<IActionResult> GetById(int id)
{
IActionResult Result =
BadRequest($"El producto {id} no ha sido encontrado.");

var Product =
await Mediator.Send(new GetProductByIdQuery { Id = id });
if(Product != null)
{
Result = Ok(Product);
}
return Result;
}

Probar la funcionalidad
1. Ejecuta la aplicación.

2. En la página index.html prueba la funcionalidad POST /api/Product para crear un nuevo


producto.

102 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

El resultado será similar al siguiente.

3. Agrega uno o más productos adicionales.

4. Prueba la funcionalidad GET /api/Product para obtener la lista de productos creada.

103 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

El resultado será similar al siguiente.

5. Prueba la funcionalidad GET /api/Product/{id} para recuperar un producto existente por su


Id.

104 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

El resultado será similar al siguiente.

6. Prueba la funcionalidad GET /api/Product/{id} para recuperar un producto no existente por


su Id. El resultado será similar al siguiente.

105 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

7. Prueba la funcionalidad para PUT /api/Product para modificar un producto existente.

El resultado será similar al siguiente.

106 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

8. Prueba la funcionalidad para PUT /api/Product para modificar un producto no existente. El


resultado será similar al siguiente.

9. Prueba la funcionalidad DELETE /api/Product para eliminar un producto por su Id.

107 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

El resultado será similar al siguiente.

10. Prueba la funcionalidad DELETE /api/Product para eliminar un producto no existente. El


resultado será similar al siguiente.

Hemos cubierto la implementación y definición de CQRS utilizando la funcionalidad del patrón


Mediador a través de la biblioteca MediatR, sin embargo, el Mediador que implementamos en la
lección anterior también podría haber funcionado en lugar de la biblioteca MediatR.

En la siguiente lección minimizaremos el uso de código en el controlador para dejarlo más limpio.

108 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Lección 4:

Manejo de excepciones
Cuando ejecutamos algún tipo de lógica de negocios y deseamos reportar alguna condición
significativa a las capas superiores, por ejemplo, para notificar al usuario que una determinada acción
no pudo ser realizada, es común hacerlo lanzado algún tipo de excepción personalizada.

En esta lección mostraremos una forma de manejar excepciones de forma centralizada para devolver
a los clientes respuestas apropiadas.

Objetivos

Al finalizar esta lección, los participantes contarán con las habilidades y conocimientos para:

• Crear tipos de excepciones personalizadas para notificar condiciones de error.


• Administrar excepciones en aplicaciones ASP.NET Core.

109 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Introducción
En la lección anterior implementamos el patrón CQRS para realizar operaciones CRUD. Si examinamos
el código implementado en los manejadores de peticiones y en el controlador ProductController
podemos notar las siguientes situaciones:

a) CreateProductCommandHandler devuelve -1 para indicar que no se pudo agregar un producto.


En el controlador ProductController agregamos lógica para examinar el valor devuelto para
crear la respuesta apropiada para el cliente.
b) UpdateProductCommandHandler devuelve false cuando el producto no puede ser encontrado
o no puede ser modificado. En el controlador ProductController agregamos lógica para
examinar el valor devuelto para crear la respuesta apropiada para el cliente.
c) DeleteProductCommandHandler devuelve false cuando el producto no puede ser eliminado.
En el controlador ProductController agregamos lógica para examinar el valor devuelto para
crear la respuesta apropiada para el cliente.
d) GetAllProductsQueryHandler devuelve null cuando se genera una excepción al obtener los
productos. En el controlador ProductController no estamos verificando esta condición de error.
e) GetProductByIdQueryHandler devuelve null cuando no se encuentra el producto buscado o
cuando se genera una excepción al obtener el producto. En el controlador ProductController
agregamos lógica para examinar el valor devuelto para crear la respuesta apropiada para el
cliente.

Para evitar la creación de código en el controlador, lanzaremos excepciones personalizadas desde los
distintos manejadores de comandos y consultas, y las atraparemos de forma centralizada para
construir la respuesta apropiada que devolveremos al cliente.

110 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Implementar excepciones de notificación


Implementemos una estrategia para enviar excepciones desde los manejadores de comandos y
consultas para notificar los casos en que no se pueda realizar alguna operación.

Preparar la solución inicial


1. Crea una copia del directorio de la solución CQRSDemo de la lección anterior y asígnale el
nombre CQRSDemoWithExceptions.

2. Cambia el nombre del archivo CQRSDemo.sln por CQRSDemoWithExceptions.sln.

3. Abre la solución CQRSDemoWithExceptions.sln en Visual Studio bajo el contexto de


Administrador.

Crear las excepciones personalizadas


1. Agrega un nuevo directorio llamado Domain en la raíz del proyecto.

2. Agrega un nuevo directorio llamado Exceptions dentro del directorio Domain.

3. Agrega un nuevo archivo de clase llamado GeneralException.cs dentro del directorio


Exceptions con el siguiente código.

public class GeneralException : Exception


{
public GeneralException() { }
public GeneralException(string message)
: base(message) { }
public GeneralException(string message, Exception inner)
: base(message, inner) { }
}

Utilizaremos este tipo de excepción para notificar una condición general de error
personalizada, por ejemplo, cuando no se haya podido crear un producto.

4. Agrega un nuevo archivo de clase llamado EntityNotFoundException.cs en el directorio


Exceptions con el siguiente código.

public class EntityNotFoundException : Exception


{
public string Entity { get; set; }
public object Key { get; set; }
public EntityNotFoundException() { }
public EntityNotFoundException(string message) :
base(message) { }
public EntityNotFoundException(
string message, Exception innerException) :

111 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

base(message, innerException){ }
public EntityNotFoundException(
string entity, object key) : base($"{entity} {key} no encontrado.") =>
(Entity, Key) = (entity, key);
}

Este tipo de excepción nos permitirá notificar el caso en que una entidad no haya sido
encontrada. Hemos definido la propiedad Entity para colocar ahí un texto que identifique la
entidad buscada. La propiedad Key nos permitirá indicar el valor que fue buscado y no pudo
ser encontrado.

Lanzar las excepciones


1. Modifica el código del método Handle de la clase CreateProductCommandHandler para lanzar
una excepción en el caso de que no se haya podido agregar el producto.

public async Task<int> Handle(CreateProductCommand command,


CancellationToken cancellationToken)
{
var Product = new Product
{
Name = command.Name,
QuantityPerUnit = command.QuantityPerUnit,
Description = command.Description,
UnitPrice = command.UnitPrice,
UnitsInStock = command.UnitsInStock,
UnitsOnOrder = 0,
ReorderLevel = 0,
Discontinued = false
};

int Id = await Context.Add(Product);


if(Id == -1)
{
throw new GeneralException(
"El producto no pudo ser creado.");
}

return Id;
}

El código requerirá importar el espacio de nombres CQRSDemo.Domain.Exceptions.

2. Modifica la clase UpdateProductCommandHandler para lanzar una excepción cuando el


producto a modificar no sea encontrado y otra excepción cuando el producto no pueda ser
modificado. El método ahora ya no necesita devolver información por lo que la definición
también cambiará.

public class UpdateProductCommandHandler :


AsyncRequestHandler<UpdateProductCommand>

112 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

{
readonly IProductContext Context;
public UpdateProductCommandHandler(IProductContext context) =>
Context = context;

protected override async Task Handle(UpdateProductCommand command,


CancellationToken cancellationToken)
{
Product Product = await Context.GetById(command.Id);
if (Product != null)
{
Product.Name = command.Name;
Product.QuantityPerUnit = command.QuantityPerUnit;
Product.Description = command.Description;
Product.UnitPrice = command.UnitPrice;
if (!await Context.Update(Product))
{
throw new GeneralException(
$"El producto {command.Id} no pudo ser modificado");
}
}
else
{
throw new EntityNotFoundException("Producto", command.Id);
}

}
}

El código requerirá importar el espacio de nombres CQRSDemo.Domain.Exceptions.

Debido a que hemos modificado la definición de la clase para implementar una petición que
no espera un valor de retorno, debemos modificar la clase UpdateProductCommand.

3. Modifica la definición de la clase UpdateProductCommand para implementar una petición que


no espera un valor.

public class UpdateProductCommand : IRequest


{
public int Id { get; set; }
public string Name { get; set; }
public string QuantityPerUnit { get; set; }
public string Description { get; set; }
public decimal UnitPrice { get; set; }
}

4. Modifica la clase DeleteProductCommandHandler para lanzar una excepción cuando el


producto a eliminar no pueda ser eliminado. El método ahora ya no necesita devolver
información por lo que la definición también cambiará.

public class DeleteProductCommandHandler :


AsyncRequestHandler<DeleteProductCommand>

113 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

{
readonly IProductContext Context;
public DeleteProductCommandHandler(IProductContext context) =>
Context = context;

protected override async Task Handle(DeleteProductCommand command,


CancellationToken cancellationToken)
{
if(!await Context.Remove(command.Id))
{
throw new GeneralException(
$"El producto {command.Id} no fue eliminado.");
}
}
}

El código requerirá importar el espacio de nombres CQRSDemo.Domain.Exceptions.

Debido a que hemos modificado la definición de la clase para implementar una petición que
no espera un valor de retorno, debemos modificar la clase DeleteProductCommand.

5. Modifica la definición de la clase DeleteProductCommand para implementar una petición que


no espera un valor.

public class DeleteProductCommand :


IRequest
{
public int Id { get; set; }
}

6. Modifica el código del método Handle de la clase GetAllProductsQueryHandler para lanzar una
excepción cuando la lista de productos a devolver sea null.

public async Task<IEnumerable<Product>> Handle(GetAllProductsQuery query,


CancellationToken cancellationToken)
{
var Products = await Context.GetAll();
if(Products is null)
{
throw new GeneralException(
"Error al obtener la lista de productos.");
}
return Products;
}
}

El código requerirá importar el espacio de nombres CQRSDemo.Domain.Exceptions.

7. Modifica el código del método Handle de la clase GetProductByIdQueryHandler para lanzar


una excepción cuando el producto a devolver sea null.

114 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

public async Task<Product> Handle(GetProductByIdQuery query,


CancellationToken cancellationToken)
{
var Product = await Context.GetById(query.Id);
if(Product is null)
{
throw new EntityNotFoundException("Producto", query.Id);
}
return Product;
}

El código requerirá importar el espacio de nombres CQRSDemo.Domain.Exceptions.

Modificar el controlador ProductController


Limpiemos ahora el código del controlador.

1. Modifica el código del método Create. Podemos devolver con toda seguridad el resultado
obtenido del método Send ya que de no haberse podido crear el producto, se habrá lanzado
una excepción que será atrapada por un filtro que implementaremos posteriormente.

[HttpPost]
public async Task<IActionResult> Create(CreateProductCommand command)
{
return Ok(await Mediator.Send(command));
}

2. Modifica el código del método Update. Podemos devolver con toda seguridad el mensaje
indicando que el producto ha sido modificado ya que de no haberse podido modificar, se habrá
lanzado una excepción que será atrapada por el filtro que implementaremos posteriormente.

[HttpPut]
public async Task<IActionResult> Update(UpdateProductCommand command)
{
await Mediator.Send(command);
return Ok($"El producto {command.Id} ha sido modificado.");
}

3. Modifica el código del método Delete. Podemos devolver con toda seguridad el mensaje
indicando que el producto ha sido eliminado ya que de no haberse podido eliminar, se habrá
lanzado una excepción que será atrapada por el filtro que implementaremos posteriormente.

[HttpDelete]
public async Task<IActionResult> Delete(DeleteProductCommand command)
{
await Mediator.Send(command);
return Ok($"Producto {command.Id} eliminado.");
}

115 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

4. Modifica el código del método GetById. Podemos devolver con toda seguridad el producto ya
que de no haberse podido encontrar, se habrá lanzado una excepción que será atrapada por
el filtro que implementaremos posteriormente.

[HttpGet("{id}")]
public async Task<IActionResult> GetById(int id)
{
return Ok(await Mediator.Send(
new GetProductByIdQuery { Id = id }));
}

Crear los manejadores de las excepciones

Cuando una excepción sea lanzada, un manejador se encargará de procesarla para crear la respuesta
apropiada que será devuelta al cliente.

Para atrapar excepciones de forma centralizada en aplicaciones ASP.NET Core, podemos definir un
filtro que se ejecute después de que se haya generado una excepción en una acción. Antes de definir
el filtro de ASP.NET Core, implementemos las clases que nos permitirán manejar las excepciones que
hemos creado.

1. Agrega un nuevo directorio llamado ExceptionHandlers en la raíz del proyecto.

2. Agrega un nuevo archivo de clase llamado IExceptionHandler.cs dentro del directorio


ExceptionHandlers con el siguiente contenido.

public interface IExceptionHandler


{
Task Handle(ExceptionContext context);
}

El código requerirá importar el espacio de nombres Microsoft.AspNetCore.Mvc.Filters.

La interface IExceptionHandler define el método Handle que deberá ser implementado por
los manejadores de excepciones. El método Handle recibe un parámetro de tipo
ExceptionContext que tendrá información detallada del tipo de excepción que se esté
procesando. El filtro de ASP.NET Core que crearemos se encargará de ejecutar el método
Handle del manejador de la excepción pasándole el parámetro ExceptionContext.

El código de los manejadores de excepción será muy similar, simplemente crearán un objeto
con la respuesta apropiada que será devuelta. Encapsulemos este código en una clase base.

3. Agrega un nuevo archivo de clase llamado ExceptionHandlerBase.cs en el directorio


ExceptionHandlers con el siguiente código.

public class ExceptionHandlerBase


{

116 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

// Lista de URIs que describen los distintos tipos de problemas HTTP


readonly Dictionary<int, string> RFC7231Types =
new Dictionary<int, string>
{
{ StatusCodes.Status500InternalServerError,
"https://datatracker.ietf.org/doc/html/rfc7231#section-6.6.1"},
{StatusCodes.Status404NotFound,
"https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.4"}
};

public Task SetResult(ExceptionContext context,


int? status, string title, string detail)
{
// Valores para especificar en respuestas de APIs HTTP basados en
// https://tools.ietf.org/html/rfc7807
ProblemDetails Details = new ProblemDetails
{
Status = status,
Title = title,
Type = RFC7231Types.ContainsKey(status.Value) ?
RFC7231Types[status.Value] : "",
Detail = detail
};

// Establecer el valor IActionResult a devolver


context.Result = new ObjectResult(Details)
{
StatusCode = status
};

// Indicar que se ha manejado la excepción.


context.ExceptionHandled = true;

return Task.CompletedTask;
}
}

El código requerirá importar los espacios de nombres Microsoft.AspNetCore.Http,


Microsoft.AspNetCore.Mvc.Filters, Microsoft.AspNetCore.Mvc.

ExceptionHandlerBase será la clase base que heredarán los manejadores de excepciones.

El método SetResult es encargado de devolver la respuesta con el detalle de la excepción. La


clase ProblemDetails contiene las propiedades necesarias para especificar errores en
respuestas de APIs HTTP con base en la especificación https://tools.ietf.org/html/rfc7807.

El diccionario RFC7231Types contiene los URIs que identifican los diferentes tipos de
problema.

El objeto context permite establecer el valor IActionResult a devolver y permite indicar que se
ha manejado la excepción.

117 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

4. Agrega un nuevo archivo de clase llamado GeneralExceptionHandler.cs en el directorio


ExceptionHandlers con el siguiente código.

public class GeneralExceptionHandler :


ExceptionHandlerBase, IExceptionHandler
{
public Task Handle(ExceptionContext context)
{
return SetResult(context, StatusCodes.Status500InternalServerError,
"Ha ocurrido un error al procesar la petición.",
context.Exception.Message);
}
}

El código requerirá importar los espacios de nombres Microsoft.AspNetCore.Mvc.Filters y


Microsoft.AspNetCore.Http.

Puedes notar que la clase hereda de ExceptionHandlerBase e implementa la interface


IExceptionHandler.

5. Agrega un nuevo archivo de clase llamado EntityNotFoundExceptionHandler.cs en el


directorio ExceptionHandlers con el siguiente código.

public class EntityNotFoundExceptionHandler :


ExceptionHandlerBase, IExceptionHandler
{
public Task Handle(ExceptionContext context)
{
EntityNotFoundException Exception =
context.Exception as EntityNotFoundException;

return SetResult(context, StatusCodes.Status404NotFound,


"El recurso especificado no fue encontrado",
$"Recurso {Exception.Entity} {Exception.Key} no encontrado. ");
}
}

El código requerirá importar los espacios de nombres Microsoft.AspNetCore.Mvc.Filters,


CQRSDemo.Domain.Exceptions y Microsoft.AspNetCore.Http.

El código realiza una conversión de la propiedad context.Exception a una instancia de


EntityNotFoundException para extraer el valor de las propiedades Entity y Key.

Crear el Filtro
Para atrapar todas las excepciones, definiremos un filtro de ASP.NET Core que se ejecute después de
que se haya lanzado una excepción en una acción. Para crear el filtro, heredaremos de la clase
abstracta ExceptionFilterAttribute. Esta clase se ejecuta de forma asíncrona después de que una

118 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

acción haya lanzado una excepción. Las clases que deriven de ExceptionFilterAttribute deben
sobrescribir el método OnException o el método OnExceptionAsync, pero no ambos.

1. Crea un nuevo directorio con el nombre Filters en la raíz del proyecto.

2. Agrega un nuevo archivo de clase llamado ApiExceptionFilterAttribute.cs en el directorio


Filters con el siguiente código.

public class ApiExceptionFilterAttribute : ExceptionFilterAttribute


{
// Diccionario de parejas Excepción-Manejador
readonly IDictionary<Type, IExceptionHandler> ExceptionHandlers;

public ApiExceptionFilterAttribute(
IDictionary<Type, IExceptionHandler> exceptionHandlers) =>
ExceptionHandlers = exceptionHandlers;

// Invocado cuando una acción lanza una excepción


public override void OnException(ExceptionContext context)
{
// Obtener el tipo de Excepción.
Type ExceptionType = context.Exception.GetType();

// Buscar el manejador de la excepción.


if (ExceptionHandlers.ContainsKey(ExceptionType))
{
// Si el manejador de la excepción es encontrado,
// ejecutar el método Handle.
ExceptionHandlers[ExceptionType].Handle(context);
}
else
{
// Si el tipo de excepción no es encontrado,
// invocar el método Handle de la clase ExceptionHandlerBase
// sin especificar el detalle de la excepción.
new ExceptionHandlerBase().SetResult(context,
StatusCodes.Status500InternalServerError,
"Ha ocurrido un error al procesar la respuesta.",
"");
}
base.OnException(context);
}
}

El código requerirá importar los espacios de nombres Microsoft.AspNetCore.Mvc.Filters,


CQRSDemo.ExceptionHandlers y Microsoft.AspNetCore.Http.

El filtro recibe en el constructor un diccionario conteniendo los tipos de excepciones con sus
respectivos tipos de manejadores.

119 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

El filtro busca el manejador correspondiente a la excepción generada y si es encontrado, invoca


su método Handle para que construya la respuesta apropiada. En caso contrario, invoca al
método SetResult de la clase ExceptionHandlerBase para construir la respuesta de cualquier
otra excepción.

Registrar el filtro
El siguiente paso es registrar el nuevo filtro creado.

1. Abre el archivo Startup.cs.

2. En el método ConfigureServices localiza la línea de código services.AddControllers(); y


modifícala por lo siguiente.

services.AddControllers(options=>
{
options.Filters.Add(new ApiExceptionFilterAttribute(
new Dictionary<Type, IExceptionHandler>
{
{typeof(EntityNotFoundException),
new EntityNotFoundExceptionHandler()},
{typeof(GeneralException),
new GeneralExceptionHandler()}
}
));
});

El código requerirá importar los espacios de nombres CQRSDemo.Filters,


CQRSDemo.ExceptionHandlers y CQRSDemo.Domain.Exceptions.

Probar la funcionalidad
1. Ejecuta la aplicación.

2. Prueba la funcionalidad POST /api/Product para crear un nuevo producto. Podrás ver un
resultado similar al siguiente.

3. Prueba la funcionalidad PUT /api/Product para modificar un producto existente. Podrás ver
un resultado similar al siguiente.

120 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

4. Prueba la funcionalidad PUT /api/Product para modificar un producto no existente. Podrás


ver un resultado similar al siguiente.

5. Prueba la funcionalidad DELETE /api/Product para eliminar un producto existente. Podrás ver
un resultado similar al siguiente.

6. Prueba la funcionalidad DELETE /api/Product para eliminar un producto no existente. Podrás


ver un resultado similar al siguiente.

121 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

7. Prueba la funcionalidad GET /api/Product/{id} para buscar un producto existente. Podrás ver
un resultado similar al siguiente.

8. Prueba la funcionalidad GET /api/Product/{id} para buscar un producto no existente. Podrás


ver un resultado similar al siguiente.

9. Regresa a Visual Studio y detén la ejecución.

10. Abre el archivo appsettings.json.

122 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

11. Modifica la cadena de conexión para especificar una base de datos no existente.

"CQRSDemo": "Server=(localdb)\\mssqllocaldb;Database=CQRSDemoNOEXISTE"

Esto hará que las operaciones sobre la base de datos no puedan ser realizadas.

12. Ejecuta la aplicación.

13. Prueba la funcionalidad GET /api/Product para intentar obtener la lista de productos. Podrás
ver un resultado similar al siguiente.

14. Prueba la funcionalidad POST /api/Product para crear un nuevo producto. Podrás ver un
resultado similar al siguiente.

15. Regresa a Visual Studio y detén la ejecución.

16. Corrige el valor de la cadena de conexión para que la aplicación vuelva a funcionar
correctamente.

¡Ahora ya tenemos un manejo de excepciones centralizado!

123 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Otra técnica común para manejo centralizado de excepciones es a través de la implementación de un


Middleware que se integra al pipeline de peticiones de ASP.NET Core. Un filtro de ASP.NET Core es a
final de cuentas una especie de Middleware.

124 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Lección 5:

Validación Fluida
La gran mayoría del software que desarrollamos requiere de algún tipo de implementación de
validación de los datos de entrada, debido a ello, existen frameworks y guías que nos ayudan a realizar
esa tarea.

Tradicionalmente, cuando se trata de validar modelos, el enfoque común que seguimos es el uso de
Anotaciones de Datos (Data Annotations) donde declaramos atributos sobre las propiedades del
modelo. Sin embargo, cuando deseamos implementar un código limpio o implementar principios
SOLID, las Anotaciones de Datos representan problemas graves para sistemas escalables ya que
claramente no es una buena práctica combinar los modelos con la lógica de validación.

Con la implementación de anotaciones de datos en clases .NET, ¿Qué pasaría si una clase modelo tiene
que ser utilizada en otra aplicación o método donde el tipo de validación es distinto? ¿Qué sucedería
si necesitamos validar un modelo del que no tenemos acceso a él para definir sus anotaciones?

En esta lección describiremos el uso de la biblioteca de Validación Fluida (FluentValidation) como la


alternativa preferida a las Anotaciones de Datos y su implementación en aplicaciones ASP.NET Core.

Objetivos

Al finalizar esta lección, los participantes contarán con las habilidades y conocimientos para:

• Describir las ventajas de la Validación Fluida.


• Describir el propósito de la biblioteca FluentValidation.
• Implementar la biblioteca FluentValidation con la biblioteca MediatR y el patrón CQRS.

125 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Introducción
FluentValidation es una biblioteca de validación para .NET de uso gratuito para crear reglas de
validación fuertemente tipadas. FluentValidation nos ayuda a implementar validaciones limpias, fáciles
de crear y mantener. Incluso funciona en modelos externos a los que no tenemos acceso. Con esta
biblioteca, podemos separar las clases modelo de la lógica de validación como sugiere una arquitectura
limpia. No hace falta que las clases modelo se “ensucien” como lo hacen las anotaciones de datos.
Además, un mejor control de la validación es algo que hace que los desarrolladores prefieran
FluentValidation.

FluentValidation utiliza expresiones lambda para construir reglas de validación.

Es probable que, para sistemas pequeños, la estrategia de validación recomendada sea Anotaciones
de Datos debido a su facilidad de implementación. Sin embargo, para sistemas más grandes y
complejos, la recomendación es separar la responsabilidad de la validación utilizando objetos de
validación con Validación Fluida.

126 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Iniciando con la biblioteca FluentValidation


Antes de implementar FluentValidation en aplicaciones ASP.NET Core, exploremos la funcionalidad
que esta nos proporciona.

Crear un proyecto Web API


Para ejemplificar el uso de la biblioteca FluentValidation crearemos una aplicación ASP.NET Core Web
API.

1. Abre Visual Studio bajo el contexto del Administrador.

2. Crea un nuevo proyecto llamado FluentValidationDemo utilizando la plantilla ASP.NET Core


Web API y con soporte a OpenAPI.

Instalación
Antes de crear validadores, necesitamos agregar una referencia del ensamblado FluentValidation.dll
en nuestro proyecto. La manera más simple de hacer esto es utilizando el administrador de paquetes
NuGet.

1. Agrega el paquete NuGet FluentValidation.AspNetCore al proyecto. Este paquete contiene


funcionalidad que se integra al esquema de validación de ASP.NET Core. Incluye la biblioteca
FluentValidation además de las extensiones de FluentValidation para inyección de
dependencias.

Crear el primer validador


Para definir un conjunto de reglas de validación para un objeto particular, necesitamos crear una clase
que herede de AbstractValidator<T> donde T es el tipo de la clase que queremos validar.

1. Agrega un nuevo directorio llamado Models en la raíz del proyecto.

2. Agrega un nuevo archivo de clase llamado Product.cs en el directorio Models con el siguiente
código.

public class Product


{
public int Id { get; set; }
public string Name { get; set; }

127 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

public string QuantityPerUnit { get; set; }


public decimal UnitPrice { get; set; }
public int UnitsInStock { get; set; }
}

La clase Product representa la clase modelo que queremos validar.

Podemos definir un conjunto de reglas de validación para esta clase heredando de la clase
abstracta AbstractValidator<Product>.

3. Agrega un nuevo directorio llamado Validators en la raíz del proyecto.

4. Agrega un nuevo archivo de clase llamado ProductValidator.cs en el directorio Validators con


el siguiente código.

public class ProductValidator : AbstractValidator<Product>


{
}

El código requerirá importar los espacios de nombres FluentValidation y


FluentValidationDemo.Models.

Las reglas de validación deben ser definidas en el constructor de la clase validador. Para
especificar una regla de validación para una propiedad en particular, invocamos al método
RuleFor pasándole una expresión lambda que indique la propiedad que deseamos validar.

5. Agrega el siguiente código a la clase ProductValidator para asegurar que la propiedad Name
tenga un valor.

public ProductValidator()
{
RuleFor(product => product.Name).NotEmpty();
}

El validador “Not Empty” fallará si la propiedad Name es Null, cadena vacía o cadena con
únicamente espacios.

Para ejecutar el validador debemos instanciar la clase validador e invocar el método Validate
o ValidateAsync pasándole el objeto a validar.

6. Agrega un nuevo archivo llamado ValidatorsController.cs en el directorio Controllers


utilizando la plantilla API Controller – Empty.

7. Agrega el siguiente código a la clase ValidatorsController para ejemplificar la ejecución del


validador.

[HttpGet("validate-product")]
public async Task<IActionResult> ValidateProduct(string name)

128 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

{
IActionResult Result;

Product Product = new Product { Name = name };


ProductValidator Validator = new ProductValidator();
ValidationResult ValidationResult =
await Validator.ValidateAsync(Product);

if(ValidationResult.IsValid)
{
Result = Ok("Producto válido!!!");
}
else
{
StringBuilder Builder = new StringBuilder();
foreach (var Failure in ValidationResult.Errors)
{
Builder.AppendLine(
string.Format("Propiedad: {0}. Error: {1}",
Failure.PropertyName, Failure.ErrorMessage));
}
Result = BadRequest(Builder.ToString());
}
return Result;
}

El código requerirá importar los espacios de nombres FluentValidationDemo.Models,


FluentValidationDemo.Validators, FluentValidation.Results y System.Text.

El método ValidateAsync devuelve un objeto FluentValidation.Results.ValidationResult que


contiene dos propiedades:

• IsValid. Una propiedad booleana que indica si la validación fue exitosa.


• Errors. Una colección de objetos ValidationFailure conteniendo el detalle de cada falla
de validación.

8. Ejecuta la aplicación y prueba la funcionalidad GET /api/Validators/validate-product


proporcionando un nombre de producto.

129 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Podrás ver un resultado similar a siguiente.

9. Prueba la misma funcionalidad sin proporcionar un nombre de producto o proporcionando


espacios en blanco. Podrás ver un resultado similar al siguiente.

También es posible invocar el método ToString del objeto ValidationResult para combinar
todos los mensajes de error en una sola cadena. De manera predeterminada, los mensajes son

130 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

separados por alimento de línea, pero, si lo deseamos podemos pasar un carácter separador
diferente en el método ToString. El siguiente código devuelve la lista de mensajes de error
separados por un caracter ~.

string Messages = ValidationResult.ToString("~");

En caso de que no haya errores, ToString devolverá una cadena vacía.

Encadenando validadores
FluentValidation nos permite también encadenar múltiples validadores para la misma propiedad.

1. Modifica el código del validador con el siguiente código para validar que la propiedad Name
tenga un valor y que ese valor no sea el texto “Chai”.

public ProductValidator()
{
RuleFor(product => product.Name).NotEmpty().NotEqual("Chai");
}

2. Ejecuta la aplicación y verifica que la validación falla cuando proporcionas el texto Chai como
nombre del producto. Podrás ver un resultado similar al siguiente.

Lanzando excepciones
En lugar de devolver ValidationResult, podemos utilizar alternativamente el método de extensión
ValidateAndThrow o ValidateAndThrowAsync para indicar a FluentValidation que dispare una
excepción cuando la validación falle.

1. Agrega el siguiente código a la clase ValidatorsController para ejemplificar el uso del método
ValidateAndThrowAsync.

[HttpGet("validate-and-throw")]
public async Task<IActionResult> ValidateAndThrow(string name)
{

131 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

IActionResult Result;

Product Product = new Product { Name = name };


ProductValidator Validator = new ProductValidator();
try
{
await Validator.ValidateAndThrowAsync(Product);
Result = Ok("Producto válido!!!");
}
catch(ValidationException ex)
{
StringBuilder Builder = new StringBuilder();
foreach (var Failure in ex.Errors)
{
Builder.AppendLine(
string.Format("Propiedad: {0}. Error: {1}",
Failure.PropertyName, Failure.ErrorMessage));
}

Result = BadRequest(Builder.ToString());
}

return Result;
}

El código requerirá importar el espacio de nombres FluentValidation.

El método ValidateAndThrowAsync dispara una excepción de tipo ValidationException que


contiene los mensajes de error en su propiedad Errors.

El método ValidateAndThrow es un método de extensión que envuelve la API de opciones de


FluentValidation y es equivalente a hacer lo siguiente.

await Validator.ValidateAsync(Product, options => options.ThrowOnFailures());

Propiedades complejas
Los validadores pueden ser reutilizados para propiedades complejas. Veamos un ejemplo.

1. Agrega un nuevo archivo de clase llamado Supplier.cs en el directorio Models con el siguiente
código.

public class Supplier


{
public int Id { get; set; }
public string CompanyName { get; set; }
public string Address { get; set; }
public string Country { get; set; }
public string City { get; set; }
public string PostalCode { get; set; }
}

132 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

La clase Supplier permite almacenar la información de un proveedor de productos.

2. Modifica la clase Product para incluir una propiedad que especifique el proveedor del
producto.

public class Product


{
public int Id { get; set; }
public string Name { get; set; }
public string QuantityPerUnit { get; set; }
public decimal UnitPrice { get; set; }
public int UnitsInStock { get; set; }
public Supplier Supplier { get; set; }
}

3. Agrega un nuevo archivo de clase llamado SupplierValidator.cs en el directorio Validators con


el siguiente código.

public class SupplierValidator : AbstractValidator<Supplier>


{
public SupplierValidator()
{
RuleFor(supplier => supplier.PostalCode).NotNull();
}
}

El código requerirá importar el espacio de nombres FluentValidation y


FluentValidationDemo.Models.

El validador únicamente valida que el código postal no sea Null.

Ahora podemos utilizar el validador SupplierValidator en la definición del validador


ProductValidator.

4. Modifica el código de la clase ProductValidator para incluir la validación del proveedor.

public class ProductValidator : AbstractValidator<Product>


{
public ProductValidator()
{
RuleFor(product => product.Name).NotEmpty().NotEqual("Chai");
RuleFor(product => product.Supplier)
.SetValidator(new SupplierValidator());
}
}

Ahora cuando invoquemos al método Validate del ProductValidator, los validadores definidos
tanto en ProductValidator como en SupplierValidator serán evaluados y los resultados serán
combinados en un solo ValidationResult.

133 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

5. Agrega el siguiente código en la clase ValidatorsController para ejemplificar la ejecución de los


validadores.

[HttpGet("validate-product-and-supplier")]
public async Task<IActionResult> ValidateProductAndSupplier(string name)
{
IActionResult Result;

Product Product = new Product { Name = name };


ProductValidator Validator = new ProductValidator();
ValidationResult ValidationResult =
await Validator.ValidateAsync(Product);

if (ValidationResult.IsValid)
{
Result = Ok("Producto válido!!!");
}
else
{
Result = BadRequest(ValidationResult.ToString("~"));
}
return Result;
}

6. Ejecuta la aplicación y prueba la funcionalidad GET /api/Validators/validate-product-and-


supplier proporcionando un nombre de producto. Podrás ver un resultado similar al siguiente.

Si la propiedad hija (Supplier) es Null como en nuestro caso, el validador de la propiedad no


será ejecutado. Esa es la razón de que el resultado de la validación haya sido exitoso.

7. Modifica el código que crea la instancia de Product en el método ValidateProductAndSupplier


para crear una instancia para la propiedad Supplier.

Product Product = new Product


{
Name = name,
Supplier = new Supplier()
};

Puedes notar que no estamos especificando valores para las propiedades de la instancia
Supplier.

134 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

8. Ejecuta la aplicación y prueba la funcionalidad GET /api/Validators/validate-product-and-


supplier proporcionando un nombre de producto. Podrás ver un resultado similar al siguiente.

Puedes notar que debido a que no asignamos un valor a PostalCode, se ha devuelto un error
de validación.

9. Prueba la funcionalidad GET /api/Validators/validate-product-and-supplier sin proporcionar


un nombre de producto. Podrás ver un resultado similar al siguiente.

Puedes notar que ahora se muestran los dos errores de validación separados por el carácter
~.

10. Modifica el código que crea la instancia de Product en el método ValidateProductAndSupplier


para crear una instancia para la propiedad Supplier asignando un valor a PostalCode.

Product Product = new Product


{
Name = name,
Supplier = new Supplier { PostalCode = "72500"}
};

11. Ejecuta la aplicación y prueba la funcionalidad GET /api/Validators/validate-product-and-


supplier proporcionando un nombre de producto. Podrás ver un resultado similar al siguiente.

135 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

12. Prueba la funcionalidad GET /api/Validators/validate-product-and-supplier sin proporcionar


un nombre de producto. Podrás ver un resultado similar al siguiente.

En lugar de utilizar un validador hijo, también podemos definir reglas hijas en línea. Veamos
un ejemplo.

13. Agrega un nuevo archivo de clase llamado ProductValidator2.cs en el directorio Validators con
el siguiente código.

public class ProductValidator2 : AbstractValidator<Product>


{
public ProductValidator2()
{
RuleFor(product => product.Name).NotEmpty().NotEqual("Chai");
RuleFor(product => product.Supplier.PostalCode).NotNull();
}
}

Puedes notar que ahora estamos validando directamente la propiedad PostalCode sin utilizar
el validador SupplierValidator.

14. Agrega el siguiente código en la clase ValidatorsController para ejemplificar la ejecución del
nuevo validador.

[HttpGet("validate-product-and-supplier-using-child-rule")]
public async Task<IActionResult>
ValidateProductAndSupplierUsingChildRule(string name)

136 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

{
IActionResult Result;

Product Product = new Product


{
Name = name
};

ProductValidator2 Validator = new ProductValidator2();


ValidationResult ValidationResult =
await Validator.ValidateAsync(Product);

if (ValidationResult.IsValid)
{
Result = Ok("Producto válido!!!");
}
else
{
Result = BadRequest(ValidationResult.ToString("~"));
}
return Result;
}

Puedes notar que no hemos asignado un valor a la propiedad Supplier de Product.

15. Ejecuta la aplicación y prueba la nueva funcionalidad proporcionando un nombre de producto.


Una excepción será disparada debido a que la propiedad Supplier de Product no tiene un valor
asignado.

137 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

En este caso, una verificación de Null no será realizada automáticamente sobre Supplier, por
lo tanto, debemos agregar explícitamente esa condición.

16. Modifica el código de ProductValidator2 para agregar una condición que valide que Supplier
no sea Null.

public class ProductValidator2 : AbstractValidator<Product>


{
public ProductValidator2()
{
RuleFor(product => product.Name).NotEmpty().NotEqual("Chai");
RuleFor(product => product.Supplier.PostalCode)
.NotNull().When(product => product.Supplier != null);
}
}

17. Ejecuta la aplicación y prueba nuevamente la funcionalidad proporcionando un nombre de


producto. Podrás ver un resultado similar al siguiente.

Ahora la regla sobre la propiedad Product.Supplier.PostalCode solo será evaluada cuando la


propiedad Product.Supplier sea distinta de Null.

18. Modifica el código que crea la instancia Product en ValidateProductAndSupplierUsingChildRule


para crear una instancia para la propiedad Supplier.

Product Product = new Product


{
Name = name,
Supplier = new Supplier()
};
19. Puedes notar que no estamos especificando valores para las propiedades de la instancia
Supplier.

20. Ejecuta la aplicación y prueba la funcionalidad proporcionando un nombre de producto.


Podrás ver un resultado similar al siguiente.

138 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

La validación se está realizando.

21. Prueba la funcionalidad sin proporcionar un nombre de producto. Podrás ver un resultado
similar al siguiente.

Puedes notar que ahora se muestran los dos errores de validación separados por el carácter
~.

22. Modifica el código que crea la instancia Product en ValidateProductAndSupplierUsingChildRule


para crear una instancia para la propiedad Supplier asignando un valor a PostalCode.

Product Product = new Product


{
Name = name,
Supplier = new Supplier { PostalCode = "72500"}
};

23. Ejecuta la aplicación y prueba la funcionalidad proporcionando un nombre de producto.


Podrás ver un resultado similar al siguiente.

139 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

24. Prueba la funcionalidad sin proporcionar un nombre de producto. Podrás ver un resultado
similar al siguiente.

Colecciones

Colecciones de tipos simples

Podemos utilizar el método RuleForEach para aplicar la misma regla a múltiples elementos en una
colección.

1. Agrega un nuevo archivo de clase llamado Employee.cs en el directorio Models con el siguiente
código.

public class Employee


{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public List<string> PhoneNumbers { get; set; }

2. Agrega un nuevo archivo de clase llamado EmployeeValidator.cs en el directorio Validators


con el siguiente código.

public class EmployeeValidator : AbstractValidator<Employee>

140 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

{
public EmployeeValidator()
{
RuleForEach(employee => employee.PhoneNumbers)
.NotNull();
}
}

El código requerirá importar los espacios de nombres FluentValidationDemo.Models y


FluentValidation.

La regla que hemos creado ejecutará una verificación NotNull para cada elemento en la
colección PhoneNumbers.

3. Agrega el siguiente código en la clase ValidatorsController para ejemplificar la ejecución del


nuevo validador.

[HttpGet("validate-collections-simple-types")]
public async Task<IActionResult> ValidateCollectionsSimpleTypes()
{
IActionResult Result;

Employee Employee = new Employee


{
PhoneNumbers = new List<string>
{
"+52-222-2162834",
null,
"+57-312-0645678"
}
};

EmployeeValidator Validator = new EmployeeValidator();


ValidationResult ValidationResult =
await Validator.ValidateAsync(Employee);

if (ValidationResult.IsValid)
{
Result = Ok("Empleado válido!!!");
}
else
{
Result = BadRequest(ValidationResult.ToString("~"));
}
return Result;
}

Puedes notar que estamos proporcionando un teléfono Null. Esto debe hacer fallar la
validación.

4. Ejecuta la aplicación y prueba la funcionalidad. Podrás ver un resultado similar al siguiente.

141 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Si queremos acceder al índice del elemento de la colección que causó la falla de validación,
podemos utilizar el marcador de posición (placeholder) {CollectionIndex}.

5. Modifica el código de EmployeeValidator para personalizar el mensaje de error e incluir el


marcador de posición que indique el elemento que causó la falla de validación.

public class EmployeeValidator : AbstractValidator<Employee>


{
public EmployeeValidator()
{
RuleForEach(employee => employee.PhoneNumbers)
.NotNull()
.WithMessage("La dirección {CollectionIndex} es requerida.");
}
}

6. Ejecuta la aplicación y prueba la funcionalidad. Podrás ver un resultado similar al siguiente.

El índice del elemento de la colección que hizo fallar la regla es 1. Si tuviéramos más elementos
Null, los mensajes correspondientes serian mostrados.

7. Modifica el código que crea la instancia Employee en ValidateCollectionsSimpleTypes para que


no incluya elementos Null en su propiedad PhoneNumbers.

142 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Employee Employee = new Employee


{
PhoneNumbers = new List<string>
{
"+52-222-2162834",
"+57-312-1635679",
"+57-312-0645678"
}

};

8. Ejecuta la aplicación y prueba la funcionalidad. Podrás ver un resultado similar al siguiente.

Colecciones de tipos complejos

También es posible combinar RuleForEach con SetValidator cuando la colección es de otros objetos
complejos.

1. Agrega un nuevo archivo de clase llamado Category.cs en el directorio Models con el siguiente
código.

public class Category


{
public int Id { get; set; }
public string Name { get; set; }
public List<Product> Products { get; set; }
}

2. Agrega un nuevo archivo de clase llamado CategoryValidator.cs en el directorio Validators con


el siguiente código.

public class CategoryValidator : AbstractValidator<Category>


{
public CategoryValidator()
{
RuleForEach(category => category.Products)
.SetValidator(new ProductValidator());
}
}

143 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

El código requerirá importar los espacios de nombres FluentValidationDemo.Models y


FluentValidation.

3. Agrega el siguiente código en la clase ValidatorsController para ejemplificar la ejecución del


nuevo validador.

[HttpGet("validate-collections-complex-types")]
public async Task<IActionResult> ValidateCollectionsComplexTypes()
{
IActionResult Result;

Category Category = new Category


{
Name = "Bebidas",
Products = new List<Product>
{
new Product{Name = "Leche"},
new Product{Name = "Jugo"},
new Product{Id = 3},
new Product{Name= "Cerveza"},
new Product{Id = 5, Supplier = new Supplier()}
}
};

CategoryValidator Validator = new CategoryValidator();


ValidationResult ValidationResult =
await Validator.ValidateAsync(Category);

if (ValidationResult.IsValid)
{
Result = Ok("Categoría válida!!!");
}
else
{
Result = BadRequest(ValidationResult.ToString("\n"));
}
return Result;
}

4. Ejecuta la aplicación y prueba la nueva funcionalidad. Podrás ver un resultado similar al


siguiente.

144 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Alternativamente, también podemos utilizar el método ChildRules para definir reglas en línea
para los elementos de la colección.

5. Agrega un nuevo archivo de clase llamado CategoryValidator2.cs en el directorio Validators


con el siguiente código.

public class CategoryValidator2 : AbstractValidator<Category>


{
public CategoryValidator2()
{
RuleForEach(category => category.Products)
.ChildRules(products =>
{
products.RuleFor(product => product.Name)
.NotEmpty()
.WithMessage(
"Nombre del producto {CollectionIndex} requerido.");
products.RuleFor(product => product.Supplier.PostalCode)
.NotNull().When(product => product.Supplier != null)
.WithMessage(
"CP proveedor de producto {CollectionIndex} requerido.");
});
}
}

El código requerirá importar los espacios de nombres FluentValidationDemo.Models y


FluentValidation.

6. Agrega el siguiente código en la clase ValidatorsController para ejemplificar la ejecución del


nuevo validador.

[HttpGet("validate-collections-complex-types-inline")]
public async Task<IActionResult> ValidateCollectionsComplexTypesInline()
{
IActionResult Result;

Category Category = new Category


{
Name = "Bebidas",
Products = new List<Product>
{
new Product{Name = "Leche"},
new Product{Name = "Jugo"},
new Product{Id = 3},
new Product{Name= "Cerveza"},
new Product{Id = 5, Supplier = new Supplier()}
}
};

CategoryValidator2 Validator = new CategoryValidator2();


ValidationResult ValidationResult =
await Validator.ValidateAsync(Category);

145 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

if (ValidationResult.IsValid)
{
Result = Ok("Categoría válida!!!");
}
else
{
Result = BadRequest(ValidationResult.ToString("\n"));
}
return Result;
}

7. Ejecuta la aplicación y prueba la nueva funcionalidad. Podrás ver un resultado similar al


siguiente.

Si lo necesitamos, podemos utilizar el método Where para condicionar los elementos de la


colección que deben ser validados.

8. Agrega un nuevo archivo de clase llamado CategoryValidator3.cs en el directorio Validators


con el siguiente código.

public class CategoryValidator3 : AbstractValidator<Category>


{
public CategoryValidator3()
{
RuleForEach(category => category.Products)
.Where(product => product.UnitsInStock > 0)
.ChildRules(products =>
{
products.RuleFor(product => product.Name)
.NotEmpty()
.WithMessage(
"Nombre del producto {CollectionIndex} requerido.");
products.RuleFor(product => product.Supplier.PostalCode)
.NotNull().When(product => product.Supplier != null)
.WithMessage(
"CP proveedor de producto {CollectionIndex} requerido.");
});
}
}

146 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

El código requerirá importar los espacios de nombres FluentValidationDemo.Models y


FluentValidation.

La validación solamente será aplicada a los productos cuya unidad en existencia sea mayor que
0.

9. Agrega el siguiente código en la clase ValidatorsController para ejemplificar la ejecución del


nuevo validador.

[HttpGet("validate-collections-complex-types-inline-where")]
public async Task<IActionResult>
ValidateCollectionsComplexTypesInlineWhere()
{
IActionResult Result;

Category Category = new Category


{
Name = "Bebidas",
Products = new List<Product>
{
new Product{Name = "Leche", UnitsInStock = 10},
new Product{Name = "Jugo", UnitsInStock = 20},
new Product{Id = 3},
new Product{Name= "Cerveza", UnitsInStock = 30},
new Product{Id = 5, Supplier = new Supplier(),
UnitsInStock = 1}
}
};

CategoryValidator3 Validator = new CategoryValidator3();


ValidationResult ValidationResult =
await Validator.ValidateAsync(Category);

if (ValidationResult.IsValid)
{
Result = Ok("Categoría válida!!!");
}
else
{
Result = BadRequest(ValidationResult.ToString("\n"));
}
return Result;
}

10. Ejecuta la aplicación y prueba la nueva funcionalidad. Podrás ver un resultado similar al
siguiente.

147 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Puedes notar que el 3er producto de la colección no fue validado ya que no tiene una
existencia mayor que 0.

También es posible crear reglas que se apliquen directamente sobre colecciones en lugar de
aplicarlas sobre los elementos de la colección.

11. Agrega un nuevo archivo de clase llamado CategoryValidator4.cs en el directorio Validators


con el siguiente código.

public class CategoryValidator4 : AbstractValidator<Category>


{
public CategoryValidator4()
{
// Solo se permiten máximo 3 productos
RuleFor(category => category.Products)
.Must(products => products.Count <= 3)
.WithMessage("Máximo 3 productos permitidos.");

// Cada producto debe tener nombre.


RuleForEach(category => category.Products)
.Must(product => !string.IsNullOrWhiteSpace(product.Name))
.WithMessage("Nombre requerido producto {CollectionIndex}.");
}
}

El código requerirá importar los espacios de nombres FluentValidationDemo.Models y


FluentValidation.

12. Agrega el siguiente código en la clase ValidatorsController para ejemplificar la ejecución del
nuevo validador.

[HttpGet("validate-collections-complex-types-for-foreach")]
public async Task<IActionResult>
ValidateCollectionsComplexTypesForForEach()
{
IActionResult Result;

Category Category = new Category


{

148 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Name = "Bebidas",
Products = new List<Product>
{
new Product{Name = "Leche"},
new Product{Name = "Jugo"},
new Product{Id = 3},
new Product{Name = "Cerveza"}
}
};

CategoryValidator4 Validator = new CategoryValidator4();


ValidationResult ValidationResult =
await Validator.ValidateAsync(Category);

if (ValidationResult.IsValid)
{
Result = Ok("Categoría válida!!!");
}
else
{
Result = BadRequest(ValidationResult.ToString("\n"));
}
return Result;
}

13. Ejecuta la aplicación y prueba la nueva funcionalidad. Podrás ver un resultado similar al
siguiente.

Una alternativa al uso de RuleForEach es invocar ForEach como parte de un RuleFor. Con este
enfoque podemos combinar reglas que actúen directamente sobre la colección con reglas que
actúen sobre elementos individuales dentro de la colección.

14. Agrega un nuevo archivo de clase llamado CategoryValidator5.cs en el directorio Validators


con el siguiente código.

public class CategoryValidator5 : AbstractValidator<Category>


{
public CategoryValidator5()
{
// Solo se permiten máximo 3 productos
RuleFor(category => category.Products)

149 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

.Must(products => products.Count <= 3)


.WithMessage("Máximo 3 productos permitidos.")
.ForEach(products => products // Cada producto debe tener nombre.
.Must(product =>
!string.IsNullOrWhiteSpace(product.Name))
.WithMessage(
"Nombre requerido producto {CollectionIndex}.")
);
}
}

El código requerirá importar los espacios de nombres FluentValidationDemo.Models y


FluentValidation.

El código anterior combina una regla que actúa directamente sobre la colección y una regla
que actúa sobre cada elemento de la colección.

15. Agrega el siguiente código en la clase ValidatorsController para ejemplificar la ejecución del
nuevo validador.

[HttpGet("validate-collections-complex-types-forforeach-combined")]
public async Task<IActionResult>
ValidateCollectionsComplexTypesForForEachCombined()
{
IActionResult Result;

Category Category = new Category


{
Name = "Bebidas",
Products = new List<Product>
{
new Product{Name = "Leche"},
new Product{Name = "Jugo"},
new Product{Id = 3},
new Product{Name = "Cerveza"}
}
};

CategoryValidator5 Validator = new CategoryValidator5();


ValidationResult ValidationResult =
await Validator.ValidateAsync(Category);

if (ValidationResult.IsValid)
{
Result = Ok("Categoría válida!!!");
}
else
{
Result = BadRequest(ValidationResult.ToString("\n"));
}
return Result;
}

150 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

16. Ejecuta la aplicación y prueba la nueva funcionalidad. Podrás ver un resultado similar al
siguiente.

Validación de herencia
Si un objeto contiene una propiedad que es una clase base o Interface, podemos configurar validadores
hijos específicos para clases derivadas y/o implementadores. Veamos un ejemplo.

1. Agrega un nuevo archivo llamado IContact.cs en el directorio Models con el siguiente código.

public interface IContact


{
string Name { get; set; }
string Email { get; set; }
}

La Interface permitirá representar los datos de un contacto. Todos los contactos deben tener
un nombre y un correo.

2. Agrega un nuevo archivo de clase llamado Person.cs en el directorio Models con el siguiente
código.

public class Person : IContact


{
public string Name { get; set; }
public string Email { get; set; }
public DateTime DateOfBirth { get; set; }
}

La clase Person representa un tipo de contacto con una fecha de nacimiento.

3. Agrega un nuevo archivo de clase llamado Organisation.cs en el directorio Models con el


siguiente código.

public class Organisation : IContact


{

151 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

public string Name { get; set; }


public string Email { get; set; }
public string Address { get; set; }
}

Una organización es otro tipo de contacto con una dirección.

4. Agrega un nuevo archivo de clase llamado ContactRequest.cs en el directorio Models con el


siguiente código.

public class ContactRequest


{
public IContact Contact { get; set; }
public string MessageToSend { get; set; }
}

La clase ContactRequest será utilizada para poder enviar un mensaje a un contacto.

5. Agrega un nuevo archivo de clase llamado PersonValidator.cs en el directorio Validators con


el siguiente código.

public class PersonValidator : AbstractValidator<Person>


{
public PersonValidator()
{
RuleFor(person => person.Name).NotEmpty();
RuleFor(person => person.Email).NotEmpty();
RuleFor(person => person.DateOfBirth).GreaterThan(DateTime.MinValue);
}
}

El código requerirá importar los espacios de nombres FluentValidationDemo.Models y


FluentValidation.

PersonValidator define 3 reglas de validación sobre la clase Person.

6. Agrega un nuevo archivo de clase llamado OrganisationValidator.cs en el directorio Validators


con el siguiente código.

public class OrganisationValidator : AbstractValidator<Organisation>


{
public OrganisationValidator()
{
RuleFor(organisation => organisation.Name).NotEmpty();
RuleFor(organisation => organisation.Email).NotEmpty();
RuleFor(Organisation => Organisation.Address).NotEmpty();
}
}

152 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

El código requerirá importar los espacios de nombres FluentValidationDemo.Models y


FluentValidation.

OrganisationValidator define 3 reglas de validación sobre la clase Organisation.

Ahora crearemos el validador para la clase ContactRequest. Podemos definir validadores


específicos para la propiedad Contact dependiendo del tipo de instancia que almacenen en
tiempo de ejecución. Haremos esto invocando el método SetInheritanceValidator pasando
una función que pueda ser utilizada para definir validadores hijos específicos.

7. Agrega un nuevo archivo de clase llamado ContactRequestValidator.cs en el directorio


Validators con el siguiente código.

public class ContactRequestValidator : AbstractValidator<ContactRequest>


{
public ContactRequestValidator()
{
RuleFor(ContactRequest => ContactRequest.Contact)
.SetInheritanceValidator(polymorphicValidator =>
{
polymorphicValidator.Add(new OrganisationValidator());
polymorphicValidator.Add(new PersonValidator());
});
}
}

El código requerirá importar los espacios de nombres FluentValidationDemo.Models y


FluentValidation.

El método Add también tiene sobrecargas que permiten la construcción de los validadores
hijos. El método también trabaja con colecciones donde cada elemento de la colección puede
ser de diferentes subclases.

8. Agrega el siguiente código en la clase ValidatorsController para ejemplificar la ejecución del


nuevo validador con el tipo de contacto Person.

[HttpGet("validate-inheritance-person")]
public async Task<IActionResult>ValidateInheritancePerson()
{
IActionResult Result;

ContactRequest Contact = new ContactRequest


{
Contact = new Person
{
Name = "Santiago Nazar",
Email = "snazar@outlook.com"
}
};

153 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

ContactRequestValidator Validator = new ContactRequestValidator();


ValidationResult ValidationResult =
await Validator.ValidateAsync(Contact);

if (ValidationResult.IsValid)
{
Result = Ok("Contacto válido!!!");
}
else
{
Result = BadRequest(ValidationResult.ToString("\n"));
}
return Result;
}
}

9. Ejecuta la aplicación y prueba la funcionalidad. Podrás ver un resultado similar al siguiente.

Puedes notar que el validador PersonValidator fue aplicado. La prueba falló debido a que no
especificamos una fecha de nacimiento para el tipo Person.

10. Agrega el siguiente código en la clase ValidatorsController para ejemplificar la ejecución del
nuevo validador con el tipo de contacto Organisation.

[HttpGet("validate-inheritance-organisation")]
public async Task<IActionResult> ValidateInheritanceOrganisation()
{
IActionResult Result;

ContactRequest Contact = new ContactRequest


{
Contact = new Organisation
{
Name = "NorthWind",
Email = "sales@northwind.com"
}
};

ContactRequestValidator Validator = new ContactRequestValidator();


ValidationResult ValidationResult =

154 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

await Validator.ValidateAsync(Contact);

if (ValidationResult.IsValid)
{
Result = Ok("Contacto válido!!!");
}
else
{
Result = BadRequest(ValidationResult.ToString("\n"));
}
return Result;
}

11. Ejecuta la aplicación y prueba la funcionalidad. Podrás ver un resultado similar al siguiente.

Puedes notar que el validador OrganisationValidator fue aplicado. La regla falló debido a que
no especificamos una dirección para el contacto Organisation.

Conjunto de reglas (RuleSets)


RuleSet nos permite agrupar reglas de validación que pueden ser ejecutadas juntas como un grupo
ignorando otras reglas. Veamos un ejemplo.

1. Agrega un nuevo archivo de clase llamado Client.cs en el directorio Models con el siguiente
código.

public class Client


{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}

Podemos crear un validador para la clase Client que establezca reglas para las 3 propiedades.

2. Agrega un nuevo archivo de clase llamado ClientValidator.cs en el directorio Validators con el


siguiente código.

public class ClientValidator : AbstractValidator<Client>

155 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

{
public ClientValidator()
{
RuleSet("Names", () =>
{
RuleFor(client => client.FirstName).NotEmpty();
RuleFor(client => client.LastName).NotEmpty();
});

RuleFor(client => client.Id).NotEqual(0);


}
}

El código requerirá importar los espacios de nombres FluentValidationDemo.Models y


FluentValidation.

Puedes notar que hemos agrupado las reglas para las propiedades FirstName y LastName en
un RuleSet llamado “Names”.

Ahora podríamos invocar únicamente las reglas del RuleSet proporcionando opciones
adicionales al método Validate.

3. Agrega el siguiente código en la clase ValidatorsController para ejemplificar la ejecución del


nuevo validador.

[HttpGet("validate-ruleset")]
public async Task<IActionResult> ValidateRuleSet()
{
IActionResult Result;

Client Client = new Client


{
FirstName = "Santiago", LastName = "Nazar"
};

ClientValidator Validator = new ClientValidator();


ValidationResult ValidationResult =
await Validator.ValidateAsync(
Client, options => options.IncludeRuleSets("Names")
);

if (ValidationResult.IsValid)
{
Result = Ok("Cliente válido!!!");
}
else
{
Result = BadRequest(ValidationResult.ToString("\n"));
}
return Result;
}

156 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Puedes notar que no estamos proporcionando el Id en la instancia de Client. Además, estamos


proporcionando como opciones del método ValidateAsync el nombre del RuleSet a través de
la opción IncludeRuleSets que acepta el nombre de uno o más RuleSet.

4. Ejecuta la aplicación y prueba la nueva funcionalidad. Podrás ver un resultado similar al


siguiente.

Puedes notar que la validación ha sido exitosa, aunque no hayamos proporcionado el Id del
cliente. La razón es que solo especificamos la validación de las reglas del conjunto llamado
“Names”.

RuleSet nos permite dividir definiciones de validación complejas en segmentos pequeños que
pueden ser ejecutados de forma aislada. Si invocamos el método Validate sin proporcionar un
RuleSet, entonces únicamente las reglas que no estén en el RuleSet serán ejecutadas.

5. Agrega el siguiente código en la clase ValidatorsController para ejemplificar la ejecución del


nuevo validador sin especificar un RuleSet.

[HttpGet("validate-without-ruleset")]
public async Task<IActionResult> ValidateWithoutRuleSet()
{
IActionResult Result;

Client Client = new Client


{
Id = 5
};

ClientValidator Validator = new ClientValidator();


ValidationResult ValidationResult =
await Validator.ValidateAsync(Client);

if (ValidationResult.IsValid)
{
Result = Ok("Cliente válido!!!");
}
else
{
Result = BadRequest(ValidationResult.ToString("\n"));
}
return Result;
}

157 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

6. Ejecuta la aplicación y prueba la nueva funcionalidad. Podrás ver un resultado similar al


siguiente.

Puedes notar que únicamente se está ejecutando la regla que no se encuentra en el RuleSet.

También es posible ejecutar tanto las reglas del RuleSet como las reglas no incluidas en él
mediante el uso del método IncludeRulesNotInRuleSet.

7. Agrega el siguiente código en la clase ValidatorsController para ejemplificar la ejecución del


validador especificando un RuleSet e incluir las reglas que no están incluidas en él.

[HttpGet("validate-include-all-rules")]
public async Task<IActionResult> ValidateIncludeAllRules()
{
IActionResult Result;

Client Client = new Client


{
Id = 5
};

ClientValidator Validator = new ClientValidator();


ValidationResult ValidationResult =
await Validator.ValidateAsync(Client, options =>
{
options.IncludeRuleSets("Names")
.IncludeRulesNotInRuleSet();
});

if (ValidationResult.IsValid)
{
Result = Ok("Cliente válido!!!");
}
else
{
Result = BadRequest(ValidationResult.ToString("\n"));
}
return Result;
}
}

8. Ejecuta la aplicación y prueba la funcionalidad. Podrás ver un resultado similar al siguiente.

158 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Puedes notar que se han ejecutado todas las reglas, las incluidas en el RuleSet y las no incluidas.

Otra forma de incluir las reglas fuera del RuleSet es especificando el nombre especial de
RuleSet “default” (no sensible a mayúsculas/minúsculas).

ValidationResult ValidationResult =
await Validator.ValidateAsync(Client, options =>
{
// Opción 1
/*
options.IncludeRuleSets("Names")
.IncludeRulesNotInRuleSet();
*/
// Opción 2
options.IncludeRuleSets("Names", "default");
});

No debemos crear un RuleSet propio llamado “default” ya que FluentValidation tratará esas
reglas como no parte de un RuleSet.

También es posible forzar a que se ejecuten todas las reglas independientemente de si se


encuentran en un RuleSet o no mediante la invocación de IncludeAllRuleSets.

ValidationResult ValidationResult =
await Validator.ValidateAsync(Client, options =>
{
options.IncludeAllRuleSets();
});

Incluyendo reglas
Podemos incluir reglas de otros validadores siempre y cuando sean validadores del mismo tipo. Esto
nos permite separar reglas a través de múltiples clases y posteriormente unirlas. Veamos un ejemplo.

1. Agrega un nuevo archivo de clase llamado PersonAgeValidator.cs en el directorio Validators


con el siguiente código.

public class PersonAgeValidator : AbstractValidator<Person>


{

159 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

public PersonAgeValidator()
{
RuleFor(person => person.DateOfBirth)
.Must(dateOfBirth =>
dateOfBirth > DateTime.MinValue &&
dateOfBirth.AddYears(18) <= DateTime.Now)
.WithMessage("La edad debe ser mayor o igual a 18 años.");
}
}

El código requerirá importar los espacios de nombres FluentValidationDemo.Models y


FluentValidation.

El validador PersonAgeValidator valida que la edad de una persona sea mayor o igual a 18.

2. Agrega un nuevo archivo de clase llamado PersonNameValidator.cs en el directorio Validators


con el siguiente código.

public class PersonNameValidator : AbstractValidator<Person>


{
public PersonNameValidator()
{
RuleFor(person => person.Name).NotEmpty();
}
}

El código requerirá importar los espacios de nombres FluentValidationDemo.Models y


FluentValidation.

El validador PersonNameValidator valida que la propiedad Name tenga un valor.

Debido a que ambos validadores validan el mismo tipo, en este caso Person, podemos
combinar ambas reglas utilizando Include.

3. Agrega un nuevo archivo de clase llamado PersonAgeAndNameValidator.cs en el directorio


Validators con el siguiente código.

public class PersonAgeAndNameValidator : AbstractValidator<Person>


{
public PersonAgeAndNameValidator()
{
Include(new PersonAgeValidator());
Include(new PersonNameValidator());
}
}

El código requerirá importar los espacios de nombres FluentValidationDemo.Models y


FluentValidation.

160 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

PersonAgeAndNameValidator incluye los dos validadores, aunque podría incluir reglas


adicionales sobre el mismo tipo Person. Solo es posible incluir validadores del mismo tipo raíz,
en este caso, Person.

4. Agrega el siguiente código en la clase ValidatorsController para ejemplificar las reglas incluidas.

[HttpGet("validate-including-rules")]
public async Task<IActionResult> ValidateIncludingRules()
{
IActionResult Result;

Person Person = new Person


{
};

PersonAgeAndNameValidator Validator = new PersonAgeAndNameValidator();


ValidationResult ValidationResult =
await Validator.ValidateAsync(Person);

if (ValidationResult.IsValid)
{
Result = Ok("Persona válida!!!");
}
else
{
Result = BadRequest(ValidationResult.ToString("\n"));
}
return Result;
}

5. Ejecuta la aplicación y prueba la funcionalidad. Podrás ver un resultado similar al siguiente.

Inyección de dependencias
Los validadores pueden ser utilizados con cualquier biblioteca de inyección de dependencias tal como
la biblioteca Microsoft.Extensions.DependencyInjection. Para inyectar un validador para un modelo
especifico, debemos registrar el validador con el proveedor de servicio como IValidator<T> donde T
es el tipo de objeto que será validado.

161 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

1. Agrega el siguiente código al final del método ConfigureServices del archivo Startup.cs para
registrar el validador PersonAgeAndNameValidator en el contenedor de inyección de
dependencias.

services.AddScoped<IValidator<Person>, PersonAgeAndNameValidator>();

El código requerirá importar los espacios de nombres FluentValidationDemo.Validators,


FluentValidationDemo.Models y FluentValidation.

Ahora ya podemos inyectar el validador tal y como lo haríamos con otra dependencia.

2. Agrega un nuevo archivo llamado PersonController.cs en el directorio Controllers utilizando la


plantilla API Controller – Empty.

3. Modifica el código de la clase PersonController para que sea similar al siguiente código.

public class PersonController : ControllerBase


{
readonly IValidator<Person> Validator;
public PersonController(IValidator<Person> validator) =>
Validator = validator;

[HttpGet("validate-person-from-di")]
public async Task<IActionResult> ValidatePersonFromDI()
{
IActionResult Result;

Person Person = new Person


{
};

ValidationResult ValidationResult =
await Validator.ValidateAsync(Person);

if (ValidationResult.IsValid)
{
Result = Ok("Persona válida!!!");
}
else
{
Result = BadRequest(ValidationResult.ToString("\n"));
}
return Result;
}

El código requerirá importar los espacios de nombres FluentValidationDemo.Models,


FluentValidation y FluentValidation.Results.

162 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

4. Ejecuta la aplicación y prueba la nueva funcionalidad. Podrás ver un resultado similar al


siguiente.

Puedes notar que la validación se ha realizado como era esperada.

Registro automático

Podemos hacer uso de los métodos de extensión contenidos en el paquete


FluentValidation.DependencyInjectionExtensions para encontrar todos los validadores derivados de
AbstractValidator contenidos en un ensamblado especifico.

1. Elimina o comenta el siguiente código del archivo Startup.cs.

services.AddScoped<IValidator<Person>, PersonAgeAndNameValidator>();

2. Agrega el siguiente código al final del método ConfigureServices del archivo Startup.cs.

services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());

El código requerirá importar el espacio de nombres System.Reflection.

El código agregado navegará sobre todos los tipos públicos en el assembly actual en ejecución
para encontrar todos los validadores no abstractos y los registrará con el proveedor de
servicios. De manera predeterminada, los validadores serán registrados como Scoped pero
podemos utilizar sobrecargas del método para especificar el ámbito de validez de los
validadores, como en el siguiente ejemplo.

services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly(),
ServiceLifetime.Transient);

3. Ejecuta la aplicación y prueba la funcionalidad GET /api/Person/validate-person-from-di.


Podrás ver un resultado similar al siguiente.

163 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Puedes notar que un validador para la clase Person fue ejecutado. Debido a que en nuestro
ejemplo existen varios validadores para la clase Person, el ultimo validador registrado ha sido
inyectado.

4. Agrega un nuevo archivo llamado PersonValidatorsController.cs en el directorio Controllers


utilizando la plantilla API Controller – Empty.

5. Modifica el código de la clase PersonValidatorsController para que sea similar al siguiente


código.

public class PersonValidatorsController : ControllerBase


{
readonly IEnumerable<IValidator<Person>> Validators;
public PersonValidatorsController(
IEnumerable<IValidator<Person>> validators) =>
Validators = validators;

[HttpGet("validate-person-validators")]
public async Task<IActionResult> ValidatePersonValidators()
{
Person Person = new Person
{
Name = "Santiago Nazar"
};

ValidationResult ValidationResult;
StringBuilder SB = new StringBuilder();
foreach (var Validator in Validators)
{
SB.AppendLine($"{Validator.GetType()}:");
SB.AppendLine("*************************");
ValidationResult =
await Validator.ValidateAsync(Person);
if (ValidationResult.IsValid)
{
SB.AppendLine("Persona válida!!!");
}
else
{
SB.AppendLine(ValidationResult.ToString("\n"));
}

164 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

SB.AppendLine("*************************");
}
return Ok(SB.ToString());
}
}

El código requerirá importar los espacios de nombres FluentValidationDemo.Models,


FluentValidation, FluentValidation.Results y System.Text.

El código itera y ejecuta cada uno de los validadores registrados para la clase Person.

6. Ejecuta la aplicación y prueba la nueva funcionalidad. Podrás ver un resultado similar al


siguiente.

Hasta aquí hemos visto una introducción a FluentValidation, lo esencial para entender mejor su
incorporación en el desarrollo de una arquitectura limpia combinada con otros patrones y bibliotecas
como CQRS o MediatR.

Para obtener más información sobre la biblioteca FluentValidation puede consultarse el


siguiente enlace:

FluentValidation
https://docs.fluentvalidation.net/en/latest/index.html

165 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

FluentValidation con MediatR


Veamos ahora como podemos integrar la biblioteca MediatR con la biblioteca FluentValidation para
implementar la validación de las peticiones.

Crear el proyecto inicial


Para ejemplificar la validación de peticiones MediatR utilizando la biblioteca FluentValidation
utilizaremos la solución CQRSDemoWithExceptions creada en la lección anterior. Recordemos que
tenemos un endpoint para crear un producto. Agregaremos ahora validación de los datos del producto
a crear.

1. Realiza una copia del directorio de la solución CQRSDemoWithExceptions creada en una


lección anterior y asígnale el nombre CQRSDemoWithValidation.

2. Renombra el archivo CQRSDemoWithExceptions.sln por CQRSDemoWithValidation.sln.

3. Abre Visual Studio bajo el contexto de Administrador.

4. Abre la solución CQRSDemoWithValidation.sln.

Instalar el paquete FluentValidation


1. Instala el paquete NuGet FluentValidation.DependencyInjectionExtensions al proyecto.

Crear el validador
1. Crea un nuevo directorio llamado Validators dentro del directorio Application\Products.

2. Agrega un nuevo archivo de clase llamado CreateProductCommandValidator.cs dentro del


directorio Validators con el siguiente código.

public class CreateProductCommandValidator :


AbstractValidator<CreateProductCommand>
{
public CreateProductCommandValidator()
{
RuleFor(product => product.Name).NotEmpty();
RuleFor(product => product.UnitPrice).GreaterThan(0);
RuleFor(product => product.UnitsInStock).GreaterThanOrEqualTo(0);
}
}

166 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

El código requerirá importar los espacios de nombres FluentValidation y


CQRSDemo.Application.Products.Commands.

El código del validador es simple. Únicamente estamos verificando que el nombre del producto
no esté vació, que el precio sea mayor que cero y que las unidades en existencia no sean
negativas.

Registrar el validador
1. Agrega el siguiente código al final del método ConfigureServices del archivo Startup.cs.

services.AddValidatorsFromAssembly(typeof(Startup).Assembly);

El código requerirá importar el espacio de nombres FluentValidation.

El código registra todos los validadores que se encuentran en el ensamblado que contiene al
tipo Startup.

Agregar el validador al comportamiento de canalización


1. Agrega un nuevo directorio llamado Common dentro del directorio Application.

2. Agrega un nuevo directorio llamado Behaviors dentro del directorio Application\Common.

3. Agrega un nuevo archivo de clase llamado ValidationBehavior.cs dentro del directorio


Behaviors con el siguiente código.

public class ValidationBehavior<RequestType, ResponseType> :


IPipelineBehavior<RequestType, ResponseType>
where RequestType : IRequest<ResponseType>
{
readonly IEnumerable<IValidator<RequestType>> Validators;

public ValidationBehavior(
IEnumerable<IValidator<RequestType>> validators) =>
Validators = validators;

public async Task<ResponseType> Handle(


RequestType request, CancellationToken token,
RequestHandlerDelegate<ResponseType> next)
{
if(Validators.Any())
{
var Context = new ValidationContext<RequestType>(request);

// Invocar a los validadores


var ValidationResults =
await Task.WhenAll(

167 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Validators
.Select(
validator => validator.ValidateAsync(Context, token)));

// Obtener las fallas de validación de cada validador


var Failures =
ValidationResults
.SelectMany(validationResult => validationResult.Errors)
.Where(failure => failure != null)
.ToList();

if(Failures.Count != 0)
{
// Lanzar una excepción si hay fallas.
throw new ValidationException(Failures);
}
}
return await next();
}
}

El código requerirá importar los espacios de nombres MediatR, FluentValidation y


System.Threading.

Este comportamiento de canalización genérico será capaz de procesar las validaciones de


cualquier tipo de petición, no solo las validaciones de la petición CreateProductCommand.

El constructor recibe una colección con los validadores asociados al tipo de petición
RequestType.

El método Handle se encarga de ejecutar los validadores en caso de que exista alguno.

ValidationContext es una clase de FluentValidation que es pasada al validador. El contexto


tiene varias propiedades incluyendo la referencia al objeto que será validado. Normalmente
esta clase es utilizada cuando deseamos pasar datos que los validadores necesitan para realizar
la validación.

Si se encuentran fallas de validación, se lanza una excepción de tipo ValidationException.

Registrar el comportamiento de canalización


1. Agrega el siguiente código al final del método ConfigureServices del archivo Startup.cs.

services.AddTransient(typeof(IPipelineBehavior<,>),
typeof(ValidationBehavior<,>));

El código requerirá importar CQRSDemo.Application.Common.Behaviors.

168 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

2. Ejecuta la aplicación y prueba la funcionalidad POST /api/Product sin proporcionar el nombre,


con precio cero y existencia negativa.

Podrás ver un resultado devolviendo una excepción similar a la siguiente.

3. Agrega un nuevo archivo de clase llamado ValidationExceptionHandler.cs en el directorio


ExceptionHandlers con el siguiente código.

public class ValidationExceptionHandler :


ExceptionHandlerBase, IExceptionHandler
{
public Task Handle(ExceptionContext context)
{
ValidationException Exception =
context.Exception as ValidationException;

StringBuilder Builder = new StringBuilder();


foreach (var Failure in Exception.Errors)
{
Builder.AppendLine(
string.Format("Propiedad: {0}. Error: {1}",
Failure.PropertyName, Failure.ErrorMessage));
}

return SetResult(context, StatusCodes.Status400BadRequest,


"Error en los datos de entrada.",
Builder.ToString());
}
}

169 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

El código requerirá importar los espacios de nombres Microsoft.AspNetCore.Mvc.Filters,


FluentValidation, System.Text y Microsoft.AspNetCore.Http.

4. Abre el archivo ExceptionHandlerBase.cs.

5. Modifica el diccionario RFC7231Types para incluir un nuevo URI.

readonly Dictionary<int, string> RFC7231Types =


new Dictionary<int, string>
{
{ StatusCodes.Status500InternalServerError,
"https://datatracker.ietf.org/doc/html/rfc7231#section-6.6.1"},
{StatusCodes.Status404NotFound,
"https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.4"},
{StatusCodes.Status400BadRequest,
"https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.1"}
};

6. Abre el archivo Startup.cs.

7. Agrega el nuevo tipo de manejador de excepción creado.

services.AddControllers(options=>
{
options.Filters.Add(new ApiExceptionFilterAttribute(
new Dictionary<Type, IExceptionHandler>
{
{typeof(EntityNotFoundException),
new EntityNotFoundExceptionHandler()},
{typeof(GeneralException),
new GeneralExceptionHandler()},
{typeof(ValidationException),
new ValidationExceptionHandler()}
}
));
});

8. Ejecuta la aplicación y prueba la funcionalidad POST /api/Product sin proporcionar el nombre,


con precio cero y existencia negativa.

Podrás ver un resultado similar al siguiente.

170 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Puedes notar que la validación se ha realizado como era esperado.

9. Prueba la funcionalidad POST /api/Product proporcionando el nombre, un precio mayor que


cero y una existencia 0.

Podrás ver un resultado similar al siguiente.

La validación se ha realizado correctamente.

171 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Lección 6:

El patrón Specification
Otro de los patrones comúnmente utilizados en arquitecturas orientadas al dominio, es el patrón de
diseño Specification.

En esta lección exploraremos el patrón de diseño Specification y la forma en que podemos utilizarlo
para implementar una arquitectura limpia.

Objetivos

Al finalizar esta lección, los participantes contarán con las habilidades y conocimientos para:

• Describir el patrón Specification.


• Implementar el patrón Specification en aplicaciones .NET.

172 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Introducción
El patrón Specification es un patrón de diseño de software que nos permite encapsular reglas de
negocio en una sola unidad: La Especificación (Specification). Una vez encapsuladas, las reglas de
negocio pueden ser reutilizadas en diferente partes del código y además pueden ser modificadas
fácilmente. El patrón Specification se utiliza con frecuencia en el contexto del diseño orientado al
dominio.

En el dominio, una Especificación (Specification) recibe una entidad como parámetro y nos dice si
satisface o no las reglas de negocio que encapsula. Las reglas de negocio no son más que una serie de
condiciones que ciertas entidades deben cumplir.

El patrón Specification es explicado detalladamente en el documento “Specifications”


elaborado por Eric Evans y Martin Fowler.

Specifications
https://martinfowler.com/apsupp/spec.pdf

173 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Entendiendo el patrón Specification


Ejemplifiquemos el patrón Specification a través de una aplicación Web API.

Crear un proyecto Web API


1. Abre Visual Studio bajo el contexto del Administrador.

2. Crea un nuevo proyecto llamado SpecificationDemo utilizando la plantilla ASP.NET Core Web
API y con soporte a OpenAPI.

Crear un modelo
1. Crea un nuevo directorio llamado Models en la raíz del proyecto.

2. Agrega un nuevo archivo de clase llamado Product.cs en el directorio Models con el siguiente
código para representar los datos de un producto.

public class Product


{
public int Id { get; set; }
public string Name { get; set; }
public DateTime CreatedDate { get; set; }
public int CategoryId { get; set; }
public decimal UnitPrice { get; set; }
public int UnitsInStock { get; set; }
public bool Discontinued { get; set; }
}

Crear un repositorio de datos


1. Agrega un nuevo archivo de clase llamado ProductRepository.cs en el directorio Models con
el siguiente código.

public class ProductRepository


{
IReadOnlyList<Product> Products = new List<Product>
{
// Lista de productos a consultar...
};

La clase ProductRepository simula un repositorio de datos contenidos en una colección de


productos de solo lectura.

174 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Implementar reglas de negocio


Un requerimiento de la aplicación puede ser el de ofrecer una forma de obtener un producto por su
Id.

1. Agrega el siguiente código en la clase ProductRepository.

public Product GetProductById(int id) =>


Products.FirstOrDefault(p => p.Id == id);

Otro requerimiento de la aplicación podría ser el de consultar los nuevos productos agregados
con base a una fecha.

2. Agrega el siguiente código en la clase ProductRepository.

public IReadOnlyList<Product> GetByCreatedDate(DateTime minCreatedDate)


{
return Products.Where(product =>
product.CreatedDate >= minCreatedDate).ToList();
}

El método GetByCreatedDate satisface el requerimiento de nuestra aplicación.

Si deseamos buscar productos por precio o por categoría, podríamos necesitar agregar otros
dos métodos.

3. Agrega el siguiente código al final de la clase ProductRepository.

public IReadOnlyList<Product> GetByUnitPrice(decimal minPrice)


{
return Products.Where(product =>
product.UnitPrice >= minPrice).ToList();
}

public IReadOnlyList<Product> GetByCategoryId(int categoryId)


{
return Products.Where(product =>
product.CategoryId == categoryId).ToList();
}

El código se volvería más complicado cuando necesitáramos combinar criterios de búsqueda.


En este caso, podríamos agregar un método adicional que maneje los distintos criterios y
devuelva el resultado solicitado.

4. Agrega el siguiente código al final de la clase ProductRepository.

public IReadOnlyList<Product> Find(


DateTime? minCreatedDate = null,
decimal minPrice = 0,

175 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

int categoryId = 0)
{
var Query = Products.Select(p=>p);

if(minCreatedDate.HasValue)
{
Query = Query.Where(p => p.CreatedDate >= minCreatedDate.Value);
}

if(minPrice > 0)
{
Query = Query.Where(p => p.UnitPrice >= minPrice);
}

if(categoryId > 0)
{
Query = Query.Where(p => p.CategoryId == categoryId);
}

return Query.ToList();
}

El método Find implementaría la lógica para determinar los criterios que hayan sido
especificados y devolver el resultado correcto. Si necesitáramos implementar otro criterio de
búsqueda tendríamos que agregar un nuevo método. Con esto podemos notar que estamos
violando el principio Open/Closed de SOLID.

Más problemas podrían presentarse cuando no solo necesitemos buscar datos en la base de
datos, sino que también deseemos validar datos en memoria. Por ejemplo, si deseamos validar
sin un producto puede ser vendido antes de autorizar la venta, podríamos necesitar crear el
código para implementar la lógica de validación.

5. Agrega un nuevo archivo llamado ProductController.cs en el directorio Controllers utilizando


la plantilla API Controller – Empty.

6. Agrega el siguiente código a la clase ProductController para validar si un producto puede ser
vendido.

[HttpGet("{id}")]
public IActionResult IsAvailableForSale(int id)
{
IActionResult Result;

// Obtener el producto del repositorio.


Product Product = new ProductRepository()
.GetProductById(id);

// Verificar que no esté discontinuado y


// que tenga existencia.
if(!Product.Discontinued && Product.UnitsInStock > 0)

176 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

{
// Puede ser vendido
Result = Ok();
}
else
{
// No puede ser vendido
Result = BadRequest();
}

return Result;
}

El código requerirá importar el espacio de nombres SpecificationDemo.Models.

El criterio que agregamos indica que un producto puede ser vendido solo si no está
discontinuado y además tiene una existencia mayor que cero. Por simplicidad, no estamos
considerando el caso cuando el producto no existe.

Si además necesitáramos obtener de la base de datos todos los productos que cumplan el
mismo criterio, necesitaríamos agregar un método con una lógica de validación similar en el
repositorio.

7. Agrega el siguiente código al final de la clase ProductRepository.

public IReadOnlyList<Product> FindAvailableForSale()


{
return Products.Where(product =>
!product.Discontinued && product.UnitsInStock > 0).ToList();
}

El problema con este código es que estaríamos violando el principio DRY ya que estaríamos
duplicando el código tanto en el método IsAvailableForSale como en el método
FindAvailableForSale.

Aquí es donde el patrón Specification puede ayudarnos. Podemos agregar una nueva clase que
sepa exactamente cuándo un producto puede ser vendido. Una vez creada la clase, podemos
reutilizarla en ambos escenarios.

8. Agrega un nuevo archivo de clase llamado ProductAvailableSpecification.cs en el directorio


Models con el siguiente código.

public class ProductAvailableSpecification


{
public bool IsSatisfiedBy(Product product)
{
return !product.Discontinued &&
product.UnitsInStock > 0;
}

177 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

9. Modifica el método IsAvailableForSale de la clase ProductController para utilizar la


especificación creada.

[HttpGet("{id}")]
public IActionResult IsAvailableForSale(int id)
{
IActionResult Result;

// Obtener el producto del repositorio.


Product Product = new ProductRepository()
.GetProductById(id);

// Verificar que no esté discontinuado y


// que tenga existencia.
var Specification = new ProductAvailableSpecification();
if(Specification.IsSatisfiedBy(Product))
{
// Puede ser vendido
Result = Ok();
}
else
{
// No puede ser vendido
Result = BadRequest();
}

return Result;
}

10. Modifica el código del método FindAvailableForSale de la clase ProductRepository para


reutilizar la especificación creada.

public IReadOnlyList<Product> FindAvailableForSale()


{
var Specification = new ProductAvailableSpecification();
return Products.Where(product =>
Specification.IsSatisfiedBy(product)).ToList();
}

Este enfoque elimina la duplicación de código de reglas de negocio y nos ayuda a crear
fácilmente consultas y validaciones.

Existen 2 escenarios principales donde el patrón Specification nos puede ser útil.

a) Búsqueda de datos en la base de datos que cumplan un criterio.


b) Validación de objetos en memoria.

178 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Probar la funcionalidad
1. Modifica el código de la propiedad Products de la clase ProductRepository para agregar
productos de prueba.

IReadOnlyList<Product> Products = new List<Product>


{
// Lista de productos a consultar...
new Product{Id = 1, Name = "Chai",
UnitsInStock = 1, Discontinued = false},
new Product{Id = 2, Name = "Chang",
UnitsInStock = 0, Discontinued = true},
new Product{Id = 3, Name = "Tofu",
UnitsInStock = 1, Discontinued = true},
new Product{Id = 4, Name = "Pavlova",
UnitsInStock = 0, Discontinued = false},
};

2. Agrega el siguiente código en la clase ProductController para devolver una lista de productos
disponibles para venta.

[HttpGet]
public IActionResult GetAvailable()
{
return Ok(new ProductRepository().FindAvailableForSale());
}

3. Ejecuta la aplicación y prueba la funcionalidad GET /api/Product/{id} proporcionando el valor


2 como Id a probar.

179 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Podrás ver un resultado similar al siguiente.

El producto con Id 2 no puede ser vendido debido a que está discontinuado y sin existencia.

4. Prueba nuevamente la funcionalidad proporcionado el valor 1 para el Id. Podrás ver un


resultado similar al siguiente.

El producto con Id 1 puede ser vendido.

5. Prueba la funcionalidad GET /api/Product. Podrás ver un resultado similar al siguiente.

6. El único producto que puede ser vendido es el producto con Id 1 ya que tiene existencia mayor
que cero y no está discontinuado.

180 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Implementar el patrón Specification utilizando Expresiones


Implementemos una forma sencilla del patrón Specification para mejorarlo posteriormente.

Si lo deseas, antes de realizar los siguientes pasos, realiza un respaldo del directorio de la solución y
nómbrala como SpecificationDemoV1.

Utilizando Expresiones
La primera solución que nos puede venir a la mente para dar solución a los requisitos planteados
anteriormente en nuestro ejemplo, puede ser el uso de Expresiones de C#. En gran medida, las
Expresiones mismas son una implementación del patrón Specification. Podemos definir fácilmente una
Expresión y usarla en ambos escenarios. Veamos una solución basada en Expresiones antes de
implementar el patrón Specification.

1. Agrega el siguiente código en el archivo ProductController.cs para ejemplificar una solución


que utiliza Expresiones de C# para determinar si un producto se encuentra disponible para la
venta.

[HttpGet("with- expression/{id}")]
public IActionResult IsAvailableForSaleWithExpressions(int id)
{
IActionResult Result;

// Obtener el producto del repositorio.


Product Product = new ProductRepository()
.GetProductById(id);

// Crear la Expresión
Expression<Func<Product, bool>> ConditionExpression = product =>
!product.Discontinued &&
product.UnitsInStock > 0;

// Ejecutar la expresión
bool IsAvailable = ConditionExpression.Compile().Invoke(Product);

if (IsAvailable)
{
// Puede ser vendido
Result = Ok();
}
else
{
// No puede ser vendido
Result = BadRequest();
}

return Result;
}

181 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

El código requerirá importar el espacio de nombres System.Linq.Expressions.

El código compila la expresión para obtener el delegado que será invocado para realizar la
evaluación de la condición.

El código en el repositorio que devuelve los productos disponibles para venta también podría
utilizar una Expression para verificar los datos del producto.

2. Agrega el siguiente código en el archivo ProductRepository.cs para crear un método que


devuelva los productos disponibles para venta utilizando una expresión

public IReadOnlyList<Product> FindAvailableForSale(


Expression<Func<Product, bool>> conditionExpression)
{
var ExpressionDelegate = conditionExpression.Compile();
return Products.Where(ExpressionDelegate).ToList();
}

El código requerirá importar el espacio de nombres System.Linq.Expressions.

3. Agrega el siguiente código en el controlador ProductController para ejemplificar la forma en


que invocaríamos al método FindAvailableForSale del repositorio pasándole la expresión.

[HttpGet("products-with-expression")]
public IActionResult GetAvailableWithExpression()
{
// Crear la Expresión
Expression<Func<Product, bool>> ConditionExpression = product =>
!product.Discontinued &&
product.UnitsInStock > 0;

return Ok(new ProductRepository()


.FindAvailableForSale(ConditionExpression));
}

4. Ejecuta la aplicación.

5. Prueba la funcionalidad GET /api/Product/with-expression/{id} proporcionando 1 como valor


del Id. Podrás ver un resultado similar al siguiente.

182 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

6. Prueba la funcionalidad GET /api/Product/products-with-expression. Podrás ver un resultado


similar al siguiente.

La solución mediante el uso de Expresiones funciona como era esperado. Sin embargo, con
este enfoque, la lógica de verificación es difícil de reutilizar y tiende a ser duplicada en
diferentes partes de la aplicación. En nuestro ejemplo duplicamos la expresión en los 2
métodos de acción. Volvemos a estar como al principio del problema.

Una variación de esta implementación simple es la creación de una clase de especificación


genérica.

7. Agrega un nuevo archivo de clase llamado GenericSpecification.cs en el directorio Models con


el siguiente código.

public class GenericSpecification<T>


{
public Expression<Func<T, bool>> Expression { get; }
public GenericSpecification(Expression<Func<T, bool>> expression) =>
Expression = expression;

public bool IsSatisfiedBy(T entity)


{
return Expression.Compile().Invoke(entity);
}
}

El código requerirá importar el espacio de nombres System.Linq.Expressions.

8. Modifica el método FindAvailableForSale del repositorio que recibe una expresión como
parámetro para que reciba ahora una especificación.

public IReadOnlyList<Product> FindAvailableForSale(


GenericSpecification<Product> specification)
{
var ExpressionDelegate = specification.Expression.Compile();

183 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

return Products.Where(ExpressionDelegate).ToList();
}

9. Modifica el método IsAvailableForSaleWithExpressions de la clase ProductController para


hacer uso de la especificación genérica.

[HttpGet("with-expression/{id}")]
public IActionResult IsAvailableForSaleWithExpressions(int id)
{
IActionResult Result;

// Obtener el producto del repositorio.


Product Product = new ProductRepository()
.GetProductById(id);

// Inicializar la especificación
var Specification = new GenericSpecification<Product>(product =>
!product.Discontinued && product.UnitsInStock > 0);

// Ejecutar la especificación
bool IsAvailable = Specification.IsSatisfiedBy(Product);

if (IsAvailable)
{
// Puede ser vendido
Result = Ok();
}
else
{
// No puede ser vendido
Result = BadRequest();
}

return Result;
}

10. Modifica el método GetAvailableWithExpression para hacer uso de la especificación genérica.

[HttpGet("products-with-expression")]
public IActionResult GetAvailableWithExpression()
{
// Inicializar la especificación
var Specification = new GenericSpecification<Product>(product =>
!product.Discontinued &&
product.UnitsInStock > 0);

return Ok(new ProductRepository()


.FindAvailableForSale(Specification));
}

11. Ejecuta la aplicación.

184 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

12. Prueba la funcionalidad GET /api/Product/with-expression/{id} proporcionando 1 como valor


del Id. Podrás ver un resultado similar al siguiente.

13. Prueba la funcionalidad GET /api/Product/products-with-expression. Podrás ver un resultado


similar al siguiente.

Esta versión tiene los mismos detalles que la versión con expresiones, la única diferencia es
que hemos encapsulado la expresión dentro de una clase. La lógica de verificación es difícil de
reutilizar y tiende a ser duplicada en diferentes partes de la aplicación. En nuestro ejemplo
duplicamos la expresión de verificación en los 2 métodos de acción y estamos violando el
principio DRY.

Las especificaciones genéricas son una mala práctica. Si una especificación nos permite indicar
una condición arbitraria, se convierte simplemente en un contenedor de la información que le
pasa su cliente y no resuelve el problema subyacente de la encapsulación de la lógica de
negocios. Estas especificaciones no contienen ningún conocimiento en sí mismas.

185 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Especificaciones fuertemente tipadas


Un especificación fuertemente tipada (Strongly-typed Specification) nos permite encapsular la lógica
de negocios que resuelve un problema específico, con poca o nula posibilidad de modificarla desde el
exterior.

Si lo deseas, antes de realizar los siguientes pasos, puedes realizar un respaldo del directorio de la
solución y nombrarla como SpecificationDemoV2.

1. Agrega un nuevo archivo de clase llamado Specification.cs en el directorio Models con el


siguiente código.

public abstract class Specification<T>


{
public abstract Expression<Func<T, bool>> Expression { get; }
public bool IsSatisfiedBy(T entity)
{
Func<T, bool> Predicate = Expression.Compile();
return Predicate(entity);
}
}

El código requerirá importar el espacio de nombres System.Linq.Expressions.

La clase Specification expone la propiedad Expression de solo lectura que deberá ser
implementada por las clases que la hereden. Las clases que la hereden podrán establecer la
lógica de negocios a través de esta propiedad.

2. Agrega un nuevo archivo de clase llamado ProductAvailableForSaleSpecification.cs en el


directorio Models con el siguiente código.

public class ProductAvailableForSaleSpecification : Specification<Product>


{
public override Expression<Func<Product, bool>> Expression =>
product => !product.Discontinued && product.UnitsInStock > 0;
}

El código requerirá importar el espacio de nombres System.Linq.Expressions.

Puedes notar que la clase únicamente establece el valor de la propiedad Expression con la
lógica de negocios para determinar si un producto está disponible para venta o no.

3. Agrega el siguiente código en el repositorio para devolver la lista de productos que cumplen
con la especificación recibida.

public IReadOnlyList<Product> FindAvailableForSale(


Specification<Product> specification)

186 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

{
var ExpressionDelegate = specification.Expression.Compile();
return Products.Where(ExpressionDelegate).ToList();
}

4. Agrega el siguiente código en el controlador ProductController para ejemplificar el uso de la


nueva especificación.

[HttpGet("with-specification/{id}")]
public IActionResult IsAvailableForSaleWithSpecification(int id)
{
IActionResult Result;

// Obtener el producto del repositorio.


Product Product = new ProductRepository()
.GetProductById(id);

// Inicializar la especificación
var Specification = new ProductAvailableForSaleSpecification();

// Ejecutar la especificación
bool IsAvailable = Specification.IsSatisfiedBy(Product);

if (IsAvailable)
{
// Puede ser vendido
Result = Ok();
}
else
{
// No puede ser vendido
Result = BadRequest();
}

return Result;
}

Puedes notar que ahora no tenemos lógica de negocios de verificación en el método.

5. Agrega el siguiente código en el controlador para obtener la lista de productos utilizando la


nueva especificación.

[HttpGet("products-with-specification")]
public IActionResult GetAvailableWithSpecification()
{
// Inicializar la especificación
var Specification = new ProductAvailableForSaleSpecification();

return Ok(new ProductRepository()


.FindAvailableForSale(Specification));
}

Puedes notar que ahora no tenemos lógica de negocios de verificación en el método.

187 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

6. Ejecuta la aplicación.

7. Prueba la funcionalidad GET /api/Product/with-specification/{id} proporcionando 1 como


valor del Id. Podrás ver un resultado similar al siguiente.

8. Prueba la funcionalidad GET /api/Product/products-with-specification. Podrás ver un


resultado similar al siguiente.

Con este enfoque, encapsulamos la lógica de negocios en una clase, lo que facilita su
reutilización. La creación de varias instancias de la especificación no conduce a la duplicación
de la lógica de negocios, por lo que podemos hacerlo libremente.

Combinando especificaciones
Con este enfoque también es fácilmente combinar las especificaciones utilizando métodos And, Or y
Not. Veamos un ejemplo.

1. Agrega un nuevo archivo de clase llamado AndSpecification.cs en el directorio Models con el


siguiente código.

public class AndSpecification<T> : Specification<T>


{
readonly Specification<T> Left;
readonly Specification<T> Right;
public AndSpecification(
Specification<T> left, Specification<T> right) =>

188 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

(Left, Right) = (left, right);

public override Expression<Func<T, bool>> Expression


{
get
{
var Param = System.Linq.Expressions.Expression
.Parameter(typeof(T));

var Body = System.Linq.Expressions.Expression


.AndAlso(System.Linq.Expressions.Expression
.Invoke(Left.Expression, Param),
System.Linq.Expressions.Expression
.Invoke(Right.Expression, Param));

return System.Linq.Expressions.Expression
.Lambda<Func<T, bool>>(Body, Param);
}
}

El código requerirá importar el espacio de nombres System.Linq.Expressions.

2. Agrega el siguiente código en la clase Specification<T> para implementar la funcionalidad And.

public Specification<T> And(Specification<T> specification) =>


new AndSpecification<T>(this, specification);

3. Agrega un nuevo archivo de clase llamado ProductWithStockSpecification.cs en el directorio


Models con el siguiente código.

public class ProductsWithStockSpecification : Specification<Product>


{
public override Expression<Func<Product, bool>> Expression =>
product => product.UnitsInStock > 0;
}

El código requerirá importar el espacio de nombres System.Linq.Expressions.

4. Agrega un nuevo archivo de clase llamado ProductActiveSpecification.cs en el directorio


Models con el siguiente código.

public class ProductActiveSpecification : Specification<Product>


{
public override Expression<Func<Product, bool>> Expression =>
product => !product.Discontinued;
}

El código requerirá importar el espacio de nombres System.Linq.Expressions.

189 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Con este ejemplo, hemos dividido las condiciones que nos permiten determinar si un producto
se encuentra disponible para venta.

5. Modifica el código de la clase ProductAvailableForSaleSpecification para que sea similar a lo


siguiente.

public class ProductAvailableForSaleSpecification : Specification<Product>


{
// Sin And
//public override Expression<Func<Product, bool>> Expression =>
// product => !product.Discontinued && product.UnitsInStock > 0;

public override Expression<Func<Product, bool>> Expression


{
get
{
var InStock = new ProductWithStockSpecification();
var NotDiscontinued = new ProductActiveSpecification();
return InStock.And(NotDiscontinued).Expression;
}
}

Puedes notar que estamos uniendo las dos especificaciones a través del método And.

6. Ejecuta la aplicación.

7. Prueba la funcionalidad GET /api/Product/with-specification/{id} proporcionando 1 como


valor del Id. Podrás ver un resultado similar al siguiente.

8. Prueba la funcionalidad GET /api/Product/products-with-specification. Podrás ver un


resultado similar al siguiente.

190 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Resumen
Hemos visto las ventajas que nos ofrece el patrón Specification al encapsular lógica de negocios que
puede ser fácilmente reutilizable o modificable.

Cuando utilizamos un repositorio de datos, una de las preguntas relacionadas con el patrón
Specification es que si no podemos desde el repositorio devolver simplemente un tipo IQueryable<T>
y dejar que los clientes realicen las consultas que quieran. Este enfoque tiene el mismo problema que
presenta nuestra implementación inicial genérica del patrón Specification, viola el principio DRY al
duplicar lógica de negocios. Tampoco debemos utilizar expresiones C# como una implementación del
patrón Specification ya que esto no evita la duplicación de código de lógica de negocios.

191 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Lección 7:

El patrón Repository
En esta lección describiremos el patrón Repository, un patrón de diseño que sirve de intermediario
entre el dominio y las capas de mapeo de datos utilizando una interfaz similar a una colección para
acceder a los objetos del dominio.

Objetivos

Al finalizar esta lección, los participantes contarán con las habilidades y conocimientos para:

• Describir el patrón Repository.


• Implementar el patrón Repository.

192 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Introducción
El patrón Repositorio (Repository) es un patrón de diseño que consiste en separar la lógica de negocios
de la lógica que recupera los datos y los asigna a un modelo de entidad (como Entity Framework Core
o Dapper). En otras palabras, un Repositorio media entre el dominio y las capas de mapeo de datos
actuando como una colección de objetos de dominio en memoria.

Un Repositorio es una clase que oculta la lógica necesaria para almacenar o recuperar datos. Por lo
tanto, a una aplicación no le importará qué tipo de ORM estemos usando ya que todo lo relacionado
con el ORM se maneja dentro de una capa de Repositorio. Esto nos permite tener una separación más
limpia de responsabilidades. El patrón Repository es uno de los patrones de diseño más utilizados para
crear soluciones más limpias.

Beneficios del patrón Repository

Reduce la duplicidad de las consultas

Para consultar información de una fuente de datos, necesitamos escribir código. Si esa consulta la
necesitamos hacer en diferentes partes de la aplicación, no sería lo ideal escribir ese mismo código
repetidamente. Al implementar un Repositorio, el código de acceso a datos podemos ponerlo en el
repositorio e invocarlo desde múltiples partes.

Desacoplamiento de la aplicación y la capa de acceso a datos

En la actualidad existen diversas tecnologías de acceso a datos, por ejemplo, Entity Framework Core es
uno de los ORMs más utilizados, pero probablemente en un futuro existirá algún otro framework
mejorado. Para mantenerse al día con las tecnologías en evolución y mantener nuestras soluciones
actualizadas, es muy importante crear aplicaciones que puedan cambiar a una nueva tecnología de
acceso a datos con un impacto mínimo en el código base de nuestra aplicación.

También es posible que podamos necesitar múltiples ORMs en una misma solución. Por ejemplo, para
mejorar el rendimiento de las operaciones de acceso a datos podemos utilizar Dapper para leer datos
y Entity Framework Core para escribirlos.

El patrón Repository nos ayuda a lograr este desacoplamiento entre la aplicación y la capa de acceso a
datos mediante la creación de una abstracción sobre la capa de acceso a datos.

Con un Repositorio no dependeríamos de Entity Framework Core o de cualquier otro ORM, Entity
Framework Core se volvería una opción más, no la única. Recordemos que, en una arquitectura limpia,
la arquitectura debe ser independiente de los frameworks.

193 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

¿En la actualidad, vale la pena implementar un Repositorio?

Esta es una pregunta frecuente que podemos encontrar entre los desarrolladores de aplicaciones .NET,
sobre todo cuando hablamos de Entity Framework Core. Entity Framework Core implementa el patrón
Repository y el patrón Unit of Work, por lo tanto, ¿por qué necesitaríamos agregar otra capa de
abstracción sobre Entity Framework Core? Microsoft recomienda la implementación del patrón
Repository en escenarios complejos para reducir el acoplamiento y proporcionar un mejor ambiente
de pruebas para nuestra solución. En casos donde queramos un código muy simple, podemos evitar la
implementación del patrón Repository.

Para obtener más información sobre el patrón Repository se pueden consultar los
siguientes enlaces:

Repository
https://martinfowler.com/eaaCatalog/repository.html

Using a custom repository versus using EF DbContext directly


https://docs.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-
patterns/infrastructure-persistence-layer-implementation-entity-framework-core#using-a-
custom-repository-versus-using-ef-dbcontext-directly

194 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Implementando el patrón Repository


En este ejercicio implementaremos una primera versión del patrón Repositorio y lo mejoraremos en
ejercicios posteriores de esta misma lección. El patrón Repository que implementaremos estará
implementado tendiendo en mente una arquitectura limpia.

Crear un proyecto Web API


1. Abre Visual Studio bajo el contexto del Administrador.

2. Crea un nuevo proyecto llamado RepositoryDemo utilizando la plantilla ASP.NET Core Web
API y con soporte a OpenAPI.

El proyecto Web API representa la capa de presentación de la solución.

Crear el proyecto para agregar las entidades e interfaces del dominio


Crearemos ahora una biblioteca de clases para agregar las entidades y las interfaces. Este proyecto no
dependerá de otros proyectos de la solución.

1. Agrega a la solución un nuevo proyecto llamado Domain utilizando la plantilla Class Library.

2. Elimina el archivo Class1.cs

Crear el proyecto para la implementación del Repositorio


Para implementar el patrón Repository, utilizaremos el enfoque Code-First de Entity Framework Core
que nos permitirá interactuar con la base de datos. Para tal fin, agregaremos una biblioteca de clases
que contenga únicamente el código relacionado con Entity Framework Core. Si posteriormente
deseáramos utilizar otro framework para implementar un Repositorio, por ejemplo, Dapper,
simplemente tendríamos que agregar un nuevo proyecto e implementar ahí el código
correspondiente.

1. Agrega a la solución un nuevo proyecto llamado Repository.EFCore utilizando la plantilla Class


Library.

2. Elimina el archivo Class1.cs.

Agregar las entidades


Agreguemos ahora las entidades requeridas para nuestro ejemplo.

1. Crea un nuevo directorio llamado Entities en el proyecto Domain.

195 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

2. Agrega un nuevo archivo de clase llamado Category.cs en el directorio Entities con el siguiente
código.

public class Category


{
public int Id { get; set; }
public string Name { get; set; }
}

La clase Category nos permitirá representar una categoría de producto para nuestro ejemplo.
Por ejemplo, Lácteos, Bebidas, Condimentos, etc.

3. Agrega un nuevo archivo de clase llamado Product.cs en el directorio Entities con el siguiente
código.

public class Product


{
public int Id { get; set; }
public string Name { get; set; }
public decimal UnitPrice { get; set; }
public int UnitsInStock { get; set; }
public bool Discontinued { get; set; }
public int CategoryId { get; set; }
public Category Category { get; set; }
}

La clase Product nos permitirá representar un producto para nuestro ejemplo. Puedes notar
que estamos definiendo una propiedad para almacenar el identificador de la categoría y una
propiedad para almacenar una instancia de la categoría misma.

4. Modifica el código de la clase Category para agregar una propiedad que nos permita almacenar
una lista de productos que pertenezcan a la categoría.

public class Category


{
public int Id { get; set; }
public string Name { get; set; }
public List<Product> Products { get; set; }
}

Instalar Entity Framework Core


Instalemos ahora Entity Framework Core en el proyecto Repository.EFCore. Aquí es donde crearemos
nuestra clase DbContext y la implementación de los repositorios.

1. Agrega el paquete NuGet Microsoft.EntityFrameworkCore.SqlServer en el proyecto


Repository.EFCore.

196 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Crear el contexto de datos


1. En el proyecto Repository.EFCore, agrega una referencia del proyecto Domain.

2. Agrega un nuevo archivo de clase llamado ApplicationContext.cs con el siguiente código en el


proyecto Repository.EFCore.

public class ApplicationContext : DbContext


{
public ApplicationContext(DbContextOptions<ApplicationContext> options)
: base(options) { }

public DbSet<Category> Categories { get; set; }


public DbSet<Product> Products { get; set; }
}

El código requerirá importar los espacios de nombres Microsoft.EntityFrameworkCore y


Domain.Entities.

Registrar el contexto de datos


Ahora que hemos creado nuestra capa de acceso a datos, registremos el contexto de datos con el
servicio de inyección de dependencias de la aplicación ASP.NET Core Web API.

Con el fin de lograr una independencia de los proyectos Web API y Repository.EFCore, agregaremos un
nuevo proyecto de biblioteca de clases que nos permita realizar la inversión de control donde el
proyecto Web API consumirá los datos sin saber qué servicio atiende sus peticiones, esto es, no sabrá
si la capa de acceso a datos utiliza Entity Framework Core. Esto nos ayudará a no agregar paquetes
relacionados con Entity Framework Core en el proyecto Web API.

1. Agrega un nuevo proyecto a la solución llamado Infrastructure.IoC utilizando la plantilla Class


Library.

2. Elimina el archivo Class1.cs.

3. Agrega el paquete NuGet Microsoft.Extensions.DependencyInjection al nuevo proyecto


creado.

197 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

4. En el mismo proyecto, agrega una referencia del proyecto Repository.EFCore.

5. En el mismo proyecto, agrega un nuevo archivo de clase llamado DependencyContainer.cs con


el siguiente código.

public static class DependencyContainer


{
public static IServiceCollection AddRepositories(
this IServiceCollection services, IConfiguration configuration)
{
services.AddDbContext<ApplicationContext>(options =>
options.UseSqlServer(
configuration.GetConnectionString("RepositoryDB")));
return services;
}
}

El código requerirá importar el espacio de nombres Microsoft.Extensions.Configuration, el


espacio de nombres Microsoft.EntityFrameworkCore, el espacio de nombres
Microsoft.Extensions.DependencyInjection y el espacio de nombres Repository.EFCore.

6. En el proyecto RepositoryDemo, agrega la referencia del proyecto Infrastructure.IoC.

7. Abre el archivo Startup.cs del proyecto RepositoryDemo.

8. Agrega el siguiente código al final del método ConfigureServices para registrar el contexto de
datos.

services.AddRepositories(Configuration);

El código requerirá importar el espacio de nombres Infrastructure.Ioc.

9. Abre el archivo appsettings.json del proyecto RepositoryDemo.

10. Modifica el contenido del archivo para definir la cadena de conexión.

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"RepositoryDB": "Server=(localdb)\\mssqllocaldb;database=RepositoryDB"
}

198 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Utilizaremos la instancia Microsoft SQL Server Express LocalDb como servidor de base de datos
y la base de datos RepositoryDB.

Crear la base de datos


El siguiente paso será crear las migraciones de Entity Framework Core para poder crear finalmente la
base de datos.

1. Agrega el paquete Microsoft.EntityFrameworkCore.Tools al proyecto Repository.EFCore. Este


paquete contiene las herramientas para crear las migraciones de Entity Framework Core y
actualizar el esquema de la base de datos.

Algunos de los comandos de las herramientas de EF Core (por ejemplo, los comandos para
trabajar con migraciones) requieren que una instancia derivada de DbContext sea creada en
tiempo de diseño para recopilar detalles sobre los tipos entidad de la aplicación y la forma en
que son mapeados con el esquema de base de datos. En la mayoría de los casos, es
conveniente que el DbContext sea creado en tiempo de diseño de forma similar a como sería
creado en tiempo de ejecución.

Hay varias maneras en las que las herramientas intentan crear DbContext: desde los servicios
de la aplicación en el caso de aplicaciones ASP.NET Core, desde un constructor sin parámetros
de la clase derivada de DbContext o a través de una clase generadora en tiempo de diseño.

Debido a que crearemos las migraciones desde el proyecto de biblioteca de clases y nuestro
tipo derivado de DbContext no contiene un constructor predeterminado (sin parámetros),
utilizaremos la técnica de una clase generadora en tiempo de diseño.

2. Agrega un nuevo archivo de clase llamado ApplicationContextFactory.cs en la raíz del proyecto


Repository.EFCore con el siguiente contenido.

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;

namespace Repository.EFCore
{
class ApplicationContextFactory :
IDesignTimeDbContextFactory<ApplicationContext>
{
public ApplicationContext CreateDbContext(string[] args)
{
var OptionsBuilder =

199 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

new DbContextOptionsBuilder<ApplicationContext>();
OptionsBuilder.UseSqlServer(
"Server=(localdb)\\mssqllocaldb;database=RepositoryDB");
return new ApplicationContext(OptionsBuilder.Options);
}
}
}

Mediante la implementación de la interface IDesignTimeDbContextFactory, indicamos a las


herramientas de Entity Framework Core la forma de crear el contexto de datos.

Para obtener más información sobre la creación del DbContext en tiempo de


diseño, se puede consultar el siguiente enlace:

Design-time DbContext Creation


https://docs.microsoft.com/en-us/ef/core/cli/dbcontext-creation?WT.mc_id=DT-MVP-
21053

3. Selecciona la opción Tools > NuGet Package Manager > Package Manager Console de la barra
de menús de Visual Studio para abrir la ventana de la consola de administración de paquetes
NuGet.

4. Escribe el siguiente comando para crear la migración inicial de Entity Framework Core.

add-migration InitialCreate -p Repository.EFCore -c ApplicationContext -o


Data/Migrations -s Repository.EFCore

La opción -p especifica el proyecto destino donde se crearán las migraciones (Default project).

La opción -c especifica la clase derivada de DbContext.

La opción -o especifica el directorio donde serán creados los archivos de la migración.

La opción -s especifica el proyecto (Startup Project) donde se encuentran las herramientas de


Entity Framework Core.

Para obtener más información sobre la herramientas de Entity Framework Core,


se puede consultar el siguiente enlace:

Entity Framework Core tools reference - .NET Core CLI


https://docs.microsoft.com/en-us/ef/core/cli/dotnet?WT.mc_id=DT-MVP-21053

5. Escribe el siguiente comando para crear la base de datos.

200 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

update-database -p Repository.EFCore -s Repository.EFCore

Crear un repositorio genérico


Tradicionalmente, podríamos invocar directamente al objeto DbContext para leer y escribir datos.
Cuando utilizamos DbContext directamente, hacemos que Entity Framework Core esté estrechamente
acoplado a la aplicación. Otra desventaja es que estaríamos exponiendo el DbContext, y esto es algo
inseguro.

Es aquí donde el patrón Repository es de gran utilidad.

Al realizar operaciones CRUD con Entity Framework Core, el código prácticamente es el mismo
independientemente de las clases entidad implicadas. Esto nos hace pensar que una solución
apropiada sería implementar un Repositorio genérico que pueda ser utilizado para realizar operaciones
CRUD con cualquier entidad. Por lo tanto, nuestra primera implementación del patrón Repository será
la de un repositorio genérico. Posteriormente, modificaremos nuestra implementación para orientarla
más a una arquitectura limpia orientada al dominio.

1. Agrega un nuevo directorio llamado Interfaces en el proyecto Domain.

2. Agrega un nuevo archivo de clase llamado IRepository.cs dentro del directorio Interfaces con
el siguiente código.

public interface IRepository<T> where T : class


{
void Add(T entity);
void Remove(T entity);
void Update(T entity);
T GetById(object id);
IEnumerable<T> GetAll();
IQueryable<T> Query();
}

IRepository es una interface genérica que puede ser utilizada para realizar operaciones CRUD
sobre las clases Category y Product. T sería a veces la clase Category y a veces la clase Product.

En nuestro ejemplo, IRepository define 6 funciones, pero podría exponer más funcionalidad
dependiendo de nuestras necesidades.

3. Agrega un nuevo directorio llamado Repositories en el proyecto Repository.EFCore.

4. Agrega un nuevo archivo de clase llamado Repository.cs en el directorio Repositories con el


siguiente código.

public class Repository<T> : IRepository<T> where T : class


{
private readonly ApplicationContext Context;

201 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

public Repository(ApplicationContext context) => Context = context;


public void Add(T entity)
{
Context.Set<T>().Add(entity);
}

public void Remove(T entity)


{
Context.Set<T>().Remove(entity);
}

public void Update(T entity)


{
Context.Set<T>().Update(entity);
}

public T GetById(object id)


{
return Context.Set<T>().Find(id);
}

public IEnumerable<T> GetAll()


{
return Context.Set<T>().ToList();
}
public IQueryable<T> Query()
{
return Context.Set<T>();
}
}

El código requerirá importar los espacios de nombres System.Linq.Expressions y


Domain.Interfaces.

La clase Repository implementa la interface IRepository y una instancia de ApplicationContext


se inyecta a través del constructor. De esta forma ocultaremos en las clases Repositorio
particulares las acciones relacionadas con DbContext.

Algo que también podemos notar es que las operaciones Add, Remove y Update no guardan
los cambios a la base de datos. Implementaremos el patrón Unit Of Work para guardar los
cambios en la base de datos.

Extender el repositorio genérico


Extendamos ahora el repositorio genérico para realizar operaciones propias de las entidades del
dominio.

1. Agrega un nuevo archivo de clase llamado IProductRepository.cs en el directorio Interfaces del


proyecto Domain con el siguiente código.

202 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

public interface IProductRepository : IRepository<Product>


{
void SetDiscontinued(int id);
IEnumerable<Product> GetDiscontinuedProducts();
void AddToCategoryFromProductNames(
Category category, IEnumerable<string> productNames);
}

El código requerirá importar el espacio de nombres Domain.Entities.

Además de poder realizar las operaciones CRUD con la entidad Product, también se podrá
establecer un producto como discontinuado, obtener la lista de productos discontinuados y se
podrán agregar varios productos a una categoría a partir de una lista de nombres de productos.

2. Agrega un nuevo archivo de clase llamado ProductRepository.cs en el directorio Repositories


del proyecto Repository.EFCore con el siguiente código.

public class ProductRepository : Repository<Product>, IProductRepository


{
public ProductRepository(ApplicationContext context)
: base(context) { }

public void SetDiscontinued(int id)


{
var Product = GetById(id);
if (Product != null)
{
Product.Discontinued = true;
}
}

public IEnumerable<Product> GetDiscontinuedProducts()


{
return Query().Where(p => p.Discontinued).ToList();
}

public void AddToCategoryFromProductNames(Category category,


IEnumerable<string> productNames)
{
foreach(var ProductName in productNames)
{
Add(new Product { Name = ProductName, Category = category });
}
}
}

El código requerirá importar los espacios de nombres Domain.Interfaces y Domain.Entities.

Puedes notar que el constructor de la clase recibe una instancia de ApplicationContext y la


envía al constructor de la clase base.

203 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

3. Agrega un nuevo archivo de clase llamado ICategoryRepository.cs en el directorio Interfaces


del proyecto Domain con el siguiente código.

public interface ICategoryRepository : IRepository<Category>


{
}

El código requerirá importar el espacio de nombres Domain.Entities.

La interface ICategoryRepository expone las operaciones CRUD expuestas por la interface


IRepository. Puedes notar que la interface no define miembros, pero está preparada para que
en un futuro podamos definir la funcionalidad adicional necesaria.

4. Agrega un nuevo archivo de clase llamado CategoryRepository.cs en el directorio Repositories


del proyecto Repository.EFCore con el siguiente código.

public class CategoryRepository : Repository<Category>, ICategoryRepository


{
public CategoryRepository(ApplicationContext context)
: base(context) { }
}

El código requerirá importar los espacios de nombres Domain.Interfaces y Domain.Entities.

5. Modifica el código del método AddRepositories del archivo DependencyContainer.cs del


proyecto Infrastructure.Ioc para registrar los servicios Repositorio.

public static IServiceCollection AddRepositories(


this IServiceCollection services, IConfiguration configuration)
{
services.AddDbContext<ApplicationContext>(options =>
options.UseSqlServer(
configuration.GetConnectionString("RepositoryDB")));

services.AddScoped<IProductRepository, ProductRepository>();
services.AddScoped<ICategoryRepository, CategoryRepository>();

return services;
}

El código requerirá importar los espacios de nombres Repository.EFCore.Repositories y


Domain.Interfaces.

Implementar el patrón Unit Of Work


Unit of Work es un patrón de diseño que nos permite mantener una lista de objetos afectados por una
transacción de negocio y coordinar el guardado de los cambios asi como la resolución de conflictos de
concurrencia.

204 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Unit of Work es responsable de confirmar los cambios en la fuente de datos, lo que garantiza una
transacción completa, sin pérdida de datos.

Supongamos que tenemos el requerimiento de insertar una nueva categoría con varios productos de
esa categoría. ¿Qué sucedería cuando insertáramos una nueva categoría, pero el repositorio de
productos fallara? En escenarios del mundo real, esto puede ser grave. Tendremos que asegurarnos
de que ambos repositorios funcionen bien antes de realizar cualquier cambio en la base de datos. Esta
es exactamente la razón por la que decidimos no incluir SaveChanges en ninguno de los repositorios.

1. Agrega un nuevo archivo de clase llamado IUnitOfWork.cs en el directorio Interfaces del


proyecto Domain con el siguiente código.

public interface IUnitOfWork


{
int SaveChanges();
Task<int> SaveChangesAsync();
}

La interface IUnitOfWork expone los métodos que nos permitirán guardar los datos en la base
de datos de forma síncrona o asíncrona.

2. Agrega un nuevo directorio llamado UnitOfWork en el proyecto Repository.EFCore.

3. Agrega un nuevo archivo de clase llamado UnitOfWork.cs en el directorio UnitOfWork con el


siguiente código.

public class UnitOfWork : IUnitOfWork


{
private readonly ApplicationContext Context;
public UnitOfWork(ApplicationContext context)
{
Context = context;
}
public int SaveChanges()
{
return Context.SaveChanges();
}
public Task<int> SaveChangesAsync()
{
return Context.SaveChangesAsync();
}
}

El código requerirá importar el espacio de nombres Domain.Interfaces.

Puedes notar que estamos recibiendo el contexto de datos a través del constructor de la clase.

205 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

La instancia del contexto de datos que los repositorios recibirán será la misma instancia que
UnitOfWork recibirá. Esto lo lograremos compartiendo la instancia que el servicio de inyección
de dependencias proporcionará.

El contenedor de servicios para inyección de dependencias se encargará de crear la instancia


de ApplicationContext y el mismo contenedor se encargará de invocar el método Dispose de
ApplicationContext cuando finalice la petición.

Para obtener más información sobre el servicio de inyección de dependencias de


ASP.NET Core pueden consultarse los siguientes enlaces:

Dependency injection in ASP.NET Core


https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-
injection?WT.mc_id=DT-MVP-21053

Entity Framework contexts


https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-
injection?WT.mc_id=DT-MVP-21053#entity-framework-contexts

Disposal of services
https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-
injection?WT.mc_id=DT-MVP-21053#disposal-of-services

4. Abre el archivo DependencyContainer.cs del proyecto Infrastructure.Ioc.

5. Modifica el método AddRepositories para registrar el servicio IUnitOfWork.

public static IServiceCollection AddRepositories(


this IServiceCollection services, IConfiguration configuration)
{
services.AddDbContext<ApplicationContext>(options =>
options.UseSqlServer(
configuration.GetConnectionString("RepositoryDB")));

services.AddScoped<IProductRepository, ProductRepository>();
services.AddScoped<ICategoryRepository, CategoryRepository>();

services.AddScoped<IUnitOfWork, UnitOfWork>();

return services;
}

El código requerirá importar el espacio de nombres Repository.EFCore.UnitOfWork.

206 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Probar la funcionalidad
Agreguemos ahora un par de controladores al proyecto Web API para probar la funcionalidad de los
repositorios que hemos creado. Por simplicidad, para centrarnos en el uso de los repositorios,
agregaremos la lógica de su uso directamente en los controladores sin utilizar una capa de servicio
adicional (por ejemplo, CQRS).

1. Agrega un nuevo archivo llamado ProductController.cs utilizando la plantilla API Controller –


Empty en el directorio Controllers del proyecto RepositoryDemo.

2. Agrega el siguiente código al controlador para recibir los servicios a través del constructor de
la clase.

readonly IProductRepository ProductRepository;


readonly ICategoryRepository CategoryRepository;
readonly IUnitOfWork UnitOfWork;

public ProductController(IProductRepository productRepository,


ICategoryRepository categoryRepository, IUnitOfWork unitOfWork) =>
(ProductRepository, CategoryRepository, UnitOfWork) =
(productRepository, categoryRepository, unitOfWork);

El código requerirá importar el espacio de nombres Domain.Interfaces.

3. Agrega el siguiente código que nos permitirá agregar una categoría de productos con una lista
de productos de esa categoría.

[HttpPost]
public IActionResult AddProductsWithCategory()
{
Category Category = new Category
{
Name = "Bebidas"
};

CategoryRepository.Add(Category);
ProductRepository.AddToCategoryFromProductNames(
Category, new List<string> { "Jugo", "Leche", "Cerveza" });
var Result = UnitOfWork.SaveChanges();
return Ok($"{Result} registros agregados");
}

El código requerirá importar el espacio de nombres Domain.Entities.

Puedes notar que después de agregar los datos a través de los repositorios, invocamos el
método SaveChanges del objeto UnitOfWork para persistir los datos a la base de datos.

4. Agrega el siguiente código que nos permitirá establecer un producto como discontinuado.

207 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

[HttpPatch("{id}")]
public IActionResult SetDiscontinued(int id)
{
ProductRepository.SetDiscontinued(id);
var Result = UnitOfWork.SaveChanges();
return Ok($"{Result} registro afectados.");
}

5. Agrega el siguiente código que nos permitirá devolver la lista de productos discontinuados.

[HttpGet("discontinued")]
public IActionResult GetDiscontinued()
{
return Ok(ProductRepository.GetDiscontinuedProducts());
}

6. Agrega el siguiente código que nos permitirá devolver la lista de todos los productos.

[HttpGet("get-all-products")]
public IActionResult GetAll()
{
return Ok(ProductRepository.GetAll());
}

7. Agrega un nuevo archivo llamado CategoryController.cs utilizando la plantilla API Controller –


Empty en el directorio Controllers del proyecto RepositoryDemo.

8. Agrega el siguiente código al controlador para recibir el servicio a través del constructor de la
clase.

readonly ICategoryRepository CategoryRepository;


public CategoryController(ICategoryRepository categoryRepository) =>
CategoryRepository = categoryRepository;

El código requerirá importar el espacio de nombres Domain.Interfaces.

9. Agrega el siguiente código para devolver el listado de todas las categorías.

[HttpGet("get-all-categories")]
public IActionResult GetAll()
{
return Ok(CategoryRepository.GetAll());
}

Es momento ahora de probar la funcionalidad.

10. Ejecuta la aplicación.

11. Prueba la funcionalidad POST /api/Product. Podrás ver un resultado similar al siguiente.

208 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Se agregó una categoría y 3 productos.

12. Prueba la funcionalidad GET /api/Category/get-all-categories para verificar que se haya


agregado la categoría. Podrás ver un resultado similar al siguiente.

13. Prueba la funcionalidad GET /api/Product/get-all-products para verificar que se hayan


agregado los 3 productos. Podrás ver un resultado similar al siguiente.

209 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Puedes notar que los productos tienen asignado el valor de la categoría.

14. Prueba la funcionalidad PATCH /api/Product/{id} proporcionado el Id de un producto


existente. Podrás ver un resultado similar al siguiente.

15. Prueba la funcionalidad GET /api/Product/Discontinued para verificar que el producto se haya
establecido como discontinuado. Podrás ver un resultado similar al siguiente.

210 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

El Repositorio genérico que hemos implementado se ve bastante útil ya que con un pequeño código
obtenemos toda la funcionalidad de acceso a datos. Ahora no solo podemos crear, modificar, eliminar
y obtener cualquier entidad, sino que también podemos realizar todo tipo de consultas utilizando
IQueryable.

211 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Implementar un repositorio orientado al dominio


A pesar de las ventajas que hemos obtenido con la implementación de un repositorio genérico,
podemos notar algunas características que harían que nuestro repositorio viole algunos principios:

• Al heredar del repositorio genérico, las clases Repositorio expondrían toda la funcionalidad
para realizar operaciones CRUD, aunque un requerimiento fuera que un repositorio solo
pudiera realizar consultas o solo agregar registros a la base de datos y no eliminar o modificar.

• La capacidad de construir cualquier tipo de consulta hace que la responsabilidad del acceso a
los datos se filtre fuera de la capa de acceso a los datos.

En un diseño orientado al dominio como es el caso de una arquitectura limpia, un repositorio no


debería ser simplemente un objeto de acceso a datos que implemente todas las operaciones CRUD de
una entidad. Un repositorio solo debe exponer un conjunto mínimo de operaciones requeridas para
satisfacer los requerimientos de negocio.

Un repositorio debería reflejar conceptos de negocio. Esto significa que los métodos de un repositorio
deberían seguir el lenguaje de dominio y no el lenguaje de conceptos de bases de datos.

Por ejemplo, si queremos consultar los productos disponibles para venta donde en términos de
requerimientos de negocio son aquellos productos no discontinuados y con unidades en existencia
mayor que cero, debemos tener un método llamado GetProductsAvailableForSale en lugar de tener
un método llamado GetProductsWithStockAndNotDiscontinued. Si los requerimientos cambiaran
agregando o quitando una condición, el nombre del método ya no reflejaría el concepto de un
producto disponible para venta.

Hagamos algunos cambios a nuestra implementación de repositorio para adecuarlo al diseño


orientado al dominio.

Si lo deseas, realiza un respaldo de la solución, por ejemplo, RepositoryDemoV1.

1. Elimina el archivo Interfaces\IRepository.cs del proyecto Domain. Debido a que actualmente


la interface define métodos del repositorio genérico y vamos a refactorizar el repositorio
genérico, esta interface ya no tiene sentido y tampoco su implementación.

2. Elimina el archivo Repositories\Repository.cs del proyecto Repository.EFCore.

3. Abre el archivo Interfaces\IProductRepository.cs del proyecto Domain.

4. Modifica el código de la interface IProductRepository para eliminar la especificación de


implementación de la interface IRepository y definir el método GetAll.

public interface IProductRepository


{

212 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

void SetDiscontinued(int id);


IEnumerable<Product> GetDiscontinuedProducts();
void AddToCategoryFromProductNames(
Category category, IEnumerable<string> productNames);
IEnumerable<Product> GetAll();
}

5. Abre el archivo Interfaces\ICategoryRepository.cs del proyecto Domain.

6. Modifica el código de la interface ICategoryRepository para eliminar la especificación de


implementación de la interface IRepository y definir los métodos Add y GetAll.

public interface ICategoryRepository


{
void Add(Category entity);
IEnumerable<Category> GetAll();
}

Ahora los repositorios exponen únicamente un conjunto mínimo de operaciones requeridas


para satisfacer los requerimientos de negocio.

7. Abre el archivo Repositories\ProductRepository.cs del proyecto Repository.EFCore.

8. Modifica el código de la clase ProductRepository para implementar la nueva interface


IProductRepository.

public class ProductRepository : IProductRepository


{
readonly ApplicationContext Context;
public ProductRepository(ApplicationContext context) =>
Context = context;

public void SetDiscontinued(int id)


{
var Product = Context.Products.Find(id);
if (Product != null)
{
Product.Discontinued = true;
}
}

public IEnumerable<Product> GetDiscontinuedProducts()


{
return Context.Products.Where(p => p.Discontinued).ToList();
}

public void AddToCategoryFromProductNames(Category category,


IEnumerable<string> productNames)
{
foreach(var ProductName in productNames)
{

213 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Context.Add(new Product { Name = ProductName, Category = category


});
}
}

public IEnumerable<Product> GetAll() =>


Context.Products.ToList();
}

Puedes notar que el repositorio recibe la instancia de ApplicationContext para realizar las
operaciones requeridas.

9. Abre el archivo Repositories\CategoryRepository.cs del proyecto Repository.EFCore.

10. Modifica el código de la clase CategoryRepository para implementar la nueva interface


ICategoryRepository.

public class CategoryRepository : ICategoryRepository


{
readonly ApplicationContext Context;
public CategoryRepository(ApplicationContext context) =>
Context = context;

public void Add(Category category) =>


Context.Add(category);

public IEnumerable<Category> GetAll() =>


Context.Categories.ToList();
}

11. Ejecuta la aplicación y verifica que todo funcione como antes.

Ahora ya tenemos implementaciones del patrón Repository orientados al dominio.

214 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Mejorar el patrón Repository con el patrón Specification


Actualmente el repositorio ProductRepository nos permite obtener una lista de los productos
discontinuados. La lógica de negocios considera que “Un producto se considera discontinuado cuándo
el valor de su propiedad Discontinued es true.”.

Una lógica de negocios relacionada con los productos que pueden ser vendidos puede ser que “Un
producto puede ser vendido cuando no esté discontinuado y haya existencia de ese producto en
almacén.”

Veamos como el patrón Specification podría ser utilizado para ayudar al repositorio a implementar
esas lógicas de negocios y las que surjan en un futuro.

Si lo deseas, realiza un respaldo de la solución, por ejemplo, RepositoryDemoV2.

1. Crea un nuevo directorio llamado Specifications en el proyecto Domain.

2. Agrega un nuevo archivo de clase llamado Specification.cs en el directorio Specifications con


el siguiente código.

public abstract class Specification<T>


{
public abstract Expression<Func<T, bool>> Expression { get; }
public bool IsSatisfiedBy(T entity)
{
Func<T, bool> ExpressionDelegate = Expression.Compile();
return ExpressionDelegate(entity);
}

public Specification<T> And(Specification<T> specification) =>


new AndSpecification<T>(this, specification);

public Specification<T> Not() =>


new NotSpecification<T>(this);
}

El código requerirá importar el espacio de nombres System.Linq.Expressions.

La clase Specification es la clase base que heredarán las especificaciones que crearemos.
Puedes notar que se trata de una especificación similar a la que creamos en una lección
anterior.

3. Agrega un nuevo archivo de clase llamado AndSpecification.cs en el directorio Specifications


con el siguiente código.

public class AndSpecification<T> : Specification<T>


{
readonly Specification<T> Left;
readonly Specification<T> Right;

215 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

public AndSpecification(
Specification<T> left, Specification<T> right) =>
(Left, Right) = (left, right);

public override Expression<Func<T, bool>> Expression


{
get
{
var Param = System.Linq.Expressions.Expression
.Parameter(typeof(T));

var Body = System.Linq.Expressions.Expression


.AndAlso(System.Linq.Expressions.Expression
.Invoke(Left.Expression, Param),
System.Linq.Expressions.Expression
.Invoke(Right.Expression, Param));

return System.Linq.Expressions.Expression
.Lambda<Func<T, bool>>(Body, Param);
}
}
}

El código requerirá importar el espacio de nombres System.Linq.Expressions.

4. Agrega un nuevo archivo de clase llamado NotSpecification.cs en el directorio Specifications


con el siguiente código.

public class NotSpecification<T> : Specification<T>


{
readonly Specification<T> Specification;
public NotSpecification(
Specification<T> specification) =>
Specification = specification;

public override Expression<Func<T, bool>> Expression


{
get
{
var Param = System.Linq.Expressions.Expression
.Parameter(typeof(T));

var Body = System.Linq.Expressions.Expression


.Not(System.Linq.Expressions.Expression
.Invoke(Specification.Expression, Param));

return System.Linq.Expressions.Expression
.Lambda<Func<T, bool>>(Body, Param);
}
}
}

El código requerirá importar el espacio de nombres System.Linq.Expressions.

216 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

5. Modifica el código de la interface IProductRepository para definir un método que permita


realizar consultas tomando como parámetro una especificación.

public interface IProductRepository


{
void SetDiscontinued(int id);
void AddToCategoryFromProductNames(
Category category, IEnumerable<string> productNames);
IEnumerable<Product> GetAll();

IEnumerable<Product> GetProductsBySpecification(
Specification<Product> specification);
}

El código requerirá importar el espacio de nombres Domain.Specifications.

Puedes notar que hemos eliminado el método GetDiscontinuedProducts. El nuevo método


agregado nos permitirá implementar esa funcionalidad a través de una especificación.

6. Modifica el código de la clase ProductRepository para implementar la nueva interface


IProductRepository.

public class ProductRepository : IProductRepository


{
readonly ApplicationContext Context;
public ProductRepository(ApplicationContext context) =>
Context = context;

public void SetDiscontinued(int id)


{
var Product = Context.Products.Find(id);
if (Product != null)
{
Product.Discontinued = true;
}
}

public void AddToCategoryFromProductNames(Category category,


IEnumerable<string> productNames)
{
foreach(var ProductName in productNames)
{
Context.Add(
new Product { Name = ProductName, Category = category });
}
}

public IEnumerable<Product> GetAll() =>


Context.Products.ToList();

public IEnumerable<Product> GetProductsBySpecification(


Specification<Product> specification)
{

217 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

var ExpressionDelegate = specification.Expression.Compile();


return Context.Products.Where(ExpressionDelegate).ToList();
}
}

El código requerirá importar el espacio de nombres Domain.Specifications.

7. Agrega un nuevo archivo de clase llamado ProductDiscontinuedSpecification.cs en el


directorio Specifications con el siguiente código.

public class ProductDiscontinuedSpecification : Specification<Product>


{
public override Expression<Func<Product, bool>> Expression =>
product => product.Discontinued;
}

El código requerirá importar los espacios de nombres System.Linq.Expressions y


Domain.Entities.

8. Agrega un nuevo archivo de clase llamado ProductWithUnitsInStockSpecification.cs en el


directorio Specifications con el siguiente código.

public class ProductWithUnitsInStockSpecification : Specification<Product>


{
public override Expression<Func<Product, bool>> Expression =>
product => product.UnitsInStock > 0;
}

El código requerirá importar los espacios de nombres System.Linq.Expressions y


Domain.Entities.

9. Agrega un nuevo archivo de clase llamado ProductAvailableForSaleSpecification.cs en el


directorio Specifications con el siguiente código.

public class ProductAvailableForSaleSpecification : Specification<Product>


{
public override Expression<Func<Product, bool>> Expression
{
get
{
var InStock =
new ProductWithUnitsInStockSpecification();
var NotDiscontinued =
new ProductDiscontinuedSpecification().Not();
return InStock.And(NotDiscontinued).Expression;
}
}
}

El código requerirá importar los espacios de nombres System.Linq.Expressions y


Domain.Entities.

218 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

10. Modifica el método GetDiscontinued del controlador ProductController para utilizar ahora la
especificación que creamos.

[HttpGet("discontinued")]
public IActionResult GetDiscontinued()
{
return Ok(ProductRepository
.GetProductsBySpecification(
new ProductDiscontinuedSpecification()));
}

El código requerirá importar el espacio de nombres Domain.Specifications.

11. Agrega el siguiente código en la clase ProductController para obtener la lista de productos
disponibles para venta.

[HttpGet("get-available-for-sale")]
public IActionResult GetAvailableForSale()
{
return Ok(ProductRepository
.GetProductsBySpecification(
new ProductAvailableForSaleSpecification()));
}

12. Ejecuta la aplicación.

13. Prueba la funcionalidad GET /api/Product/discontinued. El resultado devolverá los productos


que se encuentren discontinuados.

14. Prueba la funcionalidad GET /api/Product/get-available-for-sale. El resultado no devolverá


registros ya que todos los productos fueron agregados con existencia 0.

15. Regresa a Visual Studio y detén la ejecución.

16. Modifica la clase ProductWithUnitsInStockSpecification para considerar la existencia 0 como


válida.

public class ProductWithUnitsInStockSpecification : Specification<Product>


{
public override Expression<Func<Product, bool>> Expression =>
product => product.UnitsInStock >= 0;
}

17. Ejecuta la aplicación y prueba la funcionalidad GET /api/Product/get-available-for-sale. El


resultado devolverá todos los productos que no estén discontinuados (Discontinued = false).

219 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Implementar el patrón CQRS


Veamos ahora como combinar CQRS junto a las especificaciones y a los repositorios.

Crear la estructura de directorios


1. Agrega un nuevo proyecto llamado Application utilizando la plantilla Class Library.

2. Elimina el archivo Class1.cs.

3. Agrega la referencia del proyecto Domain al proyecto Application.

Construyamos ahora la estructura de directorios para implementar CQRS.

4. Agrega un nuevo directorio llamado Products en el proyecto Application.

5. Agrega un nuevo directorio llamado Commands dentro del directorio Products.

6. Agrega un nuevo directorio llamado Queries dentro del directorio Products.

7. Agrega un nuevo directorio llamado Handlers dentro del directorio Products.

8. Agrega un nuevo directorio llamado Categories en el proyecto Application.

9. Agrega un nuevo directorio llamado Commands dentro del directorio Categories.

10. Agrega un nuevo directorio llamado Queries dentro del directorio Categories.

11. Agrega un nuevo directorio llamado Handlers dentro del directorio Categories.

Instalar MediatR
1. Agrega el paquete NuGet MediatR.Extensions.Microsoft.DependencyInjection al proyecto
Application.

Crear los comandos, consultas y manejadores


1. Agrega un nuevo archivo llamado AddProductsToCategoryFromProductNamesCommand.cs
en el directorio Products\Commands del proyecto Application con el siguiente código.

public class AddProductsToCategoryFromProductNamesCommand : IRequest


{
public string CategoryName { get; set; }
public IEnumerable<string> ProductNames { get; set; }
}

El código requerirá importar el espacio de nombres MediatR.


220 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

2. Agrega un archivo llamado AddProductsToCategoryFromProductNamesCommandHandler.cs


en el directorio Products\Handlers del proyecto Application con el siguiente código.

public class AddProductsToCategoryFromProductNamesCommandHandler :


AsyncRequestHandler<AddProductsToCategoryFromProductNamesCommand>
{
readonly IProductRepository ProductRepository;
readonly ICategoryRepository CategoryRepository;
readonly IUnitOfWork UnitOfWork;

public AddProductsToCategoryFromProductNamesCommandHandler(
IProductRepository productRepository,
ICategoryRepository categoryRepository,
IUnitOfWork unitOfWork) =>
(ProductRepository, CategoryRepository, UnitOfWork) =
(productRepository, categoryRepository, unitOfWork);

protected async override Task Handle(


AddProductsToCategoryFromProductNamesCommand command,
CancellationToken cancellationToken)
{
Category Category = new Category
{
Name = command.CategoryName
};

CategoryRepository.Add(Category);
ProductRepository.AddToCategoryFromProductNames(Category,
command.ProductNames);
await UnitOfWork.SaveChangesAsync();
}
}

El código requerirá importar los espacios de nombres Domain.Interfaces, Domain.Entities,


MediatR y Application.Products.Commands.

Puedes notar que estamos recibiendo instancias de los repositorios y de UnitOfWork en el


constructor de la clase.

Por simplicidad, no estamos considerando el caso donde no se puedan guardar los cambios,
por ejemplo, para lanzar una excepción personalizada y atraparla con un manejador global de
excepciones.

3. Agrega un nuevo archivo de clase llamado SetDiscontinuedProductCommand.cs en el


directorio Products\Commands del proyecto Application con el siguiente código.

public class SetDiscontinuedProductCommand : IRequest


{
public int ProductId { get; set; }
}

221 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

El código requerirá importar el espacio de nombres MediatR.

4. Agrega un archivo llamado SetDiscontinuedProductCommandHandler.cs en el directorio


Products\Handlers del proyecto Application con el siguiente código.

public class SetDiscontinuedProductCommandHandler :


AsyncRequestHandler<SetDiscontinuedProductCommand>
{
readonly IProductRepository ProductRepository;
readonly IUnitOfWork UnitOfWork;
public SetDiscontinuedProductCommandHandler(
IProductRepository productRepository, IUnitOfWork unitOfWork) =>
(ProductRepository, UnitOfWork) = (productRepository, unitOfWork);

protected async override Task Handle(SetDiscontinuedProductCommand command,


CancellationToken cancellationToken)
{
ProductRepository.SetDiscontinued(command.ProductId);
await UnitOfWork.SaveChangesAsync();
}
}

El código requerirá importar los espacios de nombres Domain.Interfaces, MediatR y


Application.Products.Commands.

Puedes notar que estamos recibiendo una instancia de IProductRepository y de UnitOfWork


en el constructor de la clase.

Por simplicidad, no estamos considerando el caso donde no se puedan guardar los cambios,
por ejemplo, para lanzar una excepción personalizada y atraparla con un manejador global de
excepciones.

5. Agrega un nuevo archivo de clase llamado GetDiscontinuedProductsQuery.cs en el directorio


Products\Queries del proyecto Application con el siguiente código.

public class GetDiscontinuedProductsQuery : IRequest<List<Product>>


{
}

El código requerirá importar los espacios de nombres MediatR y Domain.Entities.

6. Agrega un nuevo archivo de clase llamado GetDiscontinuedProductsQueryHandler.cs en el


directorio Products\Handlers del proyecto Application con el siguiente código.

public class GetDiscontinuedProductsQueryHandler :


IRequestHandler<GetDiscontinuedProductsQuery, List<Product>>
{
readonly IProductRepository ProductRepository;

public GetDiscontinuedProductsQueryHandler(

222 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

IProductRepository productRepository) =>


ProductRepository = productRepository;

public Task<List<Product>> Handle(GetDiscontinuedProductsQuery query,


CancellationToken cancellationToken)
{
var DiscontinuedProducts = ProductRepository
.GetProductsBySpecification(
new ProductDiscontinuedSpecification()).ToList();
return Task.FromResult(DiscontinuedProducts);
}
}

El código requerirá importar los espacios de nombres MediatR, Application.Products.Queries,


Domain.Entities, Domain.Interfaces, System.Threading y Domain.Specifications.

7. Agrega un nuevo archivo de clase llamado GetAllProductsQuery.cs en el directorio


Products\Queries del proyecto Application con el siguiente código.

public class GetAllProductsQuery : IRequest<IEnumerable<Product>>


{
}

El código requerirá importar los espacios de nombres MediatR y Domain.Entities.

8. Agrega un nuevo archivo de clase llamado GetAllProductsQueryHandler.cs en el directorio


Products\Handlers del proyecto Application con el siguiente código.

public class GetAllProductsQueryHandler :


IRequestHandler<GetAllProductsQuery, IEnumerable<Product>>
{
readonly IProductRepository ProductRepository;
public GetAllProductsQueryHandler(IProductRepository productRepository) =>
ProductRepository = productRepository;
public Task<IEnumerable<Product>> Handle(
GetAllProductsQuery query, CancellationToken cancellationToken)
{
return Task.FromResult(ProductRepository.GetAll());
}
}

El código requerirá importar los espacios de nombres MediatR, Application.Products.Queries,


Domain.Entities, Domain.Interfaces y System.Threading.

9. Agrega un nuevo archivo de clase llamado GetAvailableForSaleProductsQuery.cs en el


directorio Products\Queries del proyecto Application con el siguiente código.

public class GetAvailableForSaleProductsQuery : IRequest<List<Product>>


{
}

223 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

El código requerirá importar los espacios de nombres MediatR y Domain.Entities.

10. Agrega un nuevo archivo de clase llamado GetAvailableForSaleProductsQueryHandler.cs en


el directorio Products\Handlers del proyecto Application con el siguiente código.

public class GetAvailableForSaleProductsQueryHandler :


IRequestHandler<GetAvailableForSaleProductsQuery, List<Product>>
{
readonly IProductRepository ProductRepository;
public GetAvailableForSaleProductsQueryHandler(
IProductRepository productRepository) =>
ProductRepository = productRepository;

public Task<List<Product>> Handle(GetAvailableForSaleProductsQuery query,


CancellationToken cancellationToken)
{
var Products = ProductRepository
.GetProductsBySpecification(
new ProductAvailableForSaleSpecification()).ToList();
return Task.FromResult(Products);
}
}

El código requerirá importar los espacios de nombres MediatR, Application.Products.Queries,


Domain.Entities, Domain.Interfaces, System.Threading y Domain.Specifications.

11. Agrega un nuevo archivo de clase llamado GetAllCategoriesQuery.cs en el directorio


Categories\Queries del proyecto Application con el siguiente código.

public class GetAllCategoriesQuery : IRequest<IEnumerable<Category>>


{
}

El código requerirá importar los espacios de nombres MediatR y Domain.Entities.

12. Agrega un nuevo archivo de clase llamado GetAllCategoriesQueryHandler.cs en el directorio


Categories\Handlers del proyecto Application con el siguiente código.

public class GetAllCategoriesQueryHandler :


IRequestHandler<GetAllCategoriesQuery, IEnumerable<Category>>
{
readonly ICategoryRepository CategoryRepository;
public GetAllCategoriesQueryHandler(
ICategoryRepository categoryRepository) =>
CategoryRepository = categoryRepository;
public Task<IEnumerable<Category>> Handle(
GetAllCategoriesQuery query, CancellationToken cancellationToken)
{
return Task.FromResult(CategoryRepository.GetAll());
}
}

224 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

El código requerirá importar los espacios de nombres MediatR, Domain.Entities,


Application.Categories.Queries, Domain.Interfaces y System.Threading.

Registrar los comandos, consultas y manejadores


1. Agrega un nuevo archivo de clase llamado DependencyContainer.cs en la raíz del proyecto
Application con el siguiente código.

using MediatR;
using Microsoft.Extensions.DependencyInjection;
using System.Reflection;

namespace Application
{
public static class DependencyContainer
{
public static IServiceCollection AddApplicationServices(
this IServiceCollection services)
{
services.AddMediatR(Assembly.GetExecutingAssembly());
return services;
}
}
}

2. Abre el archivo Startup.cs del proyecto RepositoryDemo.

3. Agrega una referencia del proyecto Application al proyecto RepositoryDemo.

4. Agrega el siguiente código al final del método ConfigureServices para registrar los servicios.

services.AddApplicationServices();

El código requerirá importar el espacio de nombres Application.

Modificar los controladores


1. Agrega el paquete NuGet MediatR.Extensions.Microsoft.DependencyInjection al proyecto
RepositoryDemo.

2. Abre el archivo ProductController.cs del proyecto RepositoryDemo.

3. Modifica el código de la clase ProductController para que sea similar al siguiente.

using Application.Products.Commands;
using Application.Products.Queries;
using MediatR;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;

225 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

namespace RepositoryDemo.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ProductController : ControllerBase
{
readonly IMediator Mediator;

public ProductController(IMediator mediator) =>


Mediator = mediator;

[HttpPost]
public IActionResult AddProductsWithCategory()
{
Mediator.Send(
new AddProductsToCategoryFromProductNamesCommand
{
CategoryName = "Bebidas",
ProductNames = new List<string>{"Leche", "Jugo", "Agua"}
}
);
return Ok("Completed!!!");
}

[HttpPatch("{id}")]
public IActionResult SetDiscontinued(int id)
{
Mediator.Send(
new SetDiscontinuedProductCommand { ProductId = id });
return Ok("Completed!!!");
}

[HttpGet("discontinued")]
public IActionResult GetDiscontinued()
{
return Ok(
Mediator.Send(
new GetDiscontinuedProductsQuery()).Result);
}

[HttpGet("get-all-products")]
public IActionResult GetAll()
{
return Ok(Mediator.Send(
new GetAllProductsQuery()).Result);
}

[HttpGet("get-available-for-sale")]
public IActionResult GetAvailableForSale()
{
return Ok(
Mediator.Send(
new GetAvailableForSaleProductsQuery()).Result);
}

226 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

}
}

4. Abre el archivo CategoryController.cs del proyecto RepositoryDemo.

5. Modifica el código de la clase CategoryController para que sea similar al siguiente.

using Application.Categories.Queries;
using MediatR;
using Microsoft.AspNetCore.Mvc;

namespace RepositoryDemo.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class CategoryController : ControllerBase
{
readonly IMediator Mediator;
public CategoryController(IMediator mediator) =>
Mediator = mediator;

[HttpGet("get-all-categories")]
public IActionResult GetAll()
{
return Ok(Mediator.Send(
new GetAllCategoriesQuery()).Result);
}

}
}

6. Ejecuta la aplicación y verifica que todo funcione correctamente.

227 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Lección 8:

Implementando una
Arquitectura Limpia
En lecciones anteriores vimos varios de los principios y patrones que podemos aplicar y utilizar para
poder implementar una arquitectura limpia. En esta lección aplicaremos varios de estos principios y
patrones para construir aplicaciones .NET implementando una Arquitectura Limpia.

Objetivos

Al finalizar esta lección, los participantes contarán con las habilidades y conocimientos para:

• Describir los elementos principales del diagrama Clean Architecture de Robert C. Martin.
• Iniciar el desarrollo de aplicaciones .NET implementando Clean Architecture.

228 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Introducción
Clean Architecture es un concepto popularizado por Robert Cecil Martin conocido como “Uncle Bob”.
Clean Architecture propone la estructuración del código en capas donde cada capa debe realizar su
propia tarea, cada capa debe tener su propia responsabilidad. Tomando como base esta idea, podemos
encontrar artículos que tratan sobre Clean Architecture, Onion Architecture, Hexagonal Architecture,
Screaming Architecture, etc., todas ellas tienen diferentes enfoques, pero comparten la idea de que
cada capa debe realizar su propia tarea y que cada capa debe tener su propia responsabilidad.

La fuente de la propuesta de Clean Architecture puede encontrarse en el siguiente enlace:

https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html

El siguiente enlace muestra un video de una charla sobre Clean Architecture de Robert C. Martin.

https://youtu.be/0oGpWmS0aYQ

La siguiente imagen muestra el diagrama de Clean Architecture propuesto por Robert C. Martin.

El objetivo principal de una arquitectura limpia es la separación de responsabilidades y esto se logra


dividiendo el software en capas. Al menos se tiene una capa para la regla de negocios y otra para las
interfaces.

229 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Los sistemas con una arquitectura limpia:

a) Son independientes de Frameworks. La arquitectura no depende de la existencia de alguna


biblioteca de software conteniendo diversas funciones. Esto nos permite utilizar esos
frameworks como herramientas, en lugar de tener que obstaculizar nuestro sistema con sus
limitadas funcionalidades.
b) Facilitan la realización de pruebas (Testables). Las reglas de negocio pueden ser probadas sin
la interfaz de usuario, la base de datos, el servidor web o cualquier otro elemento externo.
c) Independientes de la Interfaz de Usuario. La interfaz de usuario puede cambiar fácilmente,
sin cambiar el resto del sistema. Por ejemplo, una interfaz de usuario web podría reemplazarse
por una interfaz de usuario de consola sin cambiar las reglas de negocio.
d) Independientes de la base de datos. Podemos cambiar Oracle o SQL Server, por Mongo,
BigTable, CouchDB u otro manejador de base de datos. Las reglas de negocio no están
vinculadas a la base de datos.
e) Independientes de cualquier agente externo. Las reglas de negocio simplemente no saben
nada sobre el mundo exterior.

La Regla de la Dependencia
Los círculos concéntricos del diagrama representan diferentes áreas del software. Los círculos
exteriores son implementaciones. Los círculos internos son políticas o definiciones.

La regla primordial que hace que la arquitectura limpia funcione es La Regla De Dependencia. Esta
regla dice que las dependencias del código fuente solo pueden apuntar hacia adentro. Nada en un
círculo interno puede saber nada sobre algo en un círculo externo. En particular, el nombre de algo
declarado en un círculo exterior no debe ser mencionado por el código en un círculo interior. Esto
incluye funciones, clases, variables, o cualquier otra entidad de software con nombre. Nada en un
círculo externo debe afectar a los círculos internos.

Reglas de negocio empresariales (Enterprise Business Rules):


Entidades (Entities)
Las entidades encapsulan las reglas de negocio de toda la empresa. Una entidad puede ser un objeto
con métodos o puede ser un conjunto de estructuras de datos y funciones. No importa lo que sea
siempre que las entidades puedan ser utilizadas por otras aplicaciones diferentes en la empresa.

Las entidades encapsulan las reglas más generales y de alto nivel. Son las menos propensas a cambiar
cuando algo externo cambia. Por ejemplo, la entidades no se tienen que ver afectadas por un cambio
en la navegación de la página o la seguridad. Ningún cambio operativo en una aplicación en particular
debería afectar la capa de Entidades.

230 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

En resumen, podemos decir que la capa Enterprise Business Rules contiene el código que puede ser
compartido por diferentes aplicaciones con el fin de la reutilización del código. Algunos de los
elementos que pueden ubicarse en esta capa son los siguientes:

• Definición de interfaces.
• Entidades POCO que representan elementos únicos (por ejemplo, un Producto de la tabla
Products).
• Excepciones personalizadas.
• Objetos de Valor (Value Object) que, a diferencia de las entidades, los Objectos de Valor no
tienen una identificación específica, solo describen características como un Color o una
Dirección de envío.
• Eventos para comunicar los sucesos que suceden en el dominio.
• Especificaciones de lo que se puede hacer con una entidad dejando a otros componentes la
realización de esta.
• Validaciones sobre las entidades.
• DTOs.
• Etc.

Reglas de negocio de la aplicación (Application Business Rules):


Casos de Uso (Use Cases)
El software de esta capa contiene reglas de negocio específicas de la aplicación. Encapsula e
implementa todos los casos de uso del sistema. Los casos de uso orquestan el flujo de datos hacia y
desde las entidades para que estas apliquen sus reglas de negocio empresariales y permitan lograr los
objetivos del caso de uso.

Los cambios en esta capa no deben afectar a las entidades. Esta capa tampoco debe ser afectada por
cambios externos como la base de datos, la interfaz de usuario o cualquiera de los framework
utilizados.

Sin embargo, si hay algún cambio en el funcionamiento de la aplicación, esto podría afectar a los casos
de uso y, por lo tanto, al software en esta capa. Si los detalles de un caso de uso cambian, es seguro
que algún código de esta capa se verá afectado.

En esta capa podemos localizar los siguientes elementos:

• Use Case Interactor. Es el elemento que contiene el código con la lógica de negocios que
resuelve un caso de uso. Este elemento implementa la abstracción representada por el
elemento Use Case Input Port. En términos de programación orientada a objetos, el Interactor
puede ser una clase que implementa una Interface o clase abstracta (Input Port).

231 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

• Use Case Input Port. Es una abstracción que permite al Interactor recibir los datos necesarios
para resolver el caso de uso proporcionados por un elemento de la capa externa (Controller).
En términos de programación orientada a objetos, el Input Port puede ser definido como una
Interface o clase abstracta que el Interactor debe implementar y el Controller debe utilizar.
• Use Case Output Port. Es una abstracción que permite al Interactor devolver el resultado del
caso de uso a un elemento de la capa externa (Presenter). En términos de programación
orientada a objetos, el Output Port puede ser definido como una Interface o clase abstracta
que el Presenter debe implementar y el Interactor debe utilizar.

En resumen, podemos decir que en esta capa se encuentra el código encargado de dar solución a una
aplicación particular. El código en esta capa normalmente no puede ser reutilizado por otras
aplicaciones ya que dan solución a problemas distintos. Por ejemplo, el código de una aplicación de
facturación no nos serviría en una aplicación de seguimiento de clientes.

Algunos de los elementos que pueden ubicarse en esta capa son similares a los que se pueden definir
en la capa de Reglas de Negocio Empresariales, pero con enfoque en las necesidades de la aplicación
que se esté desarrollando:

• Definición de interfaces.
• Servicios para implementar la lógica de negocios.
• Excepciones personalizadas.
• Eventos.
• Especificaciones.
• Validaciones.
• DTOs,
• Etc.

Adaptadores de Interfaz (Interface Adapters)


El software en esta capa es un conjunto de adaptadores que convierten los datos del formato más
conveniente para los casos de uso y entidades, al formato más conveniente para algún agente externo
como la Base de Datos o la Web.

Es esta capa, por ejemplo, la que contendrá por completo la arquitectura MVC de una aplicación con
Interfaz de Usuario Gráfica (GUI). Presenters, Views y Controllers pertenecen a esta capa. Es probable
que los modelos sean solo estructuras de datos (DTOs) que se pasan de los controladores a los casos
de uso y luego de los casos de uso a los presentadores y vistas.

De manera similar, los datos se convierten en esta capa de la forma más conveniente para las entidades
y los casos de uso a la forma más conveniente para cualquier framework de persistencia que se esté
utilizando, es decir, la base de datos. Ningún código interno de este círculo debería saber algo sobre la

232 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

base de datos. Aquí podemos encontrar a los repositorios que funcionan como puerta de enlace
(Gateways) hacia la fuente de datos.

También en esta capa puede existir cualquier otro adaptador necesario para convertir datos de algún
formato externo, tal como el de un servicio externo, al formato interno utilizado por los casos de uso
y las entidades.

Frameworks y Drivers
La capa más externa generalmente se compone de frameworks y herramientas como la base de datos,
el framework web, etc. Generalmente, no se escribe mucho código en esta capa que no sea el código
necesario para comunicarse con el siguiente círculo hacia adentro.

Esta capa es donde van todos los detalles. La Web es considerada como un detalle, esto es, un
dispositivo de entrada y salida de información, así como cualquier otra Interfaz de Usuario como la
consola. La base de datos también es considerada como un detalle. Guardamos estas cosas en el
exterior, donde pueden hacer poco daño.

¿Solo 4 círculos?
No, los círculos son esquemáticos. No hay ninguna regla que diga que siempre debemos tener solo
estas cuatro capas. Sin embargo, siempre se aplica la regla de dependencia. Las dependencias del
código fuente siempre apuntan hacia adentro. A medida que avanzamos hacia adentro, aumenta el
nivel de abstracción y de políticas. El círculo más externo consiste en detalles concretos de bajo nivel.
A medida que avanzamos hacia adentro, el software se vuelve más abstracto y encapsula políticas de
alto nivel. El círculo más interno es el más general y de más alto nivel.

Cruzando los limites


En la parte inferior derecha del diagrama hay un ejemplo de cómo cruzamos los límites de los círculos.
El diagrama muestra a los Controladores y Presentadores comunicándose con los Casos de Uso en la
siguiente capa. El flujo de control comienza en el controlador, se mueve a través del caso de uso y
luego termina ejecutándose en el presentador. Debemos tener en cuenta también las dependencias
del código fuente. Cada una de ellas apunta hacia adentro hacia los casos de uso. Esta aparente
contradicción se resuelve utilizando el Principio de Inversión de Dependencia. En C#, por ejemplo,
podemos utilizar interfaces o clases abstractas para que en lugar de que los controladores y
presentadores accedan directamente a las implementaciones, accedan a abstracciones.

Por ejemplo, consideremos que el caso de uso necesita invocar al presentador. Esta llamada no debe
ser directa porque violaría la Regla de dependencia: ningún nombre en un círculo externo puede ser
mencionado por un círculo interno. Por lo tanto, podemos hacer que el caso de uso utilice una interface

233 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

(que se muestra en el diagrama como Puerto de salida del caso de uso – Use Case Output Port) en el
círculo interno, y hacer que el presentador en el círculo externo lo implemente.

La misma técnica es utilizada para cruzar todos los límites de las arquitecturas. Aprovechamos el
polimorfismo para crear dependencias de código fuente que se oponen al flujo de control para que
podamos cumplir con la Regla de dependencia sin importar en qué dirección vaya el flujo de control.

¿Qué datos cruzan los límites?


Normalmente, los datos que cruzan los límites son estructuras de datos simples. Podemos utilizar
estructuras básicas u objetos simples de transferencia de datos (DTOs) si lo deseamos. O los datos
pueden ser simplemente argumentos en llamadas a funciones. O podemos empaquetarlos en un
objeto. El punto importante es que estructuras de datos simples y aisladas son pasadas a través de los
límites. No debemos pasar, por ejemplo, registros o filas de bases de datos o entidades que tengan
algún tipo de dependencia que viole la Regla de dependencia.

Por ejemplo, muchos frameworks de base de datos devuelven un formato de datos conveniente en
respuesta a una consulta (por ejemplo, un objeto proxy en una entidad devuelta por Entity
Framework). Podríamos llamar a esto una estructura de fila. No debemos pasar esa estructura de fila
hacia adentro a través de un límite. Eso violaría la Regla de dependencia porque obligaría a un círculo
interno a saber algo sobre un círculo externo.

Por lo tanto, cuando pasemos datos a través de un límite, siempre deben estar en la forma que sea
más conveniente para el círculo interno.

Conclusión
Cumplir con estas simples reglas no es difícil y nos ahorrará muchos dolores de cabeza en el futuro. Al
separar el software en capas y cumplir con la Regla de dependencia, crearemos un sistema que sea
intrínsecamente comprobable, con todos los beneficios que ello implica. Cuando alguna de las partes
externas del sistema se vuelva obsoleta, como la base de datos o el framework web, podremos
reemplazar esos elementos obsoletos con un mínimo de esfuerzo.

234 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Implementando Clean Architecture con .NET


Desarrollemos una aplicación sencilla con el fin de explorar los elementos del diagrama de Arquitectura
Limpia propuesto por Robert C. Martin.

El Caso de Uso
Para la realización de este ejercicio, plantearemos el caso de uso Contactar a un vendedor.

Historia de Usuario: Contactar a un vendedor

Como persona interesada en comprar un producto, deseo contactar a un vendedor para obtener
información sobre un producto de mi interés.

Caso de Uso: Contactar a un vendedor

Datos de entrada

• Nombre del solicitante.


• Correo del solicitante.
• Teléfono del solicitante.
• Identificador (Id) del producto de interés.

Flujo primario

1. El solicitante envía la solicitud “Contactar a un vendedor” con los datos de entrada.


2. El sistema valida los datos.
3. El sistema registra la solicitud para que posteriormente se pueda notificar a un vendedor.
4. El sistema confirma al solicitante que su solicitud ha sido procesada.

Flujo alterno: Error de validación

1. El procesamiento de la solicitud es cancelado.


2. El sistema muestra el error a la persona solicitante.

Podemos notar que el caso de uso solo describe lo que se necesita en la interacción entre el usuario y
el sistema. No especifica la forma en que se debe guardar la información o la forma de introducir la
información, por ejemplo, a través de un sitio Web, una aplicación de consola o vía telefónica. Esos
son detalles, o cómo lo indica Robert C. Martin “las reglas de negocio deben ser agnósticas de los
mecanismos de entrega.”.

Creación de la estructura de la solución


Empecemos por crear la estructura de la solución utilizando Visual Studio.

235 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

1. Abre Visual Studio bajo el contexto de Administrador.

2. Crea una solución vacía llamada SellersContact utilizando la plantilla Blank Solution.

3. Agrega un nuevo directorio a la solución llamado Enterprise Business Rules. En este directorio
colocaremos el código que podría ser compartido con otras aplicaciones, por ejemplo, para
una aplicación de control de clientes, control de ventas, etc.

4. Agrega un nuevo directorio a la solución llamado Application Business Rules. En este directorio
colocaremos el código propio de la aplicación, por ejemplo, el código que resuelve el caso de
uso “Contactar a un vendedor”, la estructura para almacenar los datos de entrada y la
estructura para enviar la respuesta al solicitante.

5. Agrega un nuevo directorio a la solución llamado Interface Adapters. Aquí colocaremos los
Controladores, Presentadores, Vistas y ViewModels de la aplicación. También colocaremos
aquí el enlace (Gateway) para la persistencia de datos.

6. Agrega un nuevo directorio llamado Frameworks And Drivers a la solución. Aquí colocaremos
la aplicación o aplicaciones cliente que interactuarán directamente con el usuario.

La estructura de la solución será similar a la siguiente.

Agregar los elementos de la capa Enterprise Business Rules


1. Agrega un nuevo proyecto llamado Entities en el directorio Enterprise Business Rules utilizando
la plantilla Class Library.

2. Elimina el archivo Class1.cs.

236 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

3. Agrega un directorio llamado POCOEntities en la raíz del proyecto. Es aquí donde colocaremos
las clases que representarán un producto o un posible cliente.

4. Agrega un nuevo archivo de clase llamado Customer.cs en el directorio POCOEntities con el


siguiente código.

public class Customer


{
public string Name { get; set; }
public string Email { get; set; }
public string PhoneNumber { get; set; }
}

Esta clase almacenará los datos de la persona interesada en obtener información sobre un
producto.

5. Agrega un nuevo archivo de clase llamado Product.cs en el directorio POCOEntities con el


siguiente código.

public class Product


{
public int Id { get; set; }
public string Name { get; set; }
public List<Customer> Customers { get; set; } = new List<Customer>();

public void AddCustomer(Customer customer) =>


Customers.Add(customer);
}

Esta clase almacenará los datos de un producto y de las personas interesadas en obtener
información sobre el producto.

6. Agrega un nuevo directorio llamado Interfaces en la raíz del proyecto.

7. Agrega un nuevo archivo llamado IRepository.cs en el directorio Interfaces con el siguiente


código.

public interface IRepository<IdType, EntityType>


{
EntityType Save(EntityType entity);
EntityType Get(IdType id);
}

Esta interface nos permitirá implementar un repositorio para almacenar las solicitudes
recibidas por las personas interesadas en obtener información sobre un producto. Solo
estamos definiendo el método para guardar una solicitud y un método para obtener un
producto.

237 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Agregar el repositorio a la capa Interface Adapters


1. Agrega un nuevo directorio llamado Gateways dentro del directorio Interface Adapters.

2. Agrega un nuevo proyecto llamado Repository.Memory dentro del directorio Gateways


utilizando la plantilla Class Library.

3. Elimina el archivo Class1.cs.

4. Agrega al proyecto creado la referencia del proyecto Entities.

5. Agrega un nuevo archivo de clase llamado MemoryRepository.cs en el proyecto


Repository.Memory con el siguiente código.

using Entities.Interfaces;
using Entities.POCOEntities;
using System.Collections.Generic;

namespace Repository.Memory
{
public class MemoryRepository : IRepository<int, Product>
{
readonly Dictionary<int, Product> DB = new Dictionary<int, Product>
{
{1, new Product{Id=1, Name = "Jugo" } },
{2, new Product{Id=2, Name = "Azúcar" } },
{3, new Product{Id=1, Name = "Leche" } },
};
public Product Get(int id)
{
return DB[id];
}

public Product Save(Product product)


{
product.Id = DB.Count + 1;
DB[product.Id] = product;
return product;
}
}
}

La clase MemoryRepository implementa la interface IRepository. Por simplicidad, en el método


Get no estamos considerando el caso donde el producto solicitado no exista. También puedes
notar que estamos agregando datos de productos de ejemplo.

Agregar los elementos de la capa Application Business Rules


1. Agrega un nuevo proyecto llamado UseCases en el directorio Application Business Rules
utilizando la plantilla Class Library.

238 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

2. Elimina el archivo Class1.cs.

3. Agrega el paquete NuGet MediatR.Extensions.Microsoft.DependencyInjection. Utilizaremos


este paquete para manejar el proceso de petición/respuesta entre la capa Interface Adapters
y Application Business Rules.

4. Agrega el paquete NuGet FluentValidation.DependencyInjectionExtensions. Utilizaremos este


paquete para realizar la validación de los datos de entrada.

5. Agrega la referencia del proyecto Entities.

6. Agrega un nuevo directorio llamado ContactASeller en la raíz del proyecto. En este directorio
colocaremos los archivos que utilizaremos para dar solución al caso de uso.

7. Agrega un nuevo archivo de clase llamado ContactASellerOutputPort.cs en el directorio


ContactASeller con el siguiente código.

using FluentValidation.Results;

namespace UseCases.ContactASeller
{
public class ContactASellerOutputPort
{
public ValidationResult ValidationResult { get; set; }
public int? ProductId { get; set; }

public ContactASellerOutputPort(ValidationResult validationResult,


int? productId = null) =>
(ValidationResult, ProductId) =
(validationResult, productId);
}
}

La clase ContactASellerOutputPort nos permitirá enviar la respuesta al usuario. Podemos ver


que la clase ContactASellerOutputPort es una estructura de datos simple que utilizaremos para
cruzar los límites entre la capa Application Business Rules y la capa Interface Adapters, tal como
lo especifica la propuesta de Arquitectura Limpia.

8. Agrega un nuevo archivo de clase llamado ContactASellerInputPort.cs en el directorio


ContactASeller con el siguiente código.

using MediatR;

namespace UseCases.ContactASeller
{
public class ContactASellerInputPort :
IRequest<ContactASellerOutputPort>
{
public string CustomerName { get; set; }

239 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

public string CustomerEmail { get; set; }


public string CustomerPhoneNumber { get; set; }
public int ProductId { get; set; }
}
}

La clase ContactASellerInputPort recibirá los datos de entrada del caso de uso. Puedes notar
que estamos definiendo esta clase como un objeto IRequest de MediatR. Esta clase también
será utilizada como una estructura simple de datos que utilizaremos para cruzar los limites
entre la capa Interface Adapters y la capa Application Business Rules.

9. Agrega un nuevo archivo de clase llamado ContactASellerValidator.cs en el directorio


ContactASeller con el siguiente código.

using FluentValidation;

namespace UseCases.ContactASeller
{
public class ContactASellerValidator :
AbstractValidator<ContactASellerInputPort>
{
public ContactASellerValidator()
{
RuleFor(c => c.CustomerName).NotEmpty()
.WithMessage("Por favor proporcione su nombre.");
RuleFor(c => c.CustomerEmail).NotEmpty()
.WithMessage("Por favor proporcione un correo de contacto.");
RuleFor(c => c.CustomerEmail).EmailAddress()
.WithMessage("El correo proporcionado no es válido.");
RuleFor(c => c.ProductId).GreaterThan(0)
.WithMessage(
"Por favor proporcione el Id del producto de su interés.");
}
}
}

La clase ContactASellerValidator nos permitirá realizar la validación de los datos de entrada.


En este ejemplo, solo estamos validando que el usuario proporcione un nombre de contacto,
un correo electrónico válido y el identificador de producto de su interés.

10. Agrega un nuevo archivo de clase llamado ContactASellerInteractor.cs en el directorio


ContactASeller con el siguiente código.

using Entities.Interfaces;
using Entities.POCOEntities;
using FluentValidation;
using MediatR;
using System.Threading;
using System.Threading.Tasks;

namespace UseCases.ContactASeller

240 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

{
public class ContactASellerInteractor :
IRequestHandler<ContactASellerInputPort, ContactASellerOutputPort>
{
readonly IRepository<int, Product> Repository;
readonly IValidator<ContactASellerInputPort> Validator;

public ContactASellerInteractor(
IRepository<int, Product> repository,
IValidator<ContactASellerInputPort> validator) =>
(Repository, Validator) = (repository, validator);

public Task<ContactASellerOutputPort> Handle(


ContactASellerInputPort request,
CancellationToken cancellationToken)
{
ContactASellerOutputPort Response;
var ValidationResult = Validator.Validate(request);
if(ValidationResult.IsValid)
{
var Product = Repository.Get(request.ProductId);
Product.AddCustomer(new Customer
{
Name = request.CustomerName,
Email = request.CustomerEmail,
PhoneNumber = request.CustomerPhoneNumber
});
Repository.Save(Product);
Response = new ContactASellerOutputPort(
ValidationResult, request.ProductId);
}
else
{
Response =
new ContactASellerOutputPort(ValidationResult);
}
return Task.FromResult(Response);
}
}
}

La clase ContactASellerInteractor implementa la solución del caso de uso describiendo la


manera en el que el sistema debe comportarse de acuerdo con la especificación funcional.

Podemos notar que ContactASellerInteractor utiliza estructuras de datos simples como


puertos de entrada y salida. Además, recibe instancias del repositorio y del validador a través
del constructor.

Realizar la Inversión de Control


Podemos ahora aplicar el principio de Inversión de Control agregando un proyecto que nos permita
registrar los servicios en el contenedor de inyección de dependencias.

241 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

1. Agrega un nuevo proyecto llamado IoC en el directorio Application Business Rules utilizando la
plantilla Class Library.

2. Elimina el archivo Class1.cs.

3. Agrega la referencia de los proyectos UseCases, Entities y Repository.Memory al proyecto IoC.

4. Agrega un nuevo archivo de clase llamado DependencyContainer.cs en la raíz del proyecto IoC
con el siguiente código.

using Entities.Interfaces;
using Entities.POCOEntities;
using FluentValidation;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
using Repository.Memory;
using UseCases.ContactASeller;

namespace IoC
{
public static class DependencyContainer
{
public static IServiceCollection AddServices(
this IServiceCollection services)
{
services.AddSingleton<
IRepository<int, Product>, MemoryRepository>();
services.AddMediatR(typeof(ContactASellerInteractor));
services.AddValidatorsFromAssemblyContaining(
typeof(ContactASellerValidator));
return services;
}
}
}

En este punto el caso de uso ha sido implementado. Ahora podemos agregar los adaptadores que
podrán procesar la respuesta recibida por el Interactor.

El Interactor envía una respuesta en un formato adecuado a la capa Application Business Rules que
ahora debemos transformar a un formato entendible para el cliente en la Interfaz de usuario.

Para ejemplificar varios de los elementos descritos en el diagrama de Clean Architecture, crearemos
una aplicación de Consola como cliente.

Para que la aplicación de consola pueda interactuar con el usuario y mostrar una respuesta adecuada,
necesitaremos:

• Un ViewModel que contenga los datos de la respuesta que mostraremos al usuario.

242 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

• Un Presentador que se encargue de transformar la respuesta recibida por el Interactor y


genere un ViewModel.
• Una Vista que se encargue de generar la salida final para la interfaz de usuario.
• Un Controlador que:
o Invoque al Interactor.
o Envíe la respuesta al Presentador para que este le devuelva un ViewModel.
o Enviar el ViewModel a la Vista para construir la salida final que se mostrará al usuario.
o Mostrar al usuario el resultado generado por la Vista.

Agregar el ViewModel
1. Agrega un nuevo directorio llamado ViewModels en el directorio Interface Adapters.

2. Agrega un nuevo proyecto llamado ContactASellerViewModels en el directorio ViewModels


utilizando la plantilla Class Library.

3. Elimina el archivo Class1.cs.

4. Agrega un nuevo archivo de clase llamado ContactASellerViewModel.cs con el siguiente


código.

namespace ContactASellerViewModels
{
public class ContactASellerViewModel
{
public string ResponseText { get; private set; }
public ContactASellerViewModel(string responseText) =>
ResponseText = responseText;

}
}

Agregar el Presentador
1. Agrega un nuevo directorio llamado Presenters en el directorio Interface Adapters.

2. Agrega un nuevo proyecto llamado ContactASellerConsolePresenters en el directorio


Presenters utilizando la plantilla Class Library.

3. Elimina el archivo Class1.cs.

4. Agrega una referencia del proyecto ContactASellerViewModels.

5. Agrega una referencia del proyecto UseCases.

243 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

6. Agrega un nuevo archivo de clase llamado ContactASellerPresenter.cs en la raíz del proyecto


con el siguiente código.

using ContactASellerViewModels;
using System.Text;
using UseCases.ContactASeller;

namespace ContactASellerConsolePresenters
{
public class ContactASellerPresenter
{
public ContactASellerViewModel Format(
ContactASellerOutputPort message)
{
ContactASellerViewModel Response;

if(message.ValidationResult.IsValid)
{
Response =
new ContactASellerViewModel(
string.Format("{0} {1} {2} {3}",
"Su petición de información del producto",
message.ProductId,
"ha sido procesada.",
"Un agente se pondrá en contacto con usted."));
}
else
{
var SB = new StringBuilder();
foreach (var Error in message.ValidationResult.Errors)
{
SB.AppendLine(Error.ErrorMessage);
}
Response = new ContactASellerViewModel(SB.ToString());
}

return Response;
}
}
}

Agregar la Vista

1. Agrega un nuevo directorio llamado Views en el directorio Interface Adapters.

2. Agrega un nuevo proyecto llamado ContactASellerConsoleViews en el directorio Views


utilizando la plantilla Class Library.

3. Elimina el archivo Class1.cs.

4. Agrega una referencia del proyecto ContactASellerViewModels.

244 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

5. Agrega un nuevo archivo de clase llamado ContactASellerView.cs en el proyecto


ContactASellerConsoleViews con el siguiente código.

using ContactASellerViewModels;
using System;

namespace ContactASellerConsoleViews
{
public class ContactASellerView
{
readonly ContactASellerViewModel Model;
public ContactASellerView(ContactASellerViewModel model) =>
Model = model;

public void ExecuteResult()


{
Console.WriteLine(Model.ResponseText);
}
}
}

Agregar el Controlador

1. Agrega un nuevo directorio llamado Controllers en el directorio Interface Adapters.

2. Agrega un nuevo proyecto llamado ContactASellerConsoleControllers en el directorio


Controllers utilizando la plantilla Class Library.

3. Elimina el archivo Class1.cs.

4. Agrega el paquete NuGet MediatR al proyecto ContactASellerConsoleControllers.

5. Agrega una referencia de los proyectos UseCases, ContactASellerConsolePresenters y


ContactASellerConsoleViews.

6. Agrega un nuevo archivo de clase llamado ContactASellerController.cs con el siguiente código.

using ContactASellerConsolePresenters;
using ContactASellerConsoleViews;
using MediatR;
using UseCases.ContactASeller;

namespace ContactASellerConsoleControllers
{
public class ContactASellerController
{
readonly IMediator Mediator;
readonly ContactASellerPresenter Presenter;

public ContactASellerController(IMediator mediator,


ContactASellerPresenter presenter) =>

245 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

(Mediator, Presenter) = (mediator, presenter);

public ContactASellerView Contact(ContactASellerInputPort request)


{
var Response = Mediator.Send(request).Result;
var Model = Presenter.Format(Response);
return new ContactASellerView(Model);
}
}
}

Ahora es momento de agregar la aplicación cliente que interactuará con el usuario.

Agregar la aplicación de Consola


1. Agrega un nuevo proyecto llamado ConsoleClient en el directorio Frameworks And Drivers
utilizando la plantilla Console Application.

2. Instala el paquete NuGet Microsoft.Extensions.DependencyInjection. Este paquete contiene


la implementación predeterminada del servicio de inyección de dependencias de .NET.

3. Agrega una referencia de los proyectos IoC, ContactASellerConsoleControllers y


ContactASellerConsolePresenters.

4.

5. Agrega un nuevo archivo de clase llamado ServiceContainer.cs con el siguiente código.

using Microsoft.Extensions.DependencyInjection;
using System;

namespace ConsoleClient
{
public static class ServiceContainer
{
static IServiceProvider ServiceProvider;

public static void ConfigureServices(


Action<IServiceCollection> configureServices)
{
IServiceCollection Services = new ServiceCollection();
configureServices(Services);
ServiceProvider = Services.BuildServiceProvider();
}

public static T GetService<T>() =>


ServiceProvider.GetService<T>();
}
}

246 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Utilizaremos la clase ServiceContainer como el contenedor de servicios para inyección de


dependencias.

El método ConfigureServices se encargará de construir el objeto IServiceProvider que nos


permitirá recuperar servicios del contenedor de inyección de dependencias. Este método
recibe un delegado Action que permitirá registrar los servicios de la aplicación.

6. Modifica el método Main de la clase Program para que sea similar al siguiente.

using ContactASellerConsoleControllers;
using ContactASellerConsolePresenters;
using IoC;
using MediatR;
using UseCases.ContactASeller;

namespace ConsoleClient
{
class Program
{
static void Main(string[] args)
{
ServiceContainer.ConfigureServices((services) =>
services.AddServices());

var Controller = new ContactASellerController(


ServiceContainer.GetService<IMediator>(),
new ContactASellerPresenter());

// Esta petición debe tener éxito


Controller.Contact(new ContactASellerInputPort
{
CustomerName = "Pilar ternera",
CustomerEmail = "pternera@outlook.com",
CustomerPhoneNumber = "+57 123 456 7890",
ProductId = 2
}).ExecuteResult();

// Esta petición debe mostrar errores


Controller.Contact(new ContactASellerInputPort
{
CustomerName = "Pilar ternera",
CustomerEmail = "pternera-outlook.com",
CustomerPhoneNumber = "+57 123 456 7890"
}).ExecuteResult();
}
}
}

Probar la funcionalidad

1. Establece el proyecto de la aplicación de consola como proyecto de inicio.

247 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

2. Ejecuta la aplicación. Podrás ver un resultado similar al siguiente.

Ahora ya tenemos las bases para empezar a desarrollar aplicaciones .NET implementando una
Arquitectura Limpia.

La estructura de directorios y proyectos del ejemplo simple que hemos realizado tienen como objetivo
destacar los elementos principales que se describen en el diagrama Clean Architecture descrito por
Robert C. Martin.

Si lo deseamos, podemos utilizar algún otro tipo de estructura de nuestra aplicación, por ejemplo,
podemos utilizar la estructura de la arquitectura Onion que es una de las más utilizadas. Recordemos
que lo importante es respetar la Regla de la Dependencia.

248 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Agregar un cliente Web API


Agreguemos un cliente Web API a nuestra solución. Dado que hemos seguido las recomendaciones de
una Arquitectura Limpia, realizar esto no debe ser una tarea difícil.

1. Agrega un nuevo proyecto llamado WebAPIClient en el directorio Interface


Adapters\Gateways utilizando la plantilla ASP.NET Core Web API con soporte a OpenAPI.

2. Agrega una referencia del proyecto IoC.

3. Agrega el siguiente código al final del método ConfigureServices del archivo Startup.cs.

services.AddServices();

El código requerirá importar el espacio de nombres IoC.

4. Agrega un nuevo archivo llamado ContactASellerController.cs en el directorio Controllers


utilizando la plantilla API Controller – Empty.

5. Agrega el siguiente código a la clase ContactASellerController.

readonly IMediator Mediator;

public ContactASellerController(IMediator mediator) =>


Mediator = mediator;

[HttpPost("Contact-a-seller")]
public IActionResult Contact(ContactASellerInputPort request)
{
return Ok(Mediator.Send(request).Result);
}

El código requerirá importar los espacios de nombres MediatR y UseCases.ContactASeller.

6. Establece el proyecto WebAPIClient como proyecto de inicio.

7. Ejecuta la aplicación.

8. Prueba la funcionalidad POST /api/ContactASeller/Contact-a-seller proporcionando datos


válidos.

249 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Podrás ver una respuesta similar a la siguiente.

9. Proporciona datos incorrectos y prueba nuevamente. Podrás ver un resultado similar al


siguiente.

250 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Agregar un presentador
1. Agrega al proyecto WebAPIClient un nuevo archivo de clase llamado WebAPIPresenter.cs con
el siguiente código.

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.Text;
using UseCases.ContactASeller;

namespace WebAPIClient
{
public class WebAPIPresenter
{
public ObjectResult Format(ContactASellerOutputPort message)
{
ProblemDetails Response;
if (message.ValidationResult.IsValid)
{
Response =
new ProblemDetails
{

251 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Status = StatusCodes.Status200OK,
Title = "Solicitud procesada",
Type =

"https://datatracker.ietf.org/doc/html/rfc7231#section-6.3.1",
Detail = string.Format("{0} {1} {2} {3}",
"Su petición de información del producto",
message.ProductId,
"ha sido procesada.",
"Un agente se pondrá en contacto con usted.")
};
}
else
{
var SB = new StringBuilder();
foreach (var Error in message.ValidationResult.Errors)
{
SB.AppendLine(Error.ErrorMessage);
}
Response = new ProblemDetails
{
Status = StatusCodes.Status400BadRequest,
Title = "Solicitud cancelada",
Type =
"https://datatracker.ietf.org/doc/html/rfc7231#section-
6.5.1",
Detail = SB.ToString()
};
}

return new ObjectResult(Response);


}
}
}

2. Modifica el código del método Contact de la clase ContactASellerController para utilizar el


nuevo presentador.

[HttpPost("Contact-a-seller")]
public IActionResult Contact(ContactASellerInputPort request)
{
return new WebAPIPresenter()
.Format(Mediator.Send(request).Result);
}

3. Ejecuta la aplicación.

4. Prueba la funcionalidad POST /api/ContactASeller/Contact-a-seller proporcionando datos


válidos. Podrás ver una respuesta similar a la siguiente.

252 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

5. Proporciona datos incorrectos y prueba nuevamente. Podrás ver un resultado similar al


siguiente.

253 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Agregar un cliente Blazor Web Assembly


Agreguemos ahora un cliente SPA a nuestra solución. Esta tarea debe resumirse en crear una interfaz
de usuario y realizar llamadas a la API.

1. Agrega un nuevo proyecto llamado BlazorWasmClient en el directorio Frameworks And


Drivers utilizando la plantilla Blazor WebAssembly App con las opciones predeterminadas.

2. Agrega una referencia del proyecto UseCases. Estamos agregando esta referencia para utilizar
la clase ContactASellerInputPort para enviar la petición a la Web API. Esto nos evita crear una
nueva clase en la aplicación Blazor con los datos requeridos para realizar la petición.

3. Agrega un nuevo archivo de clase llamado ProblemDetails.cs en la raíz del proyecto. Esta clase
nos permitirá almacenar la respuesta devuelta por la Web API.

4. Modifica el código de la clase ProblemDetails para que sea similar al siguiente.

public class ProblemDetails


{
public string Type { get; set; }
public string Title { get; set; }
public int? Status { get; set; }
public string Detail { get; set; }
}

5. Agrega un nuevo archivo llamado ContactASeller.razor en el directorio Pages utilizando la


plantilla Razor Component.

254 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

6. Remplaza el bloque @code con el siguiente código para implementar la lógica que realizará la
petición a la Web API y mostrará la respuesta en la interfaz de usuario.

@code {
ContactASellerInputPort Request =
new ContactASellerInputPort();

// Para mostrar la respuesta al usuario


MarkupString Message;

// Iniciar la petición.
async Task Contact()
{
// Modifica el puerto donde se está ejecutando WebAPIClient
string WebAPIURIBase = "https://localhost:44337/api/ContactASeller/";

// Realizar la peticíon.
var Response =
await HttpClient.PostAsJsonAsync(
$"{WebAPIURIBase}contact-a-seller", Request);

// Obtener la respuesta
var ResponseContent =
await Response.Content.ReadFromJsonAsync<ProblemDetails>();

string MessageResponse = ResponseContent.Title;

if (Response.StatusCode != System.Net.HttpStatusCode.OK)
{
MessageResponse += ResponseContent.Detail;
}
Message = new MarkupString(MessageResponse.Replace("\r\n", "<br/>"));
}
}

7. Remplaza el código HTML del archivo con el siguiente código para construir la interfaz de
usuario.

@page "/contactaseller"
@using UseCases.ContactASeller
@using System.Text.Json
@inject HttpClient HttpClient

<h3>Contactar a un vendedor</h3>
<div class="form-group">
<label>Nombre:</label>
<input type="text" class="form-control"
@bind="Request.CustomerName" />
</div>

<div class="form-group">
<label>Correo electrónico:</label>
<input type="text" class="form-control"
@bind="Request.CustomerEmail" />

255 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

</div>

<div class="form-group">
<label>Teléfono:</label>
<input type="text" class="form-control"
@bind="Request.CustomerPhoneNumber" />
</div>

<div class="form-group">
<label>ID del Producto:</label>
<input type="text" class="form-control"
@bind="Request.ProductId" />
</div>

<button class="btn btn-primary" @onclick="Contact">


Contactar
</button>

<div>
@Message
</div>

8. Agrega el siguiente código al final del método ConfigureServices del archivo Startup.cs del
proyecto WebAPIClient para agregar el servicio CORS que permite las peticiones HTTP desde
navegadores web.

services.AddCors(options =>
{
options.AddPolicy("default", policy =>
{
// Podríamos especificar únicamente los orígenes permitidos.
policy.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
});
});

9. Agrega el siguiente código después de la línea app.UseRouting(); en el método Configure


para agregar el middleware de CORS en el pipeline de peticiones.

app.UseCors("default");

10. Establece el proyecto WebAPIClient y el proyecto BlazorWasmClient como proyectos de


inicio.

11. Ejecuta la aplicación.

12. En la página de la aplicación Blazor, navega a la siguiente dirección remplazando el puerto


donde se está ejecutando tu aplicación.

https://localhost:<puerto>/contactaseller

256 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Podrás ver una página similar a la siguiente.

Proporciona datos válidos y haz clic en Contactar. Podrás ver un resultado similar al siguiente.

257 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

13. Proporciona datos incorrectos y envía la petición. Podrás ver una respuesta similar a la
siguiente.

258 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

259 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)
Introducción a Clean Architecture con .NET

Resumen
La estructura de proyectos, directorios y archivos que hemos creado en esta lección ha sido diseñada
con el fin de mostrar la ubicación de cada uno de los elementos descritos en el diagrama Clean
Architecture propuesto por Robert C. Martin, sin embargo, podríamos definir una estructura distinta.
Una estructura muy utilizada es la estructura de la arquitectura Onion que destaca las capas Domain
Model, Domain Services, Application Services y la capa User Interface e Infrastructure. No importa que
estructura decidamos seguir, lo importante es recordar que la regla primordial que hace que la
arquitectura limpia funcione es La Regla De Dependencia.

La Regla de la Dependencia nos dice que las dependencias del código fuente solo pueden apuntar hacia
adentro. Nada en un círculo interno puede saber nada sobre algo en un círculo externo. En particular,
el nombre de algo declarado en un círculo exterior no debe ser mencionado por el código en un círculo
interior. Esto incluye funciones, clases, variables, o cualquier otra entidad de software con nombre.

El siguiente paso es empezar a desarrollar aplicaciones .NET implementando los principios y patrones
que hemos descrito a lo largo de este entrenamiento.

260 https://ticapacitacion.com/curso/cleanarch
Este manual fue creado por TI Capacitación para uso personal de:
Akin Ramirez (akin.ramirez@hotmail.com)

También podría gustarte