miércoles, 15 de enero de 2020

Capítulo 22: la arquitectura limpia - Clean Architecture

A lo largo de los último años han surgido varias ideas relacionadas con la arquitectura de sistemas. Este estas arquitecturas se incluyen:

·        La arquitectura hexagonal, también conocida como puertos y adaptadores, desarrollada por Alistair Cockburn, y adoptada por Steve Freeman y Nat Pryce en su maravilloso libro «Growing Object Oriented Software with Tests».
·        DCI de James Coplien and Trygve Reenskaug.
·        BCE, presentado por Ivar Jacobson en su libro «Object Oriented Software Engineering: A Use-Case Driven Approach».

Aunque todas estas arquitecturas varían en algunos de sus detalles ellas son muy similares. Todas tienen el mismo objetivo, la separación de aspectos. Todos ellos logran esta separación dividiendo el software en capas. Cada uno tiene al menos una capa de reglas de negocio y otra capa para el usuario y los interfaces de sistema.

Cada una de estas arquitecturas producen sistemas que tienen las siguientes características:

·        Independencia del frameworks. La arquitectura no depende de la existencia de alguna librería de cargado de software. Esto permite usar los frameworks como herramientas más que forzar al sistema a sus limitaciones. 
·        Testeable. Las reglas de negocio se pueden testear sin la UI, la base datos, el servidor o ningún otro elemento externo.
·        Independencia de la UI. La UI puede cambiar fácilmente sin que cambien las reglas de negocio.
·        Independencia de la base de datos. Las reglas de negocio no están vinculadas a una base de datos.
·        Independencia de cualquier agencia externa. En realidad, las reglas de negocio no saben nada sobre las interfaces del mundo exterior. 

El siguiente diagrama es un intento de integrar todas estas arquitecturas en un única idea.

La regla de la independencia

Los círculos concéntricos representan diferentes capas de software. El círculo más interno son las políticas de alto nivel mientras que los círculos más externos son las políticas de bajo nivel o mecanismos.

La principal regla que hace posible este sistema es la «regla de la independencia».       

Las reglas de la independencia deben apuntar sólo hacia dentro, hacia las políticas de alto nivel.

Ningún componente de un círculo interno debe saber nada de un círculo más exterior. En particular, el nombre de algo declarado en un círculo más exterior no se debe mencionar en un círculo más interior. Esto incluye funciones, clases, variables o cualquier otra entidad de software nombrada.

De la misma manera, los formatos de datos declarados en círculos externos no se deberían usar en círculos más internos especialmente aquellos formatos que son declarados por un framework en un círculo más externo. Nada de un círculo externo debe impactar a un círculo interno. 

Entidades

Las entidades encapsulan las conceptos críticos del negocio. Estas pueden ser clases con método o estructuras de datos con funciones. Las entidades se podrían utilizar en distintas aplicaciones.

Casos de uso

La capa de los casos de uso alberga las reglas de negocio. Un caso de uso orquesta el flujo de datos entre entidades. 

Los cambios en esta capa no deberían afectar a las entidades al igual que esta capa no debería verse afectada por cambios en detalles como la base de datos, interfaz de usuario, etc. La capa de uso está aislada de estos detalles.

Los cambios que afectan a las operaciones de la aplicación afectarán a los casos de uso y se verán reflejadas en esta capa. 

Adaptadores de interfaz

La capa de adaptadores de interfaz alberga un conjunto de adaptadores que convierten los datos de entrada al formato que esperan los casos de uso y los datos de salida del caso de uso al formato más conveniente para un agente externo como puede ser la base de datos o la web.

Esta capa es la que contendrá la arquitectura del MVC de una GUI. Los controladores, las vistas y modelos pertenecen a la capa de adaptadores de interfaz.

Por ejemplo, los datos que se envían desde un formulario se reciben en el controlador y se convierten en un formato que entienda el caso de uso. Si el caso de uso de uso requiere de un sistema de persistencia este no debería saber nada sobre el mismo. Si el sistema de persistencia es una base de datos SQL entonces todo el código SQL debería estar contenido en esta capa.

Frameworks y drivers

La capa más exterior alberga los detalles tales como la base de datos y el framework web. Generalmente, no se escribe mucho código en esta capa a excepción del código que une esta capa con la siguiente círculo interior.  

¿Sólo 4 círculos?

Los círculos no son más que una directriz. Habrá casos en los que se necesiten más capas. No obstante, estos círculos deben respetar la regla de la dependencia de manera que todas deben apuntar al interior del círculo siempre. El círculo más interno contiene las políticas de más alto nivel mientras que el círculo más interno contiene las políticas de más bajo nivel o detalles.

Cruzando límites

En la siguiente imágen se puede observar de como el flujo de ejecución cruza los límites de los círculos.
Esto muestra el controlador y al presentador comunicándose con los casos de uso estando los casos de uso en una capa más interna. El flujo de control comienza en el controlador, se mueve a través del caso de uso y termina ejecutándose en el presentador. También se pueden apreciar las dependencias de código: tanto el controlador como el presentador apuntan a los casos de uso.

Para llevar a cabo esta estrategia se utiliza el Principio de Inyección de Dependencias. 

Por ejemplo, si necesitamos hacer una llamada al presentador el caso de uso no puede hacer una llamada directa dado que esto vulneraría el Principio de Inyección de dependencias. Así que hay que hacer una llamada a la interface («Use Case Input Port») en el círculo interior y tener el presenter implementado en el círculo exterior.

Esta técnica es la misma que se usa para cruzar todos los límites en la arquitectura. Se usa el poliformismo para crear dependencias de código fuente que se oponen al flujo de control de manera que se puede aplicar la regla de la dependencia sin importar la dirección del flujo de control de viaje.

¿Cómo cruzan los límites los dátos?

La forma más usual en que los datos que cruzan los límites es en forma de estructuras de datos simples. También se pueden utilizar objetos simples o incluso simples parámetros en la llamada a la función. 

No se deben pasar ni entidades, ni filas de la base de datos, ni ningún tipo de estructura que tenga cualquier tipo de dependencia que obligue al círculo más interno a conocer esta. Por ejemplo, muchos frameworks de bases de datos devuelven un formato de datos en respuesta a una consulta, un “row structure”. Si se pasara esta estructura hacia un círculo más interno se estaría vulnerando la regla de la dependencia ya que se está obligando al círculo interior sobre el círculo exterior.

La regla es que, cuando se pasan datos entre límites, siempre se hace en el formato más conveniente para el círculo interno.

Un escenario típico

El siguiente escenario muestra un típico escenario para un sistema web usando una base de datos.
El servidor web recopila los datos de entrada del usuario y los entrega al controlador «Controller». El controlador empaqueta esos datos en una estructura de datos y pasa esta a través de «Input Boundary» al «Use Case Interactor». «Use Case Interactor» interpreta estos datos y usa esto para controlar el baile de las entidades. Este también usa el «Data Access Interface» para llevar los datos que se han usado por las entidades, que están  en memoria, a la base de datos. Al finalizar, el «Use Case Interactor» recopila datos de las entidades y construye la «Output Data» como otra estructura de datos. Estos datos se comunican a través de la interfaz «Output Boundary» al presentador.

El trabajo del presentador es reempaquetar los datos de «Output Data» en forma visible para «View Model» el cual es otra estructura de datos. El «View Model» contiene mayormente cadenas de texto y «flags» que «View» usa para mostrar los datos. Mientras que el «Output Data» podría contener los datos de tipo «Date», el presentador cargará el «ViewModel» con las cadenas correspondientes formateadas ya correctamente para el usuario. Lo mismo para cualquier otro dato como el tipo de moneda o cualquier otro dato relativo al negocio. Los nombres «Button» y «MenuItem» se colocan en el «View Model» así como los indicadores que le dicen a la vista si esos elementos deberían ser grises.

Esto deja a la vista con poco más que hacer que mover los datos desde la desde el «ViewModel» a una página HTML.

Se puede apreciar que todas las dependecias siempre cruzan los límites hacia el interior siguiendo la regla de la dependencia. 

Conclusión

Cumplir con estas normas no es muy difícil y esto ahorrará muchos dolores de cabeza en un futuro. Separando el software en capas y conformando la regla de la dependencia se creará un sistema que es intrínsecamente testeable con todos los beneficios que eso implica. 

Cuando cualquier parte externa del sistema queda obsoleta, ya sea la base de datos o el framework, se pueden reemplazar estos elementos con un esfuerzo mínimo.

No hay comentarios:

Publicar un comentario