En esta serie de 3 artículos se va a mostrar como poder disponer de una herramienta / tecnología para el stack tecnológico de Java que cubra los siguientes objetivos : ayudar a validar la arquitectura con la que se ha pensado desarrollar una aplicación, que identifique las reglas de arquitectura (reglas arquitectónicas) utilizadas, que su uso sea mayormente desatendido (que no requiera casi ninguna intervención humana mediante auditorias, revisiones o validaciones manuales), que se pueda integrar fácilmente en el código, que se pueda usar junto al proceso de construcción y que trate de adelantar en una fase temprana del desarrollo cualquier problema que se detecte.
Actualmente existen muchas herramientas/tecnologías en el mercado para "hacer esto" y en función del objetivo para el que fueron diseñadas tratan de cubrir diferentes aspectos:
- Analizar el código estático
- Analizar algún aspecto de la calidad del código: olores, complejidad, etc.
- Analizar el linting
- Analizar el formato
- Validar la estructura de un directorio o un fichero
- Validar el contenido de un fichero
- Evaluar las dependencias y/o tecnología utilizadas
- Etc.
Casi siempre se necesita cubrir varios de estos aspectos y con una sola herramienta NO nos suele servir, por lo que se requiere la combinación de varias.
Anécdota
Durante todos mis años de desarrollo he conocido muchas herramientas que sirven para esto. Y casi siempre me ha pasado que lo que necesitaba NO estaba cubierto (¿os suena?), así que como pasa siempre en estas cosas...uno se lo tenía que hacer a medida:
- Creando cosas nuevas: shell scripting, utilidades, librerías, etc.
- Adaptando cosas que ya existen: shell scripting, tunear plugins, API de Reflection, etc.
- Combinando uno o más lenguajes: Java, Groovy, Kotlin, Python, ...
- ...
Así que no descartéis nunca esta opción :-)
De momento para evitar ciertas complicaciones dejaremos de lado la opción de "hacer las cosas a medida (artesanalmente)" y trataremos de elegir alguna herramienta que cubra la gran mayoría de los objetivos buscados.
Para facilitar todo esto hemos elegido ... ArchUnit
Este primer artículo (más teórico -> todo un clásico en mis artículos) servirá para ayudar a ubicar el contexto donde el uso de esta herramienta aportará más valor.
Este artículo está dividido en 2 partes:
- 1. Introducción
- 1.1. ¿Qué aporta definir una Arquitectura?
- 1.2. Problemas "típicos" derivados de la definición de arquitecturas
- 2. Conclusiones
1. Introducción
En este apartado se tratarán los siguiente puntos :
- 1.1. ¿Qué aporta definir una Arquitectura?
- 1.2. Problemas "típicos" derivados de la definición de arquitecturas
1.1. ¿Qué aporta definir una Arquitectura?
Definir una Arquitectura = Tomar Decisiones
Photo by Cenk Batuhan Özaltun on Unsplash
Definir una arquitectura software (de una aplicación, sistemas o componente) facilita la forma de entender la estructura de los componentes software que se van a utilizar y sus relaciones.
Todo esto permite garantizar la operatividad y la calidad del servicio de los sistemas implicados desde el momento en que se crean hasta el momento en que dejan de usarse o mueren (acaban su tiempo de vida / uso).
Hay que entender que resuelve más aspectos: reduce el riesgo tecnológico del proyecto, permite cubrir los requerimientos funcionales y no funcionales, establece el contexto de uso, ayuda en la definición de los límites (restricciones), identifica las consideraciones o aspectos a asumir, establece los actores implicados, homogeneiza los desarrollos, define procedimientos, etc.
Estilos Arquitectónicos
Photo by Shane Rounce on Unsplash
Existen una gran cantidad de estilos arquitectónicos (NO confundir con patrones arquitectónicos) en los que basarse para definir una arquitectura: Monolítico, Monolítico distribuido (cliente / servidor), Basado en Componentes, Basado en Capas, Orientación a Servicio, ESB, Microservicios, Filtros Y Tuberías, Basado en Eventos, Productor / Consumidor, API Manager, Onion, Hexagonal (Puertos y Adaptadores), Serverless, etc.
¡Los estilos arquitectónicos son muchísimos!
Todos estos estilos se pueden implementar de diferentes maneras, con diferentes entornos tecnológicos, cubriendo diferentes requerimientos y con diferentes procedimientos, políticas, prácticas, etc..
Además de que algunos se pueden mezclar generando híbridos.
¡Los estilos arquitectónicos son casi infinitos! :-)
Hay que tener en cuenta varios aspectos :
- Cada enfoque de estilo arquitectónico resuelve un problema técnico concreto
- No existe un estilo arquitectónico que resuelva todo
- Todo tiene pros y contras
Por lo tanto, pueden existir muchas opciones (variabilidad inmensa) de arquitecturas resultantes.
Las señales de una Arquitectura
Photo by Chris Leipelt on Unsplash
Cuando una arquitectura esta "bien" hecha y sobre todo cuando cumple estándares, convenciones y demás, esta emite una serie de señales.
Ejemplos de Señales:
- Organización (distribución de todo lo que incluye un proyecto-> clases, compilados, testing, ficheros de configuración, recursos, etc.)
- Nomenclatura de proyectos, paquetes, clases, atributos, métodos, etc.
- Tecnologías "bien" elegidas
- Convenciones de código
- Patrones bien diseñados
- Cumplimiento de requisitos funcionales y no funcionales
- No existe un acoplamiento descontrolado a menos que se busque eso
- ...
Sobre esto existen muchos artículos en Internet por si queréis seguir investigando
El cumplimiento de estas señales al final genera una sensación de "confianza", que se traduce en la idea de que esa arquitectura se puede mantener "sin mucha dificultad" (aunque esto puede que no sea cierto) -> mantenibilidad.
Si algo se puede mantener entonces (con mayor o menor dificultad) se puede:
- Cambiar algo que ya existe (nuevo comportamiento o solucionar un problema) -> "sustituibilidad"
- Añadir algo nuevo que no existe -> "extensibilidad"
- Ampliar algo que ya existe -> "extensibilidad"
- Eliminar duplicidades -> "reutilización"
Cuando esas señales no aparecen (o no están claras) y la sensación de mantenibilidad es nula, entonces aparecen esas palabras que tanto miedo nos dan a los desarrolladores (olores de mala arquitectura) y que tanto cuestan a las empresas: código repetido, complejidad innecesaria, rigidez, fragilidad, viscosidad, etc.
¿Cúanto "cariño" le damos a una Arquitectura?
Photo by Marek Studzinski on Unsplash
Por regla general, el tema de la arquitectura es un aspecto al que NO se le suele dar "mucho cariño", una vez que esta creada y estabilizada (en algunos casos esto pueden llevar años) apenas se vuelve a tocar.
Viendo esto, os lanzo un par preguntas:
- ¿Cuántas empresas se atreven a evolucionar una arquitectura ya implementada y utilizada actualmente?
- ¿Por qué algo tan crítico y sobre el que nos hemos dejado tanto dinero deja de tener tanta importancia?
- ¿Por qué el desarrollo de una arquitectura esta "parado" si el negocio y el desarrollo de funcionalidad continua "avanzando"?
Auditorías de Código
Photo by James Sutton on Unsplash
A lo anterior hay que añadir que estamos acostumbrados a verificar / validar las arquitecturas mediante auditorias manuales de código (internas y/o externas, pair reviews, etc.), que llevan trabajo y el trabajo se traduce en "costes" -> dinero.
Hay que "conocer" muchas cosas para poder hacer una auditoría de código "bien" :
- Tecnologías
- Formas de desarrollo
- Contextos corporativos o de proyecto del porqué se hicieron las cosas de una determinada manera
- Las decisiones tomadas por los arquitectos y desarrolladores en el momento del desarrollo
- Procedimientos de desarrollo de ese momento
- Cosas que estaban de moda
- ...
Normalmente las auditorías manuales de código se hacen pocas veces o ninguna (lo habitual) salvo que aparezca un problema serio. Y si se hacen, estas pueden descubrir las "pelusas de debajo del sofá" lo que se traduce en "costes" extra para corregir lo encontrado e incluso plantear la opción de "hacerlo desde 0" (todo un clásico).
Por lo tanto, parece sensato disponer de alguna herramienta durante el desarrollo que de forma automática pueda ir verificando el cumplimiento de la arquitectura en cada momento -> verificación automatizada de la arquitectura.
1.2. Problemas "típicos" derivados de la definición de arquitecturas
Algunos de los problemas más habituales que nos encontramos a la hora de definir una arquitectura son los siguientes:
- "No decir toda la verdad" sobre el código existente
- "Buenas intenciones" sólo en documentos o presentaciones
- Los diagramas siempre funcionan
- Jugar con arquitecturas "Age of Empires"
- Paquete de utilidades para todo
Tengo que decir que existen muchos más :-(
1.2.1. "No decir toda la verdad" sobre el código existente
Photo by Jametlene Reskp on Unsplash
Años de experiencia en consultoría trabajando con arquitecturas me han llevado a utilizar la siguiente estrategia personal : "creerme a medias" lo que me cuenta de palabra un cliente sobre una arquitectura ya existente, una nueva arquitectura a construir o una integración con otra.
(Espero que nadie se lo tome mal, pero he vivido experiencias bastante curiosas al respecto y creo que mi falta de pelo lo puede demostrar...jejeje)
La mejor manera de verificar una arquitectura es siempre ver el código y ver algo funcionando con ella (si se puede).
"El código siempre dice la verdad"
Aconsejo considerar estas 3 premisas:
- El código NO miente -> Lo que esta escrito es lo que hace (otra cosa es que sea difícil de entender)
- El código que NO este escrito implica que NO existe (os sorprendería las anécdotas que tengo para contar en este punto)
- El código que esta comentado se trata como el código que NO esta escrito (y a poder ser se evita tener código comentado en producción)
Aprovecho a lanzar una pregunta :
- ¿Cuántas veces os ha sucedido que el código del sistema software que estáis implementando difiere del diseño y arquitectura originalmente planteados?
Esta situación puede ser un problema bastante común, especialmente en aquellas aplicaciones de grandes dimensiones.
1.2.2. "Buenas intenciones" sólo en documentos o presentaciones
Photo by Harry Quan on Unsplash
Casi siempre que un proyecto empieza, dando igual el motivo que lo originó (proyecto nuevo, evolutivo, correctivo, etc.), en alguna de las primeras reuniones aparecen documentos, wikis, presentaciones, correos, comentarios, entrevistas, etc. donde se habla de arquitectura (si se tiene...jejeje).
Normalmente, con todo lo anterior se trata de "demostrar" : lo buena y bonita que es o va a ser la arquitectura a utilizar, lo bien actualizada que está o va estar, la cantidad de estrategias que se van a seguir para desacoplar todo (dependencias, módulos, servicios, etc.) o bien que ya cumple, todos los estándares que adapta o adaptará y la gran cantidad de buenas prácticas de todo tipo que se van a seguir (codificación, nomenclatura, layers, etc.).
Por lo que cuando se "aterriza" en el proyecto uno se queda sorprendido de lo poco que se parece lo que nos encontramos a lo que aparecía en esa "información inicial ideal". Esto hace pensar en las buenas intenciones que tenían esos documentos y que en algún momento "paso algo" para que se dejarán de cumplir y así uno llega a esa situación que se encuentra al aterrizar.
Llegados a este punto aparecen varias opciones:
- NO se ha actualizado la documentación
- NO se ha dicho la verdad en la documentación
- NO se ha dicho la verdad sobre el código existente
Un problema común al que se enfrentan las organizaciones a la hora de desarrollar es que las implementaciones de código a menudo pueden diferir (y mucho) del diseño y las arquitecturas originales.
Por lo tanto, las reglas de arquitectura definidas inicialmente suelen ser como la documentación inicial cargada de buenas intenciones, que empiezan muy bien pero que con el tiempo se dejan de aplicar (por diferentes motivo: dejadez, poca claridad, complejidad, etc.) y al final todo tiende al caos.
1.2.3. Los diagramas siempre funcionan
Photo by Jelleke Vanooteghem on Unsplash
En un diagrama técnico o no técnico las cosas representadas nunca dan problemas: las cajas siempre arrancan como si fueran sistemas, las dependencias son buenísimas y encajan a la perfección, las flechas siempre conectan, los protocoles de comunicaciones se implementan facilmente, los textos nunca son un problema, las aplicaciones siempre despliegan, la seguridad esta "on fire", etc. -> Todo siempre es ideal y funciona
(Creo que ya sabéis por donde voy y lo relaciono con el primero de los problemas planteados :-) )
Por lo tanto, un diagrama (por ejemplo, de arquitectura) puede que tampoco diga toda la verdad y que si queremos confirmarlo lo tengamos que ver en código y en funcionamiento :
- Con todas las piezas
- Con todas las comunicaciones
- Con todas las integraciones
- Con todos los entornos
- ...
(Vamos que no nos vamos a aburrir :-))
1.2.4. El problema de las arquitectura "Age of Empires"
Photo by Irene Ortiz on Unsplash
Aclaración de "Age of Empires"
Si habéis identificado el nombre "Age of Empires" recordareis que se trata de un juego de estrategia en tiempo real de 1997 que permitía jugar durante una línea de tiempo que abarcaba desde la "Edad de Piedra" hasta la "Edad de Hierro" (aprox. 3000 años).
Se seleccionaba una de las 12 civilizaciones para competir contra el resto, sabiendo que cada una de las civilizaciones (Egipcia, Persa, Fenicia, Griega, etc.) tenía unas bonificaciones concretas y que eran mejores aplicando cierto tipos de estrategias (Pros y Contras)
El objetivo consistía en tratar de ganar al resto al conquistarlos "batallando" y para ello te dedicabas a consumir recursos del mapa (madera, oro, alimento, piedra, etc.) para construir mejores unidades de guerra, aprender tecnologías civiles y militares, tomar decisiones políticas (alianzas, etc.) y otras cosas, todo esto permitía poder avanzar de "era" lo que suponía una mejora cada vez que ejecutaba lo que te posicionaba para poder ganar con más facilidad en esas batallas.
(La relación entre "Definir una Arquitectura" y este juego parece rara pero ahora la explicare y todo tendrá sentido)
Era de Desarrollo = Era de la Historia
Wikipedia : Era (cronológica)
En cronología, una era es la fecha de un acontecimiento tomada como referencia o hito por una civilización para el cómputo del tiempo debido a su importancia.
También se denomina era al periodo histórico prolongado que se caracterizó por el dominio de un personaje, un hecho o un proceso. Es similar pero no equivalente al concepto de edad histórica o de período cuando estas se nombran como Edad de Piedra o Edad de los metales. No deben confundirse las eras con las edades de la historia en que se divide el tiempo histórico (Edad Antigua, Edad Media, Edad Moderna y Edad Contemporánea).
Una Era de Desarrollo es un periodo de tiempo (histórico) donde aparecieron uno o varios Estilos Arquitectónicos que resolvían una serie de problemas en el mundo del desarrollo.
Estilo Arquitectónico = Civilización
Cada Estilo Arquitectónico resuelve un problema concreto, por lo que hay que elegirlo bien ya que presenta pros y contras en muchos ámbitos : comunicaciones, forma de desarrollo, testing, despliegue, escalado, etc.
Mientras el tiempo "pasa", avanza la línea de tiempo y el "desarrollo" continúa evolucionando atendiendo diferentes criterios : negocio, modas, tecnologías, formas de trabajo, uso de procedimientos, etc.
El objetivo final de todo esto es definir nuestra arquitectura y cubrir así nuestras necesidades
(Las necesidades pueden ser equivocadas o no per conforman nuestra estrategia elegida).
Definir una Arquitectura = Tomar Decisiones
Hay recordar que todas las decisiones que hay que tomar a la hora de definir y/o evolucionar una arquitectura genera muchas posibilidades, es decir, es muy raro ver dos arquitecturas totalmente iguales a menos que sean copias.
Tomar Decisiones = Elegir un Estilo Arquitectónico + Cumplir necesidades específicas
Estio es lo que define la estrategia elegida para resolver un problema.
Comparar 2 Arquitecturas = Comparar 2 Civilizaciones durante la partida
La comparación implicaba que podían pasar varias cosas:
Caso "Usar Estilos Arquitectónicos de la misma Era de Desarrollo
Si (estilo-arq-1 = estilo-arq-2) y ...
- "Los dos (estilos + arquitectura) evolucionan de forma similar" -> las arquitecturas deberían ser muy parecidas
- "Un (estilo + arquitectura) evoluciona mejor" -> varias opciones posibles:
- El (estilo + arquitectura) mejor facilitaba esa evolución
- El (estilo + arquitectura) peor se ha "dormido en los laureles"
- El (estilo + arquitectura) mejor ha hecho alguna "trampa" y no todo es verdad
- El (estilo + arquitectura) mejor ha hecho alguna "apuesta" a un grupo de "tecnologías" por lo que despunta sólo en un aspecto presentando carencias en el resto
Si (estilo-arq-1 != estilo-arq-2) y ...
- "Los dos (estilo + arquitectura) evolucionan de forma similar" -> las arquitecturas pueden ser sólo un poco diferentes o bien bastante diferentes en función de lo que evolucionen
- "Los dos (estilo + arquitectura) evolucionan de forma diferente" -> las arquitecturas deberían ser muy diferentes
Caso "Usar Estilos Arquitectónicos de Diferentes Eras de Desarrollo
Si (estilo-arq pertenece a la "Era Anterior") y ...
- "Tiene capacidad de evolución" -> la arquitectura puede evolucionar a una arquitectura que cumpla un estilo de la Era en que se encuentre
- "NO tiene capacidad de evolucionar" -> varias opciones posibles:
- El (estilo + arquitectura) se quedará estancando SIN evolución posible
- El (estilo + arquitectura) se podrá evolucionar si se reinventa a uno de los Estilos correspondientes de esa Era
Si (estilo-arq pertenece a la "Era Actual") y ...
- "Tiene capacidad de evolución" -> la (estilo + arquitectura) puede evolucionar durante esa Era
- "NO tiene capacidad de evolucionar" -> El (estilo + arquitectura) se quedará estancado SIN evolución posible y sólo valdrá para esa Era y ese momento
Creo que lo he complicado un poco con esta frikada...jejeje, pero creo puede ayudar a ubicar la evolución de un arquitectura de una compañía frente al resto de arquitecturas y sobre todo frente a la evolución de las arquitecturas de otras compañías
1.2.5. El problema del paquete de utilidades
Photo by boris misevic on Unsplash
Todo un clásico en el mundo del desarrollo, cuando desarrollamos y nos molestamos en tenerlo todo organizado en paquetes (si se puede), pasado un cierto tiempo siempre aparece el paquete de utilidades "util".
Este paquete suele contener aquellas clases que consideramos de utilidad (global y/o específica de un proyecto) y donde inicialmente cada una tiene las siguientes características :
- Tratar de cumplir el PSR o reducir el máximo las responsabilidades
- Clasificarse en una tipología : converter, encoder, formateador, etc.
- Desacoplarse al máximo del resto de los componentes
- Enfoque de uso basado en la reutilización
- Utilizar una nomenclatura adecuada
- ...
(Estas características son las que se suelen considerar las mejores prácticas)
Al continuar pasando el tiempo y al evolucionar el desarrollo, este paquete se suele convertir en "cajón de sastre" de todas aquellas clases que no se pueden clasificar en estereotipos básicos (Entidad, Repositorio, Servicio, Controller, etc) y que tienen características o funcionalidades "curiosas".
Y entonces aparecen los problemas :
- Muchas dependencias con el resto de los componentes cuando no deberían existir
- Más de una funcionalidad o responsabilidad por clase
- Mala separación de utilidades
- Mala clasificación de utilidades
- Poco o nulo testing
- Uso extendido por todo el mundo -> cuando algo ahorra código, aunque este mal todo el mundo lo utiliza -> mayor distribución de su uso
- ...
En la mayoría de los casos se llega a la siguiente y peligrosa conclusión: el paquete de "util" acaba teniendo dependencias con todo y todo acaba teniendo dependencias con el paquete de "util"
2. Conclusiones
El objetivo de este artículo era ayudar a contar con palabras algunos de los típicos problemas que nos encontramos los desarrolladores a la hora de enfrentarnos a una arquitectura.
La mayoría de ellos seguro que os suenan y seguro que con alguno os robo una sonrisa, pero tengo que decir que existen muchísimos más...y esto da un poco de miedo.
Aun así, he tratado de establecer el mismo punto de partida para todos y con el que abordaremos los siguientes artículos. Y de una manera indirecta hemos podido hablar un poco de arquitectura que nunca esta de más.
Espero que os haya gustado. Si quieres estar al día de las próximas entregas sobre ArchUnit, ¡síguenos en Twitter!
Saludos :-)