Clean Arch NET
Clean Arch NET
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
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
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
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
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
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
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
Objetivos
Al finalizar este entrenamiento los participantes serán capaces de:
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
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
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.
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.
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.
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.
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.
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.
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.
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.
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
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.
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”.
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.
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.
// 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);
}
Por simplicidad, podemos utilizar el siguiente código para define las clases Product y Activity.
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.
// 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
}
}
Con esta separación, el código es más fácil de mantener al tener las responsabilidades separadas en
distintas clases.
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.
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
Refactoricemos el código de la clase LogService agregando una Interface y clases que implementen esa
Interface.
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
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.
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.
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.
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.
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();
}
El siguiente código muestra la forma en que podemos utilizar las clases anteriores.
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
Examinemos el siguiente código que define una Interface para implementar repositorios para realizar
operaciones CRUD básicas.
Si deseamos un repositorio que implemente toda la funcionalidad podríamos tener un código similar
al siguiente.
// Código de la implementación
return Result;
}
// Código de la implementación
return Result;
}
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
¿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.
// Código de la implementación
return Result;
}
// Código de la implementación
return Result;
}
Siguiendo el principio de segregación de Interface, la solución sería definir interfaces pequeñas para
construir componentes más modulares.
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
// Código de la implementación
return Result;
}
// Código de la implementación
return Result;
}
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
// Código de la implementación
return Result;
}
// Código de la implementación
return Result;
}
}
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.
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
// 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.
El siguiente código representa una abstracción que nos permitirá escribir hacia un servicio de Log.
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
// 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.
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.
// . . .
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.
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
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
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.
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
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.
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.
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.
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
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
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.
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).
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
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.
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.
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.
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.
Infraestructura (Infrastructure)
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.
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
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.
• Controladores
• Filtros
• Vistas
• ViewModels
• Inicio (Startup)
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:
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
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:
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.
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
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.
2. Crea una nueva solución llamada MediatorDemo utilizando la plantilla Blank Solution.
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.
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
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");
}
}
[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!!!");
}
}
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
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.
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.
// 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
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.
Service.SaveChanges();
return Ok("Saved!!!");
}
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.
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.
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.
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}");
}
}
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>();
La Interface IMediator define un único método que permitirá notificar a los manejadores de
notificación.
// 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!
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.
[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
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.
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
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.
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
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.
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:
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.
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
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.
Agreguemos ahora una clase que facilite al consumidor del Mediator el registro del servicio en el
contenedor de dependencias.
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
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.
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.
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}");
return Task.CompletedTask;
}
}
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
[Route("api/[controller]")]
[ApiController]
public class CommandsController : ControllerBase
{
readonly IMediator Mediator;
public CommandsController(IMediator mediator) =>
Mediator = mediator;
}
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!!!");
}
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"));
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.
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
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.
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.
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());
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:
Cada tipo de petición tiene su propia Interface de manejador de petición, así como clases base de
ayuda:
Veamos un ejemplo.
1. Agrega un archivo de clase llamado Ping.cs con el siguiente código para crear un mensaje.
2. Agrega un archivo de clase llamado PingHandler.cs con el siguiente código para crear el
manejador del mensaje.
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
4. Agrega el siguiente código para inyectar el servicio IMediator a través del constructor de la
clase.
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);
}
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
9. Agrega un nuevo archivo de clase llamado OneWay.cs con el siguiente código para ejemplificar
una petición que no devuelve valor.
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.
[HttpGet("Send-Without-Response")]
public async Task<IActionResult> SendWithoutResponse()
{
await Mediator.Send(new OneWay());
return Ok("Completed!!!");
}
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
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.
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
[HttpGet("Send-Notifications")]
public async Task<IActionResult> SendNotifications()
{
await Mediator.Publish(new PingNotification());
return Ok("Completed!!!");
}
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
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
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.
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());
5. Agrega un nuevo archivo de clase llamado CreateProduct.cs con el siguiente código para
representar una petición para crear un producto.
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
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
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.
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));
Puedes notar que estamos inyectando el servicio IMediator a través del constructor de la clase.
[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.
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
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
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:
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.
• 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.
• 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.
• 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
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
2. Crea un nuevo proyecto llamado CQRSDemo utilizando la plantilla ASP.NET Core Web API y
con soporte a OpenAPI.
2. En el directorio Models, agrega un nuevo archivo de clase llamado Product.cs con el siguiente
código.
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
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);
}
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);
}
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);
}
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;
}
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;
}
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
Puedes notar que la clase ProductContext recibe la cadena de conexión a través del
constructor.
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")));
{
"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
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
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.
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
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());
3. Crea 3 directorios llamados Commands, Queries y Handlers dentro del directorio Products. La
estructura de archivos será similar a la siguiente.
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.
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.
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.
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.
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
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;
[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 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.
[HttpPut]
public async Task<IActionResult> Update(UpdateProductCommand command)
{
IActionResult Result =
BadRequest("No se ha podido realizar la modificación.");
if (Modified)
{
Result = Ok($"El producto {command.Id} ha sido modificado.");
}
return Result;
}
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;
[HttpGet]
public async Task<IActionResult> GetAll()
{
return Ok(await Mediator.Send(new GetAllProductsQuery()));
}
[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.
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
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
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
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
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
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
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:
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:
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
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.
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.
return Id;
}
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;
}
}
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.
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;
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.
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.
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
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 }));
}
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.
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.
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
return Task.CompletedTask;
}
}
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
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.
public ApiExceptionFilterAttribute(
IDictionary<Type, IExceptionHandler> exceptionHandlers) =>
ExceptionHandlers = exceptionHandlers;
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
Registrar el filtro
El siguiente paso es registrar el nuevo filtro creado.
services.AddControllers(options=>
{
options.Filters.Add(new ApiExceptionFilterAttribute(
new Dictionary<Type, IExceptionHandler>
{
{typeof(EntityNotFoundException),
new EntityNotFoundExceptionHandler()},
{typeof(GeneralException),
new GeneralExceptionHandler()}
}
));
});
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
5. Prueba la funcionalidad DELETE /api/Product para eliminar un producto 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.
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.
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.
16. Corrige el valor de la cadena de conexión para que la aplicación vuelva a funcionar
correctamente.
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
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?
Objetivos
Al finalizar esta lección, los participantes contarán con las habilidades y conocimientos para:
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.
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
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.
2. Agrega un nuevo archivo de clase llamado Product.cs en el directorio Models con el siguiente
código.
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
Podemos definir un conjunto de reglas de validación para esta clase heredando de la clase
abstracta AbstractValidator<Product>.
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.
[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;
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;
}
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
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 ~.
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;
Result = BadRequest(Builder.ToString());
}
return Result;
}
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.
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
2. Modifica la clase Product para incluir una propiedad que especifique el proveedor del
producto.
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
[HttpGet("validate-product-and-supplier")]
public async Task<IActionResult> ValidateProductAndSupplier(string name)
{
IActionResult Result;
if (ValidationResult.IsValid)
{
Result = Ok("Producto válido!!!");
}
else
{
Result = BadRequest(ValidationResult.ToString("~"));
}
return Result;
}
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
Puedes notar que debido a que no asignamos un valor a PostalCode, se ha devuelto un error
de validación.
Puedes notar que ahora se muestran los dos errores de validación separados por el carácter
~.
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
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.
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;
if (ValidationResult.IsValid)
{
Result = Ok("Producto válido!!!");
}
else
{
Result = BadRequest(ValidationResult.ToString("~"));
}
return Result;
}
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.
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
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
~.
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
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.
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();
}
}
La regla que hemos creado ejecutará una verificación NotNull para cada elemento en la
colección PhoneNumbers.
[HttpGet("validate-collections-simple-types")]
public async Task<IActionResult> ValidateCollectionsSimpleTypes()
{
IActionResult Result;
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.
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}.
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.
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
};
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.
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
[HttpGet("validate-collections-complex-types")]
public async Task<IActionResult> ValidateCollectionsComplexTypes()
{
IActionResult Result;
if (ValidationResult.IsValid)
{
Result = Ok("Categoría válida!!!");
}
else
{
Result = BadRequest(ValidationResult.ToString("\n"));
}
return Result;
}
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.
[HttpGet("validate-collections-complex-types-inline")]
public async Task<IActionResult> ValidateCollectionsComplexTypesInline()
{
IActionResult Result;
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;
}
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
La validación solamente será aplicada a los productos cuya unidad en existencia sea mayor que
0.
[HttpGet("validate-collections-complex-types-inline-where")]
public async Task<IActionResult>
ValidateCollectionsComplexTypesInlineWhere()
{
IActionResult Result;
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.
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;
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"}
}
};
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.
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
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;
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.
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.
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
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 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.
[HttpGet("validate-inheritance-person")]
public async Task<IActionResult>ValidateInheritancePerson()
{
IActionResult Result;
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
if (ValidationResult.IsValid)
{
Result = Ok("Contacto válido!!!");
}
else
{
Result = BadRequest(ValidationResult.ToString("\n"));
}
return Result;
}
}
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;
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.
1. Agrega un nuevo archivo de clase llamado Client.cs en el directorio Models con el siguiente
código.
Podemos crear un validador para la clase Client que establezca reglas para las 3 propiedades.
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();
});
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.
[HttpGet("validate-ruleset")]
public async Task<IActionResult> ValidateRuleSet()
{
IActionResult Result;
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 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.
[HttpGet("validate-without-ruleset")]
public async Task<IActionResult> ValidateWithoutRuleSet()
{
IActionResult Result;
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
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.
[HttpGet("validate-include-all-rules")]
public async Task<IActionResult> ValidateIncludeAllRules()
{
IActionResult Result;
if (ValidationResult.IsValid)
{
Result = Ok("Cliente válido!!!");
}
else
{
Result = BadRequest(ValidationResult.ToString("\n"));
}
return Result;
}
}
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.
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.
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 validador PersonAgeValidator valida que la edad de una persona sea mayor o igual a 18.
Debido a que ambos validadores validan el mismo tipo, en este caso Person, podemos
combinar ambas reglas utilizando Include.
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
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;
if (ValidationResult.IsValid)
{
Result = Ok("Persona válida!!!");
}
else
{
Result = BadRequest(ValidationResult.ToString("\n"));
}
return Result;
}
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>();
Ahora ya podemos inyectar el validador tal y como lo haríamos con otra dependencia.
3. Modifica el código de la clase PersonController para que sea similar al siguiente código.
[HttpGet("validate-person-from-di")]
public async Task<IActionResult> ValidatePersonFromDI()
{
IActionResult Result;
ValidationResult ValidationResult =
await Validator.ValidateAsync(Person);
if (ValidationResult.IsValid)
{
Result = Ok("Persona válida!!!");
}
else
{
Result = BadRequest(ValidationResult.ToString("\n"));
}
return Result;
}
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
Registro automático
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 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);
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.
[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 itera y ejecuta cada uno de los validadores registrados para la clase Person.
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.
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
Crear el validador
1. Crea un nuevo directorio llamado Validators dentro del directorio Application\Products.
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 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 registra todos los validadores que se encuentran en el ensamblado que contiene al
tipo Startup.
public ValidationBehavior(
IEnumerable<IValidator<RequestType>> validators) =>
Validators = validators;
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)));
if(Failures.Count != 0)
{
// Lanzar una excepción si hay fallas.
throw new ValidationException(Failures);
}
}
return await next();
}
}
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.
services.AddTransient(typeof(IPipelineBehavior<,>),
typeof(ValidationBehavior<,>));
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
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
services.AddControllers(options=>
{
options.Filters.Add(new ApiExceptionFilterAttribute(
new Dictionary<Type, IExceptionHandler>
{
{typeof(EntityNotFoundException),
new EntityNotFoundExceptionHandler()},
{typeof(GeneralException),
new GeneralExceptionHandler()},
{typeof(ValidationException),
new ValidationExceptionHandler()}
}
));
});
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
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:
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.
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
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.
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
Otro requerimiento de la aplicación podría ser el de consultar los nuevos productos agregados
con base a una fecha.
Si deseamos buscar productos por precio o por categoría, podríamos necesitar agregar otros
dos métodos.
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.
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;
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 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.
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.
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
[HttpGet("{id}")]
public IActionResult IsAvailableForSale(int id)
{
IActionResult Result;
return Result;
}
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.
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.
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());
}
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
El producto con Id 2 no puede ser vendido debido a que está discontinuado y sin existencia.
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
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.
[HttpGet("with- expression/{id}")]
public IActionResult IsAvailableForSaleWithExpressions(int id)
{
IActionResult Result;
// 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 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.
[HttpGet("products-with-expression")]
public IActionResult GetAvailableWithExpression()
{
// Crear la Expresión
Expression<Func<Product, bool>> ConditionExpression = product =>
!product.Discontinued &&
product.UnitsInStock > 0;
4. Ejecuta la aplicación.
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
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.
8. Modifica el método FindAvailableForSale del repositorio que recibe una expresión como
parámetro para que reciba ahora una especificación.
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();
}
[HttpGet("with-expression/{id}")]
public IActionResult IsAvailableForSaleWithExpressions(int id)
{
IActionResult Result;
// 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;
}
[HttpGet("products-with-expression")]
public IActionResult GetAvailableWithExpression()
{
// Inicializar la especificación
var Specification = new GenericSpecification<Product>(product =>
!product.Discontinued &&
product.UnitsInStock > 0);
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
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
Si lo deseas, antes de realizar los siguientes pasos, puedes realizar un respaldo del directorio de la
solución y nombrarla como SpecificationDemoV2.
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.
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.
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();
}
[HttpGet("with-specification/{id}")]
public IActionResult IsAvailableForSaleWithSpecification(int id)
{
IActionResult Result;
// 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;
}
[HttpGet("products-with-specification")]
public IActionResult GetAvailableWithSpecification()
{
// Inicializar la especificación
var Specification = new ProductAvailableForSaleSpecification();
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.
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.
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
return System.Linq.Expressions.Expression
.Lambda<Func<T, bool>>(Body, Param);
}
}
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.
Puedes notar que estamos uniendo las dos especificaciones a través del método And.
6. Ejecuta la aplicación.
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:
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.
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.
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
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
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
2. Crea un nuevo proyecto llamado RepositoryDemo utilizando la plantilla ASP.NET Core Web
API y con soporte a OpenAPI.
1. Agrega a la solución un nuevo proyecto llamado Domain utilizando la plantilla Class Library.
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.
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.
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.
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
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.
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
8. Agrega el siguiente código al final del método ConfigureServices para registrar el contexto de
datos.
services.AddRepositories(Configuration);
{
"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.
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.
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);
}
}
}
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.
La opción -p especifica el proyecto destino donde se crearán las migraciones (Default project).
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
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.
2. Agrega un nuevo archivo de clase llamado IRepository.cs dentro del directorio Interfaces con
el siguiente código.
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.
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
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.
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
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.
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
services.AddScoped<IProductRepository, ProductRepository>();
services.AddScoped<ICategoryRepository, CategoryRepository>();
return services;
}
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.
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.
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á.
Disposal of services
https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-
injection?WT.mc_id=DT-MVP-21053#disposal-of-services
services.AddScoped<IProductRepository, ProductRepository>();
services.AddScoped<ICategoryRepository, CategoryRepository>();
services.AddScoped<IUnitOfWork, UnitOfWork>();
return services;
}
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).
2. Agrega el siguiente código al controlador para recibir los servicios a través del constructor de
la clase.
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");
}
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());
}
8. Agrega el siguiente código al controlador para recibir el servicio a través del constructor de la
clase.
[HttpGet("get-all-categories")]
public IActionResult GetAll()
{
return Ok(CategoryRepository.GetAll());
}
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
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
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
• 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.
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.
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
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
Puedes notar que el repositorio recibe la instancia de ApplicationContext para realizar las
operaciones requeridas.
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
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.
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.
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);
return System.Linq.Expressions.Expression
.Lambda<Func<T, bool>>(Body, Param);
}
}
}
return System.Linq.Expressions.Expression
.Lambda<Func<T, bool>>(Body, Param);
}
}
}
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
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
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()));
}
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()));
}
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
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.
public AddProductsToCategoryFromProductNamesCommandHandler(
IProductRepository productRepository,
ICategoryRepository categoryRepository,
IUnitOfWork unitOfWork) =>
(ProductRepository, CategoryRepository, UnitOfWork) =
(productRepository, categoryRepository, unitOfWork);
CategoryRepository.Add(Category);
ProductRepository.AddToCategoryFromProductNames(Category,
command.ProductNames);
await UnitOfWork.SaveChangesAsync();
}
}
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.
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
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.
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
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
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
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;
}
}
}
4. Agrega el siguiente código al final del método ConfigureServices para registrar los servicios.
services.AddApplicationServices();
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;
[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
}
}
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);
}
}
}
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.
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.
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
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.
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.
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.
• 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.
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.
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.
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
El Caso de Uso
Para la realización de este ejercicio, plantearemos el caso de uso 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.
Datos de entrada
Flujo primario
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.”.
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
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.
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.
Esta clase almacenará los datos de la persona interesada en obtener información sobre un
producto.
Esta clase almacenará los datos de un producto y de las personas interesadas en obtener
información sobre el producto.
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
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];
}
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
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.
using FluentValidation.Results;
namespace UseCases.ContactASeller
{
public class ContactASellerOutputPort
{
public ValidationResult ValidationResult { get; set; }
public int? ProductId { get; set; }
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
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.
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.");
}
}
}
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);
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.
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:
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
Agregar el ViewModel
1. Agrega un nuevo directorio llamado ViewModels en el directorio Interface Adapters.
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.
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
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
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
using ContactASellerViewModels;
using System;
namespace ContactASellerConsoleViews
{
public class ContactASellerView
{
readonly ContactASellerViewModel Model;
public ContactASellerView(ContactASellerViewModel model) =>
Model = model;
Agregar el Controlador
using ContactASellerConsolePresenters;
using ContactASellerConsoleViews;
using MediatR;
using UseCases.ContactASeller;
namespace ContactASellerConsoleControllers
{
public class ContactASellerController
{
readonly IMediator Mediator;
readonly 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
4.
using Microsoft.Extensions.DependencyInjection;
using System;
namespace ConsoleClient
{
public static class ServiceContainer
{
static IServiceProvider ServiceProvider;
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
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());
Probar la funcionalidad
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
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
3. Agrega el siguiente código al final del método ConfigureServices del archivo Startup.cs.
services.AddServices();
[HttpPost("Contact-a-seller")]
public IActionResult Contact(ContactASellerInputPort request)
{
return Ok(Mediator.Send(request).Result);
}
7. Ejecuta la aplicación.
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
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()
};
}
[HttpPost("Contact-a-seller")]
public IActionResult Contact(ContactASellerInputPort request)
{
return new WebAPIPresenter()
.Format(Mediator.Send(request).Result);
}
3. Ejecuta la aplicación.
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
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
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.
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();
// 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>();
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>
<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();
});
});
app.UseCors("default");
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
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)