¿Qué es la arquitectura centrada en el dominio?
Basado en la presentación de XConf 2020: ¿Qué es la arquitectura centrada en el dominio y por qué lo necesitas en tu negocio? Yerko Aguirre — Javiera Laso
La arquitectura es difícil
Conway establece que los diseños de sistemas son el fiel reflejo de como esta estructurada una organización, si una organización presenta una estructura de silos donde cada equipo esta aislado del resto, el diseño y arquitectura de los sistemas de esa organización van a presentar los mismos síntomas,
Por ejemplo, un sistema que esta dividido en equipos de frontend backend y administradores de base de datos donde existen silos y problemas de comunicación, van a producir sistemas aislados y fuertemente acoplados al contexto del equipo.
Es por esto, que debemos armar el negocio y los equipos de tal forma que represente lo que se necesita, por ejemplo, equipos de proyecto a equipos de producto, y veremos la justificación de esta decisión a medida que avances en el artículo.
Tipos de arquitectura en el desarrollo ágil
El software por su naturaleza es cambiante, cuando iniciamos en un proyecto ágil algunas veces no vemos o no nos preocupamos por conceptos de arquitectura que siempre están presentes, por lo cual el producto se vuelve difícil de cambiar en el futuro
Sabemos además, que la arquitectura no esta escrita en piedra y no es algo rígido, mas bien es algo que va evolucionando a medida que el producto se desarrolla y no es trivial identificar dónde debería ir cierta lógica de negocios sin una arquitectura bien definida o una arquitectura mala.
Existen varios tipos de arquitectura, las cuales buscan resolver distintos problemas, y las cuales varían en complejidad, entre estos tipos no existe el concepto de mejor arquitectura, ya que todo “depende” del contexto y del problema que queremos resolver. Existen distintos tipos de arquitectura, donde podemos destacar a grandes rasgos los monolitos y las arquitecturas distribuidas.
Monolitos
Los monolitos son aquellos que combinan la capa de interfaz del usuario, la capa de negocios y la base de datos en un solo sistema, es decir, es responsable por todos los pasos necesarios para una función en particular. En los monolitos podemos encontrar:
- Arquitectura por capas
- Arquitecturas modulares
- Micro Kernels
Arquitecturas distribuídas
Las arquitecturas distribuidas son aquellas que al contrario del monolito sus procesos son distribuidos, es decir si realizamos una consulta en la base de datos, vamos a dividir todos los procesos en distintos nodos, lo que mejorará el performance final de la aplicación. En esta arquitectura podemos encontrar:
- Microservicios
- Arquitectura orientada a servicios
- Arquitectura orientada a eventos
Como vemos existen varios tipos de arquitectura, las cuales buscan resolver distintos problemas, y que varían en complejidad, entre estos tipos no existe el concepto de mejor arquitectura, ya que todo “depende” del contexto y del problema que queremos resolver.
Diseño guiado por el dominio
El diseño basado en dominios es un lenguaje y enfoque de software centrado en el diseño de los dominios para modelar problemas complejos. El término fue acuñado por Eric Evans en su libro Domain Driven Design. Consiste en una colección de patrones, principios y prácticas que permiten que los equipos se centren en lo que es fundamental para el éxito del negocio mientras se elabora software que gestiona la complejidad en los espacios técnicos y comerciales.
En resumen, para llevar a cabo un buen diseño guiado por el dominio debemos seguir algunos pasos:
- El alineamiento es la parte fundamental donde debemos entender el modelo de negocios (que hace la empresa) y cuales son las necesidades de los usuarios
- Al descubrir podemos llevar iniciativas como el event storming para poder entender los dominios, los contextos y cuales pueden ser divididos para el proceso siguiente.
- Al entender los dominios, podemos descomponer en sub dominios, y comenzar a definir nuestros bounded contexts (de los cuales hablaremos después), además
- Debemos saber cómo conectar los sub-dominios ya que una arquitectura débilmente acoplada permite a los equipos trabajar en paralelo sin ser bloqueados.
- Al tener una estrategia de dominios centrales, podemos tener un lenguaje común entre el negocio y la parte técnica, a modo de definir routemaps e invertir la maniobra de Conway.
- Al organizarnos, debemos considerar que la maniobra de Conway no es lo mejor, ya que puede bloquearnos en etapas superiores.
- Al definir los roles y responsabilidades logramos que cada bounded context pueda evolucionar de forma óptima.
- Utilizar los patrones que mejor se ajusten a tu negocio y comienza a programar !
Lenguaje común entre negocio y tecnología
Es importante mantener un lenguaje común entre los expertos de negocio y expertos de tecnología, para lograr trasladar estos conceptos al código fuente, de forma tal de que nuestro producto de software refleje los conceptos que son hablados comúnmente por el negocio.
Ejemplos: Los acrónimos de negocio tienen que estar documentados en un lugar accesible, como confluence u otra wiki que haya sido creado en conjunto con las áreas involucradas.
Bounded Context
Los bounded context son el patrón principal en Domain-Driven Design. Un Bounded Context es un espacio delimitado donde un elemento del negocio tiene un significado perfectamente definido.
El contexto es definido por los términos usados en las charlas documentación, reuniones, etc.
En la imagen podemos ver que un producto conceptualmente es completamente distinto de acuerdo a su contexto. Un producto en el contexto de envío es diferente a un producto en el contexto de inventario. Aquí es donde toma más fuerza la idea de que el significado varia de acuerdo del contexto, pero también podemos observar que podría verse afectado el código (por una posible duplicación), pero podemos compararlo con la disminución de un alto acoplamiento. Además tenemos la libertad de evolucionar de acuerdo al contexto.
Ahora que conocemos los los principios de DDD podemos hablar un poco sobre las arquitecturas clásicas y sus diferencias.
Arquitectura por capas v/s orientada al dominio
En la arquitectura por capas, la dependencia va desde la capa de presentación hasta la capa de persistencia, lo cual está bien para aplicaciones cuyo grado de complejidad es bajo, pero a medida que el producto crece y su complejidad aumenta, las reglas de negocio quedan agrupadas a otras capas y es difícil de cambiar y mantener.
Por otra parte, en la arquitectura centrada en el dominio, las dependencias se invierten y es el dominio el centro de todo, por lo cual las entidades, casos de uso o reglas de negocio no dependen de tecnología y tampoco de agentes externos, por lo cual es más fácil adaptarse al cambio y evolucionar
Si llevamos esto a la vida real podemos encontrar lo siguiente:
Al mirar la imagen, y centrarnos en la arquitectura por capas podemos intentar “adivinar” sin mucho éxito, que es lo que hace la aplicación, quizás podemos decir: -“Tiene que ver con una clínica veterinaria”, pero si miramos la descrita por una arquitectura centrada en el dominio, es más “intuitivo” saber que es lo que está haciendo nuestra aplicación, vemos por ejemplo que si bien es sobre una clínica veterinaria, además es capaz de confirmar citas, agendar nuevas y buscar a los veterinarios disponibles (además de un sin número más de cosas que hace, por lo que se ve).
Podemos concluir que la estructura tradicional quiebra la encapsulación, al paso que la arquitectura por dominios la refuerza.
De la teoría a la realidad: Aplicación en proyectos reales
Arquitectura hexagonal en un e-commerce
La arquitectura hexagonal es conocida también como la arquitectura de puertos y adaptadores y tiene la misión de desacoplar las capas de la aplicación. Se describe como puerto, la definición de una interfaz pública y adaptador a la especialización de un puerto para un contexto en específico.
La elección de esta arquitectura se basa en sus características:
- Independiente del framework
- Testables
- Independientes de la UI
- Independientes de la base de datos
- Independiente de agentes externos
- Más tolerantes al cambio
- Reutilizable
- Mantenible
Un enfoque popular es el empaquetado por cuestiones técnicas. Pero este enfoque tiene algunos inconvenientes. En su lugar, podemos empaquetar por característica y crear paquetes autónomos e independientes. El resultado es una base de código que es más fácil de entender y menos propensa a errores.
Arquitectura Limpia en una inmobiliaria
La arquitectura limpia es una filosofía de diseño de software que separa los elementos de un diseño en niveles de anillo. La regla principal de la arquitectura limpia es que las dependencias de código sólo pueden provenir de los niveles externos hacia adentro. El código de las capas internas no puede tener conocimiento de las funciones de las capas externas.
En este caso, la elección de este tipo de arquitectura se realiza debido a sus características:
- Independiente del Framework y la DB
- UI puede cambiar fácilmente
- Fácil testeo
- Reutilizable
- Mantenible
Un ejemplo interesante de uso de este tipo de arquitectura se da cuando tratamos de cambiar un framework como Spring (que tiene su propia complejidad) a otro sin tantas funcionalidades pero que se ajusta a lo que necesitamos en ese momento para el negocio llamado SparkJava.
Caso: Alto tiempo de arranque al utilizar un framework robusto como Spring, sin necesitarlo. Solución: Cambiar el framework. Como funcionó esto: El desarrollo fue a nivel de framework con un par de líneas de código que logrando hacer un cambio “limpio” sin mayor impacto a las capas inferiores. Al no contar con spring security como librería dentro del framework, se necesitó implementar un middleware, además de generar un archivo de configuración de rutas, pues spring boot trae incorporada esta funcionalidad. Al hacer las pruebas de arranque en un estado de latencia, vimos que nuestro cambio disminuyó el tiempo de arranque de la aplicación de 21s a 4.4s
¿Cómo comenzar en la arquitectura centrada en el dominio?
Desenredando el monolito, es importante ver si podemos pasar de un monolito modular a microservicios, pero no siempre nuestro monolitos están descritos de forma limpia.
La expectativa es tener un monolito ordenado en torno a las capacidades de negocio, pero la realidad es que tenemos dependencias por múltiples lugares mucho acoplamiento que es difícil de evolucionar o cambiar, ya que un cambio en un lugar puede afectar el comportamiento de otra funcionalidad, lo que dificulta el proceso de cambios y de extracción de capacidades de negocio del monolito a microservicios.
Una estrategia para lograr evitar dependencias y acoplamiento es el uso de monolitos modulares donde encapsulamos en módulos los distintos bounded context o dominios de nuestra aplicación, una estrategia para tener dominios aislados es lograr la comunicación entre módulos mediante bus de eventos en memoria, de esta forma logramos evitar acoplamiento entre módulos, y a futuro es más fácil transicionar a microservicios.
Utilizando event storming y bounded context podemos definir microservicios, aunque no siempre vamos a tener una relación de 1 a 1 con respecto a microservicios/bounded context, y es posible que en un bounded context existan múltiples sub-dominios que se pueden traducir en otros microservicios.
Aquí es normal preguntarse ¿cuáles son los principios para trazar los límites del servicio?, y bueno, existe un par que debemos considerar:
Lenguaje: Usa bounded context para trazar fronteras.
Autoridad: Ten autonomía para tomar decisiones.
Tamaño: Divide el servicio cuando un equipo no sea capaz de dar soporte a ese servicio.
Frecuencia de cambios: Considera agrupar conceptos con distinta tasa de cambio por ejemplo servicios de inventario v/s servicios de reporte anuales.
Transaccionalidad: Separa responsabilidades de operaciones v/s reportes ejemplo: Proyección de demanda vs reporte histórico de ventas anuales.
Cohesion: Agrupa conceptos que tengan relación entre sí, para así asegurar mantener la experiencia del negocio.
Orientado al consumidor: Organiza alrededor de capacidades de negocio no alrededor de sistemas.
Pero como elegimos el mejor, es decir, ¿Cómo saber si necesito un monolito o microservicios?
Elige monolito cuando:
- No existe una cultura de DevOps, es importante que las personas sean capaces de desarrollar esta habilidad antes de migrar del monolito
- Estás prototipando una idea y necesitas llevarla a producción de forma rápida
- Código existente presente en una sola unidad de despliegue
Elige microservicios cuando
- Tu equipo es maduro en el negocio y la empresa tiene una cultura de DevOps
- Las personas desarrolladoras de tu equipo tienen experiencia en microservicios y entienden los conceptos de DDD (a veces adoptamos lo que está de moda sin leer los fundamentos, lo que puede llevar a equivocaciones de base que pueden impactar el negocio)
- Cuando necesitas unidades de despliegue independientes y escalar de forma horizontal
Mantener la arquitectura en el tiempo
Cuando parte el proyecto, todos se sienten a gusto con la arquitectura, pero si recordamos hoy las decisiones que tomamos ayer puede haber algunas diferencias significativas, por eso es importante mantener visibilidad de las decisiones que tomaron en el pasado, que nos ayude a guiar y verificar que la arquitectura no se desvíe a medida que pasa el tiempo. A continuación me gustaría destacar algunas prácticas:
Visualiza tu arquitectura utilizando modelos C4
Las 4 fases del modelo son: contexto del sistema, diagrama del contenedor, diagrama de componentes y el código
Los diagramas de arquitectura C4, son modelos que ayudan a explicar las aplicaciones y sus sistemas desde lo general a lo particular. Esto quiere decir que un sistema podemos explicarlo en 5 minutos, en 1 hora o en 5 horas.
Como ven en el ejemplo de arriba, podemos hablar tanto de un ciclista, llegando a la estructura molecular del caucho, lo importante es al menos tener documentado hasta el diagrama de contenedores o componentes. Aquí surge la pregunta ¿Cómo podemos hacer esa documentación?
ADRs
Los ADRs o Architectural Decision Records, son la forma en la que podemos documentar las decisiones que tomamos en cuanto a la arquitectura u otros temas, estos se almacenan en el código fuente y existen diversas formas de escribirlos, a continuación se presenta una forma popular:
Entonces, ¿ Qué forma práctica tenemos de mantener nuestra arquitectura y que además le entregue valor al negocio? considerando que ya documentamos en los ADR…
Testea tu arquitectura
Es importante tener test automatizados que aseguren que tu definición de arquitectura no se desvíe en el tiempo por ejemplo, test para mantener las capas aisladas de tecnología por ejemplo inyección de dependencias por constructor vs setter o reflection.
Los objetivos principales son:
- Evitar dependencia a nivel de ciclos.
- Evitar que la definición de arquitectura se desvíe en el tiempo.
- Fijar lineamientos de arquitectura en el equipo
- Test automatizados con los acuerdos técnicos a nivel de equipo
Además de estos test que logran mantener nuestra arquitectura debemos considerar otras formas de testear nuestra aplicación frente a lo inesperado y esperado, por un lado tenemos nuestro ciclo de desarrollo normal que describe una pirámide de pruebas convencional y por otro las fitness functions.
Observamos en la imagen que tenemos las típicas pruebas que tratamos de aplicar en todos los proyectos, en el desarrollo usamos generalmente TDD (en mi caso) por lo tanto tratamos de seguir la pirámide de pruebas, donde verificamos que las características se ajusten a los resultados comerciales deseados. En las fitness functions podemos escribir pruebas que midan la alineación de un sistema con los objetivos arquitectónicos.
Por ejemplo, existe el chaos monkey, creado por Netflix y responsable de terminar aleatoriamente las instancias en producción para garantizar que nosotros las personas programadoras implementamos los servicios para que sean resistentes a las fallas de las instancias. Algunas fitness functions interesantes que utilizamos y no sabíamos lo que eran:
- Calidad de código: coverage sobre el 90%
- Pruebas de Resilencia: como una prueba podría ejecutar una carga continua en un servicio mientras se implementa una nueva versión de ese servicio en un entorno de pre-producción.
- Pruebas de Observabilidad: métricas que indican baja conversión o volumen de usuarios utilizando splunk (fitness functions en base a métricas de negocio)
- Pruebas de Performance: Por ejemplo cuando queremos que una transacción se realice en máximo 10 segundos, la cual es una métrica definida por la experiencia y el negocio
- Análisis de seguridad: Los típicos de OWASP (como evitar compartir secretos en un deploy a git), pero también que no vaya a existir información sensible en logs, entre otros
Luego de todas estas definiciones vemos que además, las fitness functions se pueden incorporar para monitorear la alineación con las “características” arquitectónicas críticas. A medida que la organización y la tecnología evolucionan, estas fitness functions pueden ayudar a las empresas a evitar quedar con una arquitectura o pruebas obsoletas que a menudo resulta en una complejidad heredada o innecesaria que limita la entrega de valor.
¿Por qué necesitas una arquitectura centrada en el dominio en tu negocio?
Luego de toda la información que hemos revisado nos damos cuenta que la maniobra de Conway de por si no es suficiente, sino que debemos crear equipos de productos; a esta forma de trabajo se le llama maniobra inversa de Conway, donde equipos de productos son capaces de manejar la operación completa de ese producto siendo multidisciplinarios, encargados de sus propios roadmaps pero que pueden conversar entre ellos de ser necesario.
Otro punto que aprendimos es que si te demoras en adaptarte le das ventaja a la competencia, por esto, estar preparados para el cambio nos ayuda a reaccionar de forma rápida. Por ejemplo, cuando la pandemia de covid19 llegó muchos comercios debieron adaptar sus infraestructuras a una cantidad anormal de público online, muchos no lo lograron, pero otros salieron victoriosos.
Finalmente, el tener una arquitectura centrada en el dominio te da una visión técnica y del negocio. Si subes la montaña y ves la ciudad desde lejos, puede entender las zonas en las que se organiza la ciudad. Haciendo analogía con la arquitectura centrada en el dominio, en el cual sabes donde esta la lógica de negocio y entiendes el leguaje tienes una vision global de como esta construido tu producto.
Algunas lecturas recomendadas
A continuación algunos libros con los que se construyó este artículo y presentación:
- Fundamentals of software architecture an engineering approach — Mark Richards & Neal Ford
- Domain Driven Design — Erick Evans
- Building evolutionary architectures — Neal Ford, Rebecca Parsons & Patrick Kua
- Monolith to microservices — Sam Newman
Espero este artículo haya sido de ayuda y no olvides dejar tus comentarios y feedbacks en la caja de abajo, pronto vendrá la versión en inglés para que compartas con tus amigos más internaciones, muchas gracias por leer y llegar hasta aquí.