Cómo construir un eCommerce Singular. Capítulo 2, El Monolito

Aunque es verdad que a veces hacer las cosas mal cuesta más que hacerlas bien, nadie se propone de manera consciente construir un sistema de software que sea ineficiente o inmantenible. Simplemente, pasa. Algo se desarrolla pensando en resolver un problema; pero el problema va cambiando, o creciendo; y entonces se van añadiendo nuevos módulos, nuevos componentes, o se añade funcionalidad sobre algo que ya existía, y la cosa se termina por complicar.

Frankenstein, o el Nuevo Monolito

Un sistema de software donde todos sus elementos están fuertemente acoplados y además está autocontenido y se ejecuta como un sistema único puede llegar no sólo a ser difícil de mantener y evolucionar, sino que también podría no ser capaz de mantener su rendimiento y eficiencia cuando aumenta su uso y por tanto las interacciones, lo que viene conociéndose como escalar.

El concepto de acoplamiento en términos de ingeniería del software hace referencia a la dependencia que tienen diferentes partes de un sistema, a la hora de realizar la función para la que fueron diseñadas. Aunque es normal que cuando se construye un sistema, haya partes que necesiten de otras para realizar ciertas tareas, la forma en que esta dependencia se construye podrá hacerse con mayor o menor acoplamiento. Por ejemplo: el caso de un formulario en una web que acaba insertando información en una base de datos. Puede hacerse con cierto desacoplamiento cuando los datos de los formularios viajan por diferentes capas / componentes que los piden, los recogen, los validan y los procesan cada uno como corresponda; o muy acoplada cuando un único componente coordina la operación, y le va pasando al resto no sólo los datos, sino instrucciones sobre qué hacer con ellos y cómo hacerlo.

Monolito Obelix

UX Designer FullStack Developer DevOps as a Service

Tradicionalmente, se define un sistema Monolítico como aquél en el que todas las capacidades de representación, procesamiento y almacenamiento están entrelazadas, y son gestionadas como una única unidad de ejecución, prevista para correr en un único servidor. Es decir, una única aplicación contiene y es responsable de todas sus funcionalidades.

Hay muchos motivos por los que alguien acabaría teniendo un sistema Monolítico. Estadísticamente yo diría que de menor a mayor probabilidad, serían cuatro.

Primer motivo: porque alguien lo habría diseñado así intencionadamente.

En esta categoría podemos meter por ejemplo el llamado “Majestic Monolith” (no hace falta traducción) de Basecamp. En un artículo de 2016, David Heinemeier presenta el concepto Majestic Monolith para referirse a la forma en que Basecamp está construida. Los conceptos subyacentes a esta decisión de arquitectura son: eliminar abstracciones innecesarias, y hacer distribuidas únicamente las partes del sistema necesarias. Básicamente defiende que es la mejor alternativa en el escenario de un equipo reducido de desarrolladores (12), con experiencia y criterio, que conocen y entienden el código del sistema; código que se ha escrito con criterios de simplicidad, limpieza y orientación a la prueba. Teniendo en cuenta que este hombre creó Ruby on Rails, parece que es una decisión razonada que le permite mantener y evolucionar un sistema que en 2018 tiene más de 2,8 millones de cuentas registradas, accesible desde 6 plataformas.  

Segundo motivo: porque estaba mal diseñado.

Asumamos como cierto que las personas que han estudiado una carrera de informática (técnica o superior; en la universidad o en la FP, o como se llamen ahora respectivamente, que yo no estoy muy al día) han recibido los conceptos y bases necesarios para diseñar arquitecturas de software. Asumamos también como cierto que otras personas que no han estudiado en las universidades o centros de formación profesional pueden adquirir conocimiento a partir de la experiencia, la lectura de documentación y el aprendizaje autodidacta o desde un referente. Asumir estos planteamientos significa partir de la base de que las empresas ponen a diseñar arquitecturas de software a personas que saben como hacerlo, que han leído, conocen patrones de referencia, y quieren hacer bien su trabajo.

Sin embargo, shit happens. Como decía al empezar este post, no creo que nadie diseñe de manera consciente sistemas para que sean ineficientes o no escalen; simplemente a veces se toman las decisiones erróneas. En 2013 tres investigadores de la universidad de Groningen (Holanda) D. Tofan, M. Galster y P. Avgeriou publicaron un paper en el que analizaban las causas por las que un Arquitecto de Software llegaba a tomar malas decisiones a la hora de plantear una solución técnica.

D. Tofan, M. Galster y P. Avgeriou, ECSA 2013

Como resultado del estudio, llegaron a varias conclusiones, aunque a lo mejor no todas sorprendentes e inesperadas:

  1. El principal motivo que lleva a tomar decisiones erróneas a la hora de hacer una arquitectura de software es la dependencia con otras decisiones ajenas a la decisión técnica en sí; seguido por el impacto que una decisión a priori técnica tenía en el negocio. Es decir, al propósito por el que se está construyendo el software en la organización, en el caso de un eCommerce: ganar dinero vendiendo cosas por internet.
  2. Por el contrario, que hubiera demasiadas alternativas o muchas personas en el proceso de toma de decisión no se consideraban motivos relevantes de un diseño mal planteado.
  3. Existe una diferencia entre los motivos que detectan los arquitectos de software según su experiencia. Los que tenían menos de 5 años de experiencia consideraban problemático tener que enfrentarse a criterios y recomendaciones contradictorias a la hora de tomar una decisión, y que cuanto más había que pensar, más difícil les resultaba decidirse. Por el contrario, los arquitectos de software con más de 5 años de experiencia no daban importancia a estos conceptos, sino al tema del contexto y el negocio (lo cual hace inevitable resucitar la paradoja del Arquitecto de Software Junior)
  4. El tiempo medio que se se consideraba necesario para tomar las decisiones de diseño de una arquitectura de software era de 8 jornadas de trabajo. Paradójicamente, el estudio también apunta a que los arquitectos de software con menos experiencia tardaban la cuarta parte en tomar decisiones de diseño (de ahí el refrán acerca de la ignorancia y el atrevimiento)

Tercer motivo: porque estaba bien diseñado, pero en algún momento las cosas se torcieron.

Se que al final se me hacen unos post muy largos, que a veces abro muchos temas y demás, pero es que hay que hablar de la deuda técnica. Más allá de que nos guste más o menos el término, o su sentido; en el contexto de este post voy a usarlo porque me parece la forma más universal de hacer referencia a la degradación que sufren los sistemas durante su desarrollo y evolución. Puede que se partiese de un sistema bien definido y planteado, pero con el paso del tiempo la aplicación acaba siendo ponzoña. ¿Por qué? De nuevo, es algo que nadie quiere que ocurra de manera intencionada, yo creo en la buena fe de las personas. Pero ocurre.

Por lo general, el código de un sistema se degrada poco a poco, como respuesta a situaciones del tipo “me ha quedado complicado, pero ya lo arreglaré más adelante”, “no tengo tiempo para hacer las pruebas”, “este fallo debe ser un Expediente X, porque suele funcionar”, etc.

En 2015, Neil Ernst del Software Engineering Institute de la Universidad Carnegie-Mellon publicó un estudio / paper sobre los motivos de por qué sucede esta degradación. Os comparto la gráfica que me parece más relevante. Es bastante auto-explicativa, la verdad.

Neil Ernst, SEI 2015

Cuarto y último motivo: porque no sabía que se lo estaba comprando.

Creo que es lo más normal, y suele pasar cuando alguien compra un producto mirando los Cuadrantes que publican los analistas de negocio, o porque nobody ever was fired for buying IBM, o porque las demos de jugar estaban bien, etc. En lugar de hacer una due diligence tecnológica como Dios manda.

¿Qué hace aquí este Monolito?

El esfuerzo e interés por desarrollar aplicaciones desacopladas no es algo nuevo, y forma parte del corpus teórico de la ingeniería del software desde sus inicios. En el caso que nos ocupa en este contexto, que es el eCommerce (algo inherentemente ligado a la Web y en los últimos años al móvil) las arquitecturas en tres capas (presentación, lógica de negocio y datos) y el Patrón Model-View-Controller han sido la forma habitual de construir sistemas con bajo acoplamiento.

No sólo desde el punto de vista de la construcción, ya que al separar de forma conceptual las distintas partes del sistema es más fácil actuar sobre ellas; sino desde el punto de vista de la infraestructura que las soporta, con el objetivo de mejorar el escalado y el rendimiento. Poder empaquetar y desplegar cada una de las capas en servidores diferentes permite al sistema escalar de manera independiente cada capa cuando se producen los picos de acceso al sistema (cuando hay ventas privadas, o campañas como Rebajas, San Valentín, Navidades o el Black Friday) Por eso es bastante habitual ver infraestructuras donde se ha separado la parte de presentación en unos servidores optimizados para servir el front, complementados con un CDN para los estáticos (las imágenes, vídeos, etc), la parte que hace la lógica de operaciones en servidores de aplicación, y otra capa con la base de datos en alta disponibilidad.

Monolito Scrum

El equipo Scrum hace sus dailies frente al Monolito

Sin embargo, dejadme que os diga que la primera referencia a patrón MVC se remonta a finales de los 70. Y la idea de crear una arquitectura en 3 capas para construir aplicaciones cliente / servidor es de mediados de los 90. Así que, ¿a qué nos referimos hoy en día cuando se habla de un Monolito? Porque es lógico suponer que la mayoría de los eCommerce Singulares, que es de lo que va esa serie de posts, se han construido bien sobre plataforma, bien a medida, siguiendo arquitecturas y patrones que desacoplan al menos las capas de presentación-negocio-datos.

Bien. En primer lugar, aunque la tienda como tal esté desarrollada en 3 capas, puede que la capa de lógica de negocio (o back-end) se haya construido como un Monolito. Significaría que es un bloque único que recibe las peticiones de la parte del cliente (web o móvil o por qué no, un botón IoT), hace cierta lógica (como una búsqueda de productos según un criterio,  o añadir un elemento al carrito) y genera la información que se renderizará en la capa de presentación a modo de respuesta. Entendido de esta manera, el servidor puede estar construido de manera modular, con sus elementos desacoplados en paquetes y clases, de forma que tenga su componente de carrito, otro de catálogo, etc. Pero lo cierto es que al final todas importarán las mismas librerías con las mismas dependencias, compartirán el mismo modelo de almacenamiento de datos en sesión, y  la misma política de persistencia.

También el back-end de la aplicación puede considerarse un monolito si se empaqueta como un único artefacto. Podría llegar a desplegarse en varios servidores, lo que le permitiría escalar en caso de aumento de peticiones. Pero es una única unidad lógica; de forma que en caso de ser necesario hacer una modificación, hay que volver a compilar, empaquetar y desplegar toda la parte servidora, haya o no sido actualizada. Y una vez que se empaqueta, la misma versión es la que se despliega (y por tanto se ejecuta) en todos los nodos de la infraestructura. Al hacer el despliegue, aunque sea por nodos, es necesario reiniciarlos. En la medida en que haya un entorno distribuido con alta disponibilidad y persistencia compartida de sesiones, los usuarios podrían no verse afectados; sin embargo, tareas programadas o procesos de sistema (como sincronizaciones de inventario, confirmaciones de pago, o envío de newsletters) que estuvieran ejecutándose, se perderían. Por eso muchas veces hay que hacer las actualizaciones de manera programada, lo que es una barrera para llegar al despliegue continuo.   

Y el artefacto que se despliega en cada nodo, se ejecuta en un mismo entorno, compartiendo la misma configuración y el espacio de memoria y en general, los recursos del nodo. Cuanto más pesado sea, más tiempo tardará en arrancar y mayores recursos necesitará. Por tanto, todos los servicios o end-points del artefacto están replicados por igual en cada nodo y acceden a los mismos recursos del sistema, con independencia de la frecuencia con que se usen para resolver las tareas del sistema. En todos los servidores se ejecutan réplicas idénticas del back-end, capaces de hacer las mismas operaciones de lectura y escritura contra la base de datos; por tanto lo normal es que escalar la capa de negocio conlleve escalar la capa de datos.

Cuando alguna de las partes de tu sistema se ha convertido en un Monolito lo normal es que tengas algunas desventajas, que veremos a continuación.

Cosas malas que pasan cuando tienes un Monolito

Desde el punto de vista del desarrollo, cuando todo el back-end de una plataforma (y en este artículo estamos hablando de un eCommerce Top 100) está construido como un Monolito, se producen unos efectos indeseables que impactan en la productividad y eficiencia del equipo. ¿Por qué?

Porque todo el equipo de desarrollo del back-end trabaja sobre el mismo artefacto. Cuando hay demasiada lógica de negocio en la misma capa, es normal que se rompa la modularidad de los componentes con el paso del tiempo, conforme se van haciendo extensiones del producto, o se corrigen errores. Por ejemplo, añadir un nuevo medio de pago a un eCommerce puede afectar: al módulo desde el que se procesan los pagos, al que renderiza el selector de medios de pago durante el proceso de checkout, al que gestiona el medio de pago favorito en el perfil del usuario (necesario para los one-click-payments), etc. Eventualmente llega un momento en que el sistema es una maraña y es costoso tener una visión global de cómo funcionan las cosas.

Como además el back-end es un único componente que se carga completamente, los entornos de desarrollo son cada vez más costosos de arrancar, se tarda más tiempo en compilar y en ejecutar y depurar en local. Esto hace que cada vez los ordenadores para el desarrollo requieran mayores prestaciones; algo que no siempre es posible conseguir porque puede llegar a  chocar con las políticas de actualización de los departamentos de tecnología, o los responsables de compras. ¿Alguna vez habéis visto a personas cronometrando cuánto tiempo tarda en arrancar el Eclipse, o cuántas horas pierde al día mientras espera en las compilaciones? Yo sí, y es muy lamentable.

Un back-end que acaba siendo un Monolito hace que todos sus componentes y módulos que se despliegan como un mismo artefacto, estén ligados a un lenguaje de programación específico, y a unas determinadas versiones no sólo del framework de desarrollo, sino de las librerías que se importan. Los cambios de versiones son más costosos, porque se hace necesario validar el impacto que puede tener el cambio de librería en todo el sistema como un conjunto (en lugar de en partes concretas donde el cambio puede tener más sentido). Eso por no hablar de cambiar de stack tecnológico. Un proyecto de refactorización de un sistema para ir aliviando la deuda técnica, puede ser una inversión en tiempo y dinero difícil de justificar ante el responsable de asignar recursos económicos a los proyectos.

Monolito Asuán

Antes de levantar este monolito de 36m y 150 toneladas se rompió la build. Por eso se llama El Obelisco Inacabado de Asuán

A la larga, todo esto unido significa que se incrementa la curva de entrada de las nuevas incorporaciones al equipo, con lo que aumenta el tiempo que necesita una persona para hacer trabajo productivo. Cuando el tamaño del equipo de desarrollo alcanza un nivel importante, y estamos hablando de un eCommerce Top 100 (no de la demo de la tienda de mascotas), aumenta el esfuerzo de coordinación de los diferentes subproyectos / extensiones que se van haciendo en paralelo. Esto puede llegar a hacer difícil segregar las responsabilidades de los diferentes equipos, y por tanto su autonomía e independencia; y complicar el modelo de gestión de los diferentes proyectos / equipos. Podemos añadir a esta ecuación que las personas pueden estar distribuidas en diferentes localizaciones, cosa habitual en los tiempos que corren.

Desde el punto de vista del delivery, actualizar un sistema Monolítico requiere recompilar toda la aplicación, volver a empaquetarla y redespeglar. Al ser una sola pieza, hay que parar todo el sistema por completo para hacer la actualización.

Además, si llega el caso en que el sistema tiene que escalar, es necesario hacer que escale todo el monolito al completo; en lugar de aumentar la capacidad de aquella parte del proceso / operación que realmente lo necesita. Lo que lleva a aumentar la capacidad (procesamiento, memoria, almacenamiento) del servidor en el que corre, y podría no ser la estrategia de infraestructura más óptima en términos de inversión.

Finalmente, una vez que empiezan a dar servicio, en caso de que se produzcan errores, el proceso de depurarlos e identificar dónde se está produciendo el error puede llegar a ser complejo debido al acoplamiento. Es cierto que desde hace años (de cuando yo programaba) ya están inventados los IDE de desarrollo que se conectan a un servidor y permiten hacer depuración en tiempo real, y esto ayuda a encontrar los errores. Sin embargo, cuanto mayor sea el acoplamiento, y más pesado el entorno de desarrollo y sus dependencias, más implicaciones puede tener encontrar y depurar un error, corregirlo y validar la solución, por el efecto secundario que pueda tener en otros componentes.

Cuando existe fuerte acoplamiento entre los elementos de la aplicación, lo normal es que no se haya logrado separar operaciones y reutilizar código. De forma que cuando hay que modificar o corregir cualquiera de sus funcionalidades, se requiere primero entender cómo el cambio puede afectar a sus diferentes partes; y lo que es peor, identificar todos los puntos en los que se tendría que replicar la actualización.

¿Entonces?

Esta casuística la comparten centenares de plataformas y sistemas de software; y la extienden a los centenares de miles de sites de comercio electrónico por todo el mundo construidos sobre ellas. Podríamos pensar que tampoco pasa nada, que no van a morir niños de hambre en el Tercer Mundo, ni vamos a prolongar el Cambio Climático (y eso sí que son problemas de verdad).

Monolito Obelix Happy

Pues me siento más ligero sin mi Monolito

Recordemos: estamos hablando de construir un eCommerce Top 100, uno que se espera que sea referente de su sector porque lo va a construir una empresa líder. Si a tu eCommerce le vas a exigir facturar millones de euros al año; operar en decenas de países; recibir miles de peticiones concurrentes de personas de todo el mundo; conocer en tiempo  real el stock de artículos y tallas; y cerrar procesos de compra con seguridad. Y por qué no, conseguir finalizar las transacciones en ese milisegundo que es la diferencia entre comprar o quedarte sin stock.

Hoy en día tiene cierta madurez y solvencia la aproximación tecnológica que permite dividir el back-end de la aplicación en partes más pequeñas, especializadas en hacer una única tarea, cada una con su propia capacidad de desarrollarse, desplegarse y escalar por separado. Con su propia arquitectura, consumiendo los recursos que necesita, y gestionando únicamente aquellos datos sobre los que tiene responsabilidad. Pero antes de hablar sobre cómo materializar este planteamiento en una arquitectura de microservicios, habrá que saber cuáles son los elementos que tiene que tener un eCommerce Top 100. Y eso lo veremos en el siguiente capítulo.

(Continuará…)

(Espero que antes de 6 meses)

Anuncio publicitario

Deja una respuesta

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Salir /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Salir /  Cambiar )

Conectando a %s

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.