Clean Architecture — Aislando los detalles

Andrés Mauricio Gómez P
Bancolombia Tech
Published in
11 min readMar 3, 2020

“ The goal of software architecture is to minimize the human resources required to build and maintain the required system” — Clean Architecture: A Craftsman’s Guide to Software Structure and Design, First Edition

Photo by STIL on Unsplash

A medida que el entorno tecnológico va madurando y evolucionando, es natural que las decisiones tomadas en un pasado estén sujetas a cambios en el presente, en busca de satisfacer las exigencias actuales y anticiparse a las del futuro.

Es común cuando desarrollamos software que se presenten situaciones donde la toma de decisiones sobre una u otra tecnología (BD, API, Queue o Framework) no haya sido analizada ni consolidada en argumentos claros y, por el contrario, obedecen al desconocimiento, presión de entrega o novedad tecnológica del momento.

La misma evolución tecnológica impacta los proyectos de software, donde decisiones tomadas en un pasado, no necesariamente malas, están de nuevo sujetas a cambios por el progreso natural de la tecnología, es decir, lo que estaba bien usar ayer, hoy es obsoleto o existen mejores alternativas.

Las situaciones anteriormente comentadas por lo regular desenlazan en cambios que afectan únicamente al detalle tecnológico (detalles de implementación como el uso de frameworks, librerías o protocolos de comunicación), sin impactar a las reglas, lógica y procesos establecidos por el negocio.

Es por ello que, con el pasar del tiempo, han surgido una variedad de arquitecturas que favorecen, protegen y aíslan las características esenciales y fundamentales de un sistema de software, de aquellos elementos auxiliares, provisionales o circunstanciales que la acompañan.

Arquitectura de Software

La arquitectura de un sistema puede entenderse como la estructura y las reglas que guían y gobiernan la organización de los componentes que lo conforman, así también, como los lineamientos sobre el diseño e implementación de dichos componentes.

Dicha estructura debe reflejar un propósito e intención, debe hablar y modelar los conceptos fundamentales del sistema y mostrar claramente sus relaciones.

“ The architecture of a software system is the shape given to that system by those who build it. The form of that shape is in the division of that system into components, the arrangement of those components, and the ways in which those components communicate with each other. The purpose of that shape is to facilitate the development, deployment, operation, and maintenance of the software system contained within it “ — Clean Architecture: A Craftsman’s Guide to Software Structure and Design, First Edition

Como lo comenta Robert C. Martin en su libro Clean Architecture, la arquitectura debe velar por la facilidad en el desarrollo, operación y mantenimiento del software. Arquitecturas como la Hexagonal, Onion y Clean han facilitado la forma en el cual se construye, extiende y soporta software en el tiempo, permitiendo retrasar la toma de decisiones hasta el momento oportuno y otorgando flexibilidad al momento de cambiar una decisión ya tomada.

“ A good architect pretends that the decision has not been made, and shapes the system such that those decisions can still be deferred or changed for as long as possible.”

Robert C. Martin nos recuerda que el software debe ser flexible y adaptarse al cambio, de tal manera que de forma sencilla y oportuna se altere el comportamiento de un sistema. Esta flexibilidad depende en gran medida de la forma, disposición e intercomunicación otorgada a esos componentes (Arquitectura).

La forma en el cual se puede construir software moldeable, es permitiendo que el conjunto de alternativas pueda ser intercambiado sin impactar excesivamente al sistema. Vamos a entender, alternativas, como aquellos detalles tecnológicos que son necesarios para el funcionamiento de un sistema pero que su cambio no debería impactar a las reglas establecidas por el negocio. Así por ejemplo, el tipo de base de datos, el protocolo de comunicación o el framework de desarrollo, son detalles alterables con el tiempo.gRPC

“A good architect maximizes the number of decisions not made”

Este desacople entre las reglas del negocio y la implementación tecnológica debe alcanzarse con estrategias que permitan controlar, pero no depender de esos detalles tecnológicos.

Políticas y Detalles

El Software es el medio por el cual se materializa y exponen funcionalidades requeridas por el negocio, definiéndose allí el QUÉ debe hacer un sistema, las reglas y procesos del negocio. En otras palabras, se definen las políticas de alto nivel.

“All software systems can be decomposed into two major elements: policy and details. The policy element embodies all the business rules and procedures. The policy is where the true value of the system lives”

Posteriormente, son definidos los mecanismos requeridos para poder materializar el sistema. Según la necesidad, se pueden requerir de sistemas de almacenamiento de información (Postgres, Mongo, S3), de comunicación (HTTP, gRPC, MQTT) y de implementación (Frameworks, librerías).

Cada uno de ellos tienen ciertos matices y beneficios que al fin y al cabo siguen siendo particularidades susceptibles de cambiarse en un futuro, detalles que no impactan a las decisiones y reglas establecidas por el negocio. Básicamente, estos mecanismos responden al CÓMO, y se les conoce también como políticas de bajo nivel.

“The goal of the architect is to create a shape for the system that recognizes policy as the most essential element of the system while making the details irrelevant to that policy. This allows decisions about those details to be delayed and deferred.”

Complejidad

Al igual que los detalles, otra característica que debería ser aislada es el tipo de complejidad. Cada sistema de Software, dispone de 2 complejidades: esencial y accidental.

Complejidad esencial

Es aquella que es inherente al problema que queremos solucionar, refleja la intención y el propósito por el cual fue creado el sistema y está expresada en los términos del dominio del problema. No es variable a no ser que cambie el dominio y el propósito del sistema.

Complejidad accidental

Es aquella que existe y es inyectada por la solución misma, es decir por los detalles y los medios por los cuales se materializa la solución, es altamente variable y depende en gran medida de detalles y decisiones tomadas al momento de crear la solución. Es muy cambiante, ya que un nuevo framework, protocolo de comunicación, base de datos ó dispositivos la afectan y pueden aumentarla.

Hasta el momento se han presentado argumentos que demuestran la necesidad de contar con una Arquitectura de Software que otorgue flexibilidad al momento de extender el software, de reducir el acople a la tecnología, de retrasar o alterar decisiones, que separe la complejidad esencial de la accidental, que refleje un propósito e intención del sistema, que nos permita aislar las políticas de los detalles.

Al final se debería contar con una arquitectura orientada y centrada en el dominio, que preserve las reglas y procesos definidos por el negocio. Una analogía que nos puede ayudar a aclarar estas propiedades, es la de Puertos y Adaptadores, donde los diferentes mecanismos tecnológicos (adaptadores) implementan una definición (puerto) que le permite al dominio intercambiar entre adaptadores sin mayor impacto.

Source: https://blog.octo.com/en/hexagonal-architecture-three-principles-and-an-implementation-example/

“The code that implements high-level policy should not depend on the code that implements low-level details. Rather, details should depend on policies.”

Inversión de dependencias

Para desacoplar la tecnología de las reglas del negocio, necesitamos utilizar mecanismos que nos permitan desde el código del dominio, controlar, pero no depender de los detalles. Dos patrones que nos permiten habilitar este aislamiento son:

  • Inversión de dependencias (DI)
  • Inyección de dependencias

Se resalta de la imagen anterior:

  • Sin DI, un recurso se acopla a las dependencias con las cuales se relaciona. Al cambiar una dependencia, se impacta al recurso que lo usa.
  • Con DI, un recurso define el comportamiento esperado por sus dependencias mediante una interfaz, esto le permite depender de una abstracción y no de una implementación.

Un ejemplo de inversión en java se puede apreciar a continuación:

La clase Library hace uso de un servicio para obtener el recurso Book. Al negocio no le debería impactar ni importar la forma en el cual se obtiene dicho recurso, si es desde un archivo, desde una base de datos o desde un sistema cuántico. Este desacople se logra mediante la interfaz IBook, interfaz que define el comportamiento deseado de toda implementación y quien otorga la flexibilidad para intercambiar entre los diferentes mecanismos.

La interfaz no otorga ninguna información relacionada al cómo ni dónde los datos son generados, lo cual lo hace ideal para desacoplar componentes.

Se destaca además la inyección realizada al nivel del constructor, de lo contrario, sería responsabilidad de la propia clase Library instanciar sus dependencias, quedando de nuevo acoplada.

Finalmente la inversión de dependencias nos permite controlar pero no depender de los detalles, invirtiendo el flujo de control y de dependencia.

Clean Architecture

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

El concepto de arquitectura limpia planteado por Robert. C. Martin, presenta un modelo conceptual para lograr la separación de responsabilidades mediante capas correctamente definidas. Sin embargo, al materializar esta propuesta, el abanico de posibilidades se expande, derivando en diferentes estrategias de implementación, unas con mayor grado de separación de responsabilidades que otras.

Es por ello que apoyándonos en esta idea, hemos materializado esta arquitectura mediante una estructura de proyecto concreta, definiendo relaciones de dependencias claras entre los distintos módulos que la componen, reconociendo al dominio y las reglas del negocio como su núcleo.

Dicha estructura refleja un proyecto Gradle multi módulo, donde cada módulo define de forma clara sus dependencias, respetando siempre la regla de dependencia. Esta declara que; nunca una capa interior e inferior deberá conocer ni depender de capas exteriores, limitando así la visibilidad de la infraestructura en la capa del dominio, evitando que se contamine de clases, librerías y tecnologías definidas en el exterior, susceptibles de cambio.

Hemos elegido como base un proyecto multi módulo, pues nos permite contar con una separación fuerte entre capas, permitiendo especificar de forma clara las dependencias y relaciones hacia otros módulos y librerías.

El diagrama a continuación presenta los diferentes módulos de la arquitectura planteada.

Diagrama genérico de un proyecto con Clean Architecture

Entities

Es el módulo gradle más interno de la arquitectura, pertenece a la capa del dominio y encapsula la lógica y reglas del negocio mediante modelos y entidades del dominio.

Estas representan los datos y objetos de valor del negocio sin llegar a ser un módulo compuesto sólo de estructuras de datos u objetos anémicos (sin comportamiento); sino que por el contrario es donde se recomienda expresar la lógica de negocio crítica a través de funciones puras (sin efectos secundarios).

Al ser el núcleo de la arquitectura, este módulo no depende nada más que de sí mismo. Al tener que relacionarse con su entorno, se ve obligado a definir el comportamiento esperado de la infraestructura mediante interfaces y depender únicamente de estas. A esta abstracción le conoceremos como gateways (puertos) del dominio y son fundamentales para desacoplar la tecnología del dominio; se tratarán de nuevo en la capa de infraestructura con el módulo de driven adapters.

UseCase

Este módulo gradle perteneciente a la capa del dominio, implementa los casos de uso del sistema, define lógica de aplicación y reacciona a las invocaciones desde el módulo de entry points, orquestando los flujos hacia el módulo de entities. Este último es su única dependencia, el cual le permite “hablar” exclusivamente en términos del dominio y no verse contaminado por la arquitectura y tecnología circundante.

La capa del dominio encapsula únicamente las reglas y procesos definidos por el negocio, buscando reunir y aislar la complejidad esencial del sistema. Al respetar la regla de dependencia anteriormente comentada, se protege al dominio de la tecnología y detalles del entorno.

Entry — Point

Módulo gradle perteneciente a la capa de infraestructura, define y agrupa el conjunto de adaptadores, lo que le permite exponer las capacidades de los casos de uso bajo cierta tecnología concreta (API, SOAP, CLI).

Reaccionan a señales y/o eventos del exterior, adaptando y transformando los datos de entrada (DTO) a un formato compatible con el lenguaje del dominio, posteriormente delega la ejecución a los useCases.

Driven-Adapters

Módulo gradle perteneciente a la capa de infraestructura, adapta y traduce al lenguaje de dominio, interacciones con subsistemas e infraestructura externa al dominio (API, BD, File). Dependen del módulo entities pues debe implementar las interfaces definidas allí por los gateways, los cuales le otorgan al sistema la flexibilidad para intercambiar entre adaptadores.

La capa de infraestructura al ser una frontera con el exterior, es altamente variable y dependiente de la tecnología, es por ello que esta capa reúne y aísla la complejidad accidental asociada al trabajar de forma directa con la tecnología.

Application

Este módulo es el más externo de la arquitectura, es el encargado de ensamblar los distintos módulos, resolver las dependencias y crear los UseCases, inyectando en éstos instancias concretas de las dependencias declaradas, además de iniciar la aplicación, es decir es el único módulo del proyecto donde encontraremos la función “public static void main(String[] args)”.

Al final deberíamos contar con un proyecto gradle multi módulo, con una estructura que respeta la separación de responsabilidades entre el Dominio, la Infraestructura y la Aplicación, donde al nivel del dominio se definen los modelos y casos de uso del negocio, y donde la infraestructura especifica los puntos de entrada al sistema y los adaptadores hacia sistemas auxiliares.

Finalmente, este tipo de arquitectura está orientada a la mantenibilidad y eficiencia en el desarrollo de software. Además, reconoce al dominio como el centro de la arquitectura, lo que le permite proteger y aislar las reglas del negocio de un entorno cambiante como lo es la tecnología que la rodea.

Por último, reflejan la intención y propósito del sistema de forma clara. Todo esto en busca de garantizar una evolución sostenible de los sistemas de software desde el diseño y construcción del mismo.

Scaffold Clean Architecture Plugin

Pensando en la facilidad para generar una estructura base de Clean Architecture para proyectos java, en Bancolombia hemos creado el plugin scaffold-clean-architecture.

Esta iniciativa Open Source, busca agilizar y simplificar la forma en el cual se pueden generar proyectos con esta arquitectura. — ¿Has leído nuestro artículo sobre Open Source? —

Para utilizarla, lo primero que se debe realizar es crear un archivo build.gradle, el cual debe definir las siguientes líneas asegurándose de utilizar la versión más reciente:

plugins {
id "co.com.bancolombia.cleanArchitecture" version "1.5.3"
}

Ejecutamos gradle tasks para visualizar el conjunto de tareas que este nos ofrece.

gradle tasks

Para generar una estructura base podemos hacer uso del siguiente comando, especificando el nombre del paquete, el tipo imperativo o reactivo, y el nombre del proyecto.

gradle cleanArchitecture --package=co.com.bancolombia --type=imperative --name=NameProject
generación de la estructura base

El plugin ofrece otro conjunto de comandos dentro de los cuales se destaca:

gradle generateModel --name=[modelName]gradle generateUseCase --name=[useCaseName]gradle validateStructure

Para los siguientes comandos, se ofrecen un listado de adaptadores, que está en constante evolución. Para identificar el valor adecuado, apoyarse en la documentación de la librería.

gradle generateDrivenAdapter --value=[referenceNumberDrivenAdapter]gradle generateEntryPoint --value=[referenceNumberEntryPoint]
Photo by Hitesh Choudhary on Unsplash

Finalmente, invitamos a la comunidad de desarrolladores a realizar sus aportes y propuestas en la evolución de esta iniciativa en nuestro GtiHub:

Scaffold Clean Architecture Plugin.

--

--