Sei sulla pagina 1di 13

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

html

https://github.com/ivanpaulovich/clean-architecture-manga

https://github.com/edittler/clean-architecture

Robert Martin (Uncle Bob) analizó las propuestas de las arquitecturas en capas, hexagonal y
screaming (Domain Driven Design), y las combinó para dar lugar a la “arquitectura limpia”.

Robert Martin, más conocido como Tío Bob (Uncle Bob en inglés), plantea que la
arquitectura de un software debe gritar cuál es el uso de dicho software. Así como al ver
planos de edificios, si uno ve que el edificio posee un baño, cocina, comedor,
dormitorios, la arquitectura grita que es una casa familiar. De esta manera, al visualizar
la arquitectura de un software, se debería notar cual es el uso del mismo. La arquitectura
no debería indicar cuál es el framework o herramientas usadas para su construcción.

Volviendo al arquitecto de edificios, el mismo se asegura que un edificio cumpla con


los casos de uso antes de decidir qué materiales se usarían para su construcción. En el
caso del desarrollo de software, cumplimentar esto, asegura que el producto satisfaga
los casos de uso y retrase la decisión sobre los frameworks o herramientas a usar.
Incluso, una buena arquitectura basada en casos de usos permite que sea fácil cambiar la
herramienta que se va a usar.

CONCEPTOS PREVIOS

Arquitectura Hexagonal

La arquitectura hexagonal (también conocida como puertos y adaptadores) es una estrategia


para desacoplar los casos de uso de los detalles externos. Fue acuñado por Alistar Cockburn
hace más de 13 años, y esto recibió mejoras con las arquitecturas Onion y Clean.

El objetivo de esta arquitectura es permitir que una aplicación sea dirigida por usuarios,
programas o pruebas, y desarrollada y probada de forma aislada de cualquiera de sus
dispositivos y bases de datos en tiempo de ejecución.

Con este estilo tenemos:

- Un dominio de negocios independiente para incorporar las reglas comerciales


detalladas.
- Casos de uso que interactúan con el dominio e independientes de los servicios externos.
- Interfaces que proporcionan puertos.
- Adaptadores que proporcionan implementaciones de marcos, acceso a datos y UI.
- Externamente el usuario, otros sistemas y servicios.
Una forma de explicar la arquitectura hexagonal es por sus formas. Echa un vistazo a la
siguiente imagen:

- La forma de patata azul en el centro es la capa de dominio y hay razones para ello. Cada
dominio de negocios tiene sus propias reglas, reglas de negocios muy específicas, que
es la razón de su forma indefinida. Por ejemplo, diseñé nuestra capa de dominio con
bloques de construcción DDD.
- La aplicación tiene una forma hexagonal porque cada uno de sus lados tiene protocolos
específicos.
- Los puertos y adaptadores se implementan fuera de la aplicación como complementos.
- Externamente tenemos otros sistemas.

El hexagonal se divide en izquierda y derecha. A la izquierda implementamos los actores


impulsores y a la derecha implementamos los actores secundarios.

La dirección de las dependencias va hacia el centro, por lo que la capa de dominio no conoce la
capa de aplicación, pero la capa de aplicación depende del dominio, la misma regla se aplica a
las capas externas.

Reglas de negocio y casos de uso

Las reglas de negocio son las reglas detalladas, que encapsulan los campos y restricciones de la
entidad. También las reglas de negocios son los casos de uso que interactúan con múltiples
entidades y servicios. Juntos crean un proceso en la aplicación, deben mantenerse durante
mucho tiempo.

Las reglas de negocio ganarían o ahorrarían dinero a la empresa, independientemente de si se


implementan en una computadora. Ganarían o ahorrarían dinero incluso si fueran ejecutados
manualmente.
En la era DDD, tenemos patrones para describir las reglas de negocios con Entidades, Objetos
de Valor, Agregados, Servicios de Dominio, etc. Son una combinación perfecta con la
arquitectura hexagonal.

Las reglas de negocios y los casos de uso deben implementarse dentro de la capa de aplicación
y deben mantenerse durante la vida del proyecto. Por otra parte, todo lo que da soporte para
capacidades externas son solo detalles externos, pueden ser reemplazados por diferentes
razones, y no queremos que las reglas de negocio se acoplen a ellas.

Detalles externos

En la mayoría de los escenarios, podemos aplazar la implementación de detalles externos y


seguir manteniendo el progreso del desarrollo. Si su respuesta es afirmativa para cualquiera de
las siguientes preguntas, probablemente esté tratando con detalles periféricos:

- ¿La aplicación necesita una base de datos para persistir el estado?


- ¿La aplicación requiere una interfaz de usuario?
- ¿La aplicación consume una API externa?

Estos son detalles externos comunes que pueden ser burlados, falsificados o que su
implementación concreta puede ser reemplazada por diferentes motivos. Le sugiero que
aplace su implementación mientras descubre más del dominio. Tenga en cuenta la cita del tío
Bob:

“Una buena arquitectura permite aplazar decisiones importantes y un buen arquitecto


maximiza la cantidad de decisiones que no se toman.”

Visual Studio facilita la adición de bibliotecas para Reflexión, Serialización, Seguridad y muchos
otros paquetes Nuget en nuestros proyectos. El problema comienza cuando agregamos estas
bibliotecas a nuestra aplicación y dominio. Estas bibliotecas son solo detalles y deben quedar
fuera de la capa de aplicación.

¿Qué deberíamos hacer?

Deja de ir por los marcos brillantes.

Deja de escribir clases con herencia de los marcos.

Concéntrese en las reglas de negocios, aclárelas en sus capas de aplicación y dominio.

No caiga en trampas de herramientas como los scaffolding.

Crear la abstracción adecuada para estas preocupaciones periféricas.

Principio de inversión de dependencia (DIP)

En el siguiente ejemplo, el DIP se aplicó al desacoplar nuestros casos de uso de los


repositorios. Recordemos el DIP y luego navegaremos a través de un ejemplo:
Los módulos de alto nivel no deben depender de módulos de bajo nivel. Ambos deben
depender de las abstracciones. Las abstracciones no deben depender de los detalles. Los
detalles deben depender de las abstracciones.

Veamos cómo apliqué este principio en el siguiente ejemplo:

En el lado izquierdo encontramos en rojo una aplicación en capas en la que DepositUseCase


depende de la implementación de AccountSQLRepository. Es una forma acoplada de escribir
código. En el lado derecho, en azul, al agregar un IAccountRepository y aplicar DIP, el
AccountSQLRepository tiene su dependencia apuntando hacia adentro.

El DepositUseCase es el módulo de alto nivel que no depende de los detalles de la base de


datos, sino que depende de la abstracción de IAccountRepository.

IAccountRepository es la abstracción que no depende de los detalles de la base de datos.

El AccountSQLRepository es el módulo de bajo nivel que depende de la abstracción


IAccountRepository.

Esa es la idea principal detrás de la Arquitectura Hexagonal, siempre que nuestra aplicación
requiera un servicio externo, usamos el Puerto (una interfaz simple) e implementamos el
Adaptador detrás de la abstracción.

Separación de preocupaciones (SoC)

Nuestra aplicación requiere algunas capacidades externas, pero a la aplicación no le preocupan


los detalles de su implementación, solo sus abstracciones son visibles para la capa de la
aplicación. Aplicamos SoC creando límites alrededor de los adaptadores y permitiendo que se
desarrollen y prueben de forma aislada. Es una buena práctica tener paquetes diferentes para
cada implementación de Adaptador.
Podríamos tener un Adaptador específico para una Base de datos SQL y un Adaptador
específico para el Almacenamiento de Azure, ambos podrían reemplazarse con poco esfuerzo.
Esa es la idea detrás de la arquitectura hexagonal, mantener las opciones abiertas el mayor
tiempo posible y la capacidad de retroceder si es necesario.

ARQUITECTURA LIMPIA

Todas las arquitecturas tienen el mismo objetivo común, que es la “separación de intereses”,
para dar como resultado sistemas que son:

 Independientes de frameworks: no nos atamos a ninguna librería de software ni al


soporte de los proveedores.
 Testeables: las reglas de negocio se pueden probar sin la interfaz de usuario, BD,
servidor web u otro elemento externo.
 Independiente de la interfaz de usuario: podría cambiarse fácilmente
 Independiente de la BD: las reglas de negocio no están vinculadas a la tecnología de
BD utilizada
 Independiente de cualquier agente externo: de hecho, las reglas de negocio no
conocen nada sobre el mundo exterior.
La regla de la dependencia
Los círculos concéntricos representan diferentes áreas del software. En general, cuanto más
alejados, más alto será el nivel del software. Los círculos exteriores son mecanismos. Los
círculos internos son políticas.

La regla principal que hace que esta arquitectura 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 acerca de algo en un círculo exterior. En particular, el
nombre de algo declarado en un círculo exterior no debe ser mencionado por el código en el
círculo interno. Eso incluye, funciones, clases, variables, o cualquier otra entidad de software
nombrada.

De la misma manera, los formatos de datos utilizados en un círculo exterior no deben ser
utilizados por un círculo interno, especialmente si esos formatos son generados por un
framework en un círculo externo. No queremos que nada en un círculo exterior afecte los
círculos internos.

Entidades
Las entidades encapsulan las reglas de negocio de toda la empresa. Una entidad puede ser un
objeto con métodos, o puede ser un conjunto de funciones y estructuras de datos. No importa,
siempre y cuando las entidades puedan ser utilizadas por muchas aplicaciones diferentes en la
empresa.

Si no tiene una empresa y solo está escribiendo una aplicación, estas entidades son los objetos
comerciales de la aplicación. Encapsulan las reglas más generales y de alto nivel. Es menos
probable que cambien cuando algo externo cambia. Por ejemplo, no esperaría que estos
objetos se vieran afectados por un cambio en la navegación de la página o por la seguridad.
Ningún cambio operacional en ninguna aplicación en particular debe afectar la capa de la
entidad.

Casos de uso
El software en esta capa contiene reglas de negocio específicas de la aplicación. Encapsula e
implementa todos los casos de uso del sistema. Estos casos de uso orquestan el flujo de datos
hacia y desde las entidades, y dirigen a esas entidades a utilizar sus reglas de negocios en toda
la empresa para lograr los objetivos del caso de uso.

No esperamos que los cambios en esta capa afecten a las entidades. Tampoco esperamos que
esta capa se vea afectada por cambios en las externalidades, como la base de datos, la interfaz
de usuario o cualquiera de los marcos comunes. Esta capa está aislada de tales
preocupaciones.
Sin embargo, esperamos que los cambios en el funcionamiento de la aplicación sí afecten a los
casos de uso y, por lo tanto, el software en esta capa. Si los detalles de un caso de uso
cambian, entonces algunos códigos en esta capa ciertamente se verán afectados.

Adaptadores de interfaz
El software en esta capa es un conjunto de adaptadores que convierten los datos desde el
formato más conveniente para los casos de uso y las entidades, al formato más conveniente
para algún agente externo, como la BD o la Web. Es esta capa, por ejemplo, la que contendrá
por completo la arquitectura MVC de una GUI. Los presentadores, vistas y controladores
pertenecen todos a esta capa. Es probable que los modelos sean sólo estructuras de datos que
pasan de los controladores a los casos de uso y luego regresan de los casos de uso a los
presentadores y las vistas.

De manera similar, los datos se convierten, en esta capa, desde la forma más conveniente para
las entidades y los casos de uso, a la forma más conveniente para cualquier estructura de
persistencia que se esté utilizando, es decir, la base de datos. Ningún código hacia el interior
de este círculo debe saber nada sobre la base de datos. Si la base de datos es una base de
datos SQL, entonces todo el SQL debe estar restringido a esta capa, y en particular a las partes
de esta capa que tienen que ver con la base de datos.

También en esta capa se encuentra cualquier otro adaptador necesario para convertir datos de
algún formulario externo, como un servicio externo, al formulario interno utilizado por los
casos de uso y las entidades.

Frameworks (marcos) y Drivers (controladores).


La capa más externa generalmente se compone de marcos y herramientas como la Base de
datos, el Marco web, etc. Generalmente, no se escribe mucho código en esta capa que no sea
el código de cola que se comunica con el siguiente círculo hacia adentro.

Esta capa es donde van todos los detalles. La web es un detalle. La base de datos es un detalle.
Mantenemos estas cosas en el exterior donde pueden hacer poco daño.

¿Sólo cuatro círculos?


No, los círculos son esquemáticos. Puedes encontrar que necesitas más que estos cuatro. No
hay una regla que diga que siempre debes tener solo estos cuatro. Sin embargo, la regla de
dependencia siempre se aplica. Las dependencias del código fuente siempre apuntan hacia
adentro. A medida que avanzas, el nivel de abstracción aumenta. El círculo más exterior es el
detalle de hormigón de bajo nivel. A medida que avanza hacia el interior, el software se vuelve
más abstracto y encapsula políticas de nivel superior. El círculo más interno es el más general.

Cruzando las fronteras.


En la parte inferior derecha del diagrama hay un ejemplo de cómo cruzamos los límites del
círculo. Muestra a los Controladores y Presentadores que se comunican con los Casos de Uso
en la siguiente capa. Tenga en cuenta el flujo de control. Comienza en el controlador, se
mueve a través del caso de uso y luego termina ejecutándose en el presentador. Tenga en
cuenta también las dependencias del código fuente. Cada uno de ellos apunta hacia adentro
hacia los casos de uso.

Usualmente resolvemos esta aparente contradicción usando el principio de inversión de


dependencia. En un lenguaje como Java, por ejemplo, organizaríamos interfaces y relaciones
de herencia de modo que las dependencias del código fuente se opongan al flujo de control en
los puntos correctos a lo largo del límite.

Por ejemplo, considere que el caso de uso debe llamar al presentador. Sin embargo, esta
llamada no debe ser directa porque eso violaría la Regla de dependencia: ningún nombre en
un círculo exterior puede ser mencionado por un círculo interno. Así que tenemos el caso de
uso, llamar a una interfaz (que se muestra aquí como Puerto de salida del caso de uso) en el
círculo interno, y hacer que el presentador en el círculo externo lo implemente.

La misma técnica se utiliza para cruzar todos los límites en las arquitecturas. Aprovechamos el
polimorfismo dinámico para crear dependencias de código fuente que se oponen al flujo de
control para que podamos cumplir con la Regla de dependencia, sin importar en qué dirección
vaya el flujo de control.

Qué datos cruzan los límites.


Normalmente, los datos que cruzan los límites son estructuras de datos simples. Puede usar
estructuras básicas o simples objetos de transferencia de datos si lo desea. O los datos pueden
ser simplemente argumentos en llamadas a funciones. O puede empaquetarlo en un mapa
hash, o construirlo en un objeto. Lo importante es que las estructuras de datos aisladas y
simples se pasan a través de los límites. No queremos engañar y pasar Entidades o filas de
bases de datos. No queremos que las estructuras de datos tengan ningún tipo de dependencia
que infrinja la Regla de dependencia.

Por ejemplo, muchos frameworks de bases de datos devuelven un formato de datos


conveniente en respuesta a una consulta. Podríamos llamar a esto una Estructura de Fila. No
queremos pasar esa estructura de fila hacia el interior 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
exterior.

Entonces, cuando pasamos datos a través de un límite, siempre será en la forma más
conveniente para el círculo interior.

Conclusión
Cumplir con estas reglas simples no es difícil, y le ahorrará muchos dolores de cabeza en el
futuro. Al separar el software en capas y cumplir con la Regla de dependencia, creará un
sistema que es intrínsecamente comprobable, con todos los beneficios que esto implica.
Cuando cualquiera de las partes externas del sistema se vuelven obsoletas, como la base de
datos o el marco web, puede reemplazar esos elementos obsoletos con un mínimo de
esfuerzo.

POSIBLES IMPLEMENTACIONES

CAPA APPLICATION

Vamos a profundizar en las Reglas de Negocios de Aplicación implementadas por los Casos de
Uso en nuestro Contexto Limitado. Como dijo el tío Bob en su libro Arquitectura limpia:

“Del mismo modo que los planes para una casa o una biblioteca gritan sobre los casos de
uso de esos edificios, también lo debe hacer la arquitectura de una aplicación de software
sobre los casos de uso de la aplicación.”

Las implementaciones de casos de uso son módulos de primera clase en la raíz de esta capa. La
forma de un Caso de Uso es un objeto Interactor que recibe una Entrada, haga un trabajo y
luego pase la Salida a través de la persona que llama. Esa es la razón por la que soy un
defensor de las carpetas de características que describen los casos de uso y dentro de ellas las
clases necesarias:
Con un primer vistazo de las carpetas de soluciones, se puede crear una idea del propósito de
este software. Parece que puede administrar su cuenta de banco, por ejemplo, puede
depositar o retirar dinero. A continuación vemos la comunicación entre las capas:

La aplicación expone una interfaz (puerto) a la capa UI y otra interfaz (otro puerto) a la capa de
infraestructura. Lo que ha visto hasta aquí es que Enterprise + Application Business Rules se
aplica sin dependencias de marcos o sin acoplamiento de base de datos. Todos los detalles
tienen abstracciones que protegen las Reglas de Negocios que deben ser acopladas a cosas
tecnológicas.

CAPA UI (Adaptadores para la interfaz de usuario)

Ahora avanzamos a la siguiente capa, en la Capa de Interfaz de Usuario traducimos la entrada


de manera que los Casos de Uso puedan entender, es una buena práctica no reutilizar las
entidades en esta capa porque podría crear un acoplamiento, el front-end tiene marcos
específicos, otras formas de crear sus estructuras de datos, presentación diferente para cada
campo y reglas de validación.

En nuestra implementación tenemos las siguientes carpetas de características para cada caso
de uso:
- Request: una estructura de datos para la entrada del usuario (accountId y
monto).
- Un controlador con una acción: este componente recibe el
DepositRequest, llama al caso de uso de depósito apropiado, el cual
realiza un procesamiento y luego pasa la salida a través de la instancia de
Presenter.
- Presentador: convierte la salida al modelo.
- Modelo: esta es la estructura de datos de retorno para aplicaciones MVC.

Debemos resaltar que el Controlador conoce el Caso de uso de depósito y


no está interesado en la Salida, en lugar de eso, el Controlador delega la
responsabilidad de generar un Modelo para la instancia del Presentador.

CAPA INFRAESTRUCTURE (Adaptadores para la infraestructura)


Otra capa externa es la capa de infraestructura que implementa acceso a datos, marco de
inyección de dependencias (DI) y otras especificaciones de marcos. En este ejemplo tenemos
múltiples implementaciones de acceso a datos.

How and When the DI is configured


Agrupamos la DI por módulos, por lo que tenemos un módulo para el acceso a datos de Entity
Framework que requiere una cadena de conexión como esta:
public class Module : Autofac.Module
{
public string ConnectionString { get; set; }

protected override void Load(ContainerBuilder builder)


{
var optionsBuilder = new DbContextOptionsBuilder<DbContext>();
optionsBuilder.UseSqlServer(ConnectionString);
optionsBuilder.EnableSensitiveDataLogging(true);

builder.RegisterType<Context>()
.WithParameter(new
TypedParameter(typeof(DbContextOptions), optionsBuilder.Options))
.InstancePerLifetimeScope();

//
// Register all Types in EntityFrameworkDataAccess namespace
//

builder.RegisterAssemblyTypes(typeof(InfrastructureException).Assembly
)
.Where(type =>
type.Namespace.Contains("EntityFrameworkDataAccess"))
.AsImplementedInterfaces()
.InstancePerLifetimeScope();
}
}

Hay otros módulos en el mismo código base y podemos ejecutarlos cambiando


autofac.entityframework.json, una forma conveniente de configurar los módulos deseados.
{
"defaultAssembly": "Manga.Infrastructure",
"modules": [
{
"type": "Manga.Infrastructure.Modules.WebApiModule",
"properties": {
}
},
{
"type": "Manga.Infrastructure.Modules.ApplicationModule",
"properties": {
}
},
{
"type": "Manga.Infrastructure.InMemoryDataAccess.Module"
}
]
}

El autofac.json está configurado desde el principio en Program.cs. ¡Como debería ser!

Potrebbero piacerti anche