Introducción de las estrategias de migración
Si hay algo por lo que todos pasamos alguna vez, es por heredar y mantener un proyecto. Puede ser un proyecto antiguo de nuestra empresa, un proyecto que ha llegado de un cliente descontento con su anterior proveedor… etc. El mayor problema que esto supone es que habitualmente nos encontramos con proyectos desarrollados en tecnologías antiguas o desactualizadas, poco mantenidos, con un estilo o unas prácticas que no nos acaban de gustar (los programadores somos todos un poco especialitos en esto) y lo peor de todo: sin tests.
La palabra que siempre nos ronda a la cabeza cuando tratamos con estos proyectos heredados es “legacy code”, que como bien dijo Michael Feathers:
Legacy code is code without tests
Michael Feathers — Working effectively with legacy code
En realidad es mucho más que eso (que no es poco), ya que suelen ser proyectos poco o mal documentados (los tests son siempre la mejor documentación), con versiones de lenguajes, frameworks y librerías anticuadas u obsoletas, y muchas veces difíciles de volver a encarrilar, ya que se basan en prácticas poco amigables para con el testing o el refactoring. En pocas palabras: acumulan una gran deuda técnica.
Otro factor importante es que el proyecto suele ser la fuente de ingresos de una empresa que no puede permitirse el lujo de para máquinas de forma indefinida (como en su momento hiciera Evernote) para limpiar toda la deuda técnica y replantear los cimientos de su fuente de ingresos.
Si los grandes refactors muchas veces se descartan, la reescritura es algo que solo se suele plantear el equipo técnico (a veces desde un punto de vista poco empático de cara al cliente).
Otras veces el problema no es el tanto el código sino que debemos de construir un software sobre una fuente de datos que puede no encajar de la mejor forma con nuestro proyecto. O con un diseño pobre que puede ser mejorado, pero que requiere un gran esfuerzo y dedicación y que solo tras demostrar que el nuevo enfoque es mejor, el cliente estaría dispuesto a aceptar el cambio.
Ante estas situaciones ¿qué opciones tenemos?
En mi experiencia muchas veces se ha optado por mantener el proyecto legacy y o bien por presiones del equipo de desarrollo, por la dificultad para contratar a gente que se haga cargo del proyecto en el estado en que se encuentra o por problemas de crecimiento debidos al estado del proyecto, se ha optado por abordar una migración progresiva.
Esto no es otra cosa que empezar a desarrollar nuevas partes del proyecto sobre un stack moderno y con mejores prácticas, pero sin dejar de depender del antiguo proyecto. Estas migraciones pueden durar eternamente, pero suelen insuflar nueva vida a un proyecto que estaba agonizando, y con una buena planificación es posible dejar de depender del antiguo proyecto a medio plazo.
Me gustaría hoy hablar de dos estrategias de migraciones progresivas sobre los que me he apoyado cuando he tenido que abordar este tipo de tareas: Strangle Fig y Anticorruption Layer.
Strangler Fig Application (Ficus Estrangulador)
Este estrategia fue propuesta por Martin Fowler en 2004. Aunque inicialmente se llamó Strangler Application, posteriormente fue renombrado a StranglerFigApplication en 2009 para evitar la connotación negativa de la palabra Strangler (estrangulador):
bliki: StranglerFigApplication
When Cindy and I went to Australia, we spent some time in the rain forests on the Queensland coast. One of the natural…
Esta técnica de migración consiste en interponer una fachada entre el cliente y el sistema a migrar. De puertas para adentro, empezaremos a migrar progresivamente partes del proyecto legacy a el nuevo sistema, y será la fachada la que se encargará de redirigir el cliente al nuevo o al viejo sistema según sea necesario.
Esto nos va a permitir realizar una migración sin la presión de tener que “parar máquinas” y dejar el proyecto en stand-by, permitiéndonos priorizar la migración de las partes más críticas sin importar sus dependencias, ya que las podremos satisfacer contra el sistema legacy.
No todo son ventajas, por supuesto. El uso de esta estrategia puede incrementar los tiempos de respuesta mientras dure la migración, así como los costes de infraestructura, ya que al menos necesitaremos separar el sistema legacy del nuevo, además de tener una capa encargada de la distribución. Aún así, es preferible esto a dejar nuestro proyecto estancado.
Esta estrategia está siendo adoptada por frameworks como Symfony para facilitar las migraciones desde otros frameworks. Además, existen numerosos casos de éxito documentados que han seguido esta práctica.
Anticorruption Layer
La siguiente estrategia o patrón que vamos a ver es la Anticorruption Layer (abreviado como ACL) propuesta por Eric Evans en su libro Domain-Driven Design: Tackling complexity in the heart of software.
Básicamente, la ACL es una capa que se encargará de traducir entre el modelo de datos legacy y el nuevo modelo de datos. Es muy similar en misión al patrón adapter, del cual ya hablamos en su momento, pero aplicado a datos en este caso.
Pero no solo eso, sino que además, esta capa puede implementar también algunas comprobaciones de seguridad, devolver valores por defecto en caso de no existir en el sistema original, lanzar excepciones más específicas para casos de error o incluso implementar un circuit-breaker si el problema se debe a la disponibilidad del servicio.
¿Cuándo usar Anticorruption Layer?
Podemos ver la respuesta en palabras de el propio Eric Evans:
If your application needs to deal with a database or another application whose model is undesirable or inapplicable to the model you want within your own application, use an Anticorruption Layer to translate to/from that model and yours.
Eric Evans — Domain-Driven Design: Tackling complexity in the heart of software
O en palabras de Vaughn Vernon, autor de Implementing Domain-Driven Design:
Even if you are able to avoid creating a Big Ball of Mud by employing DDD techniques, you may still need to integrate with one or more. If you must integrate with one or more, try to create an Anticorruption Layer against each legacy system in order to protect your own model from the cruft that would otherwise pollute your model with the incomprehensible morass.
Vaughn Vernon — Domain-Driven Design Distilled
Es decir, siempre que necesitamos que nuestro sistema hable con otro sistema cuyo modelo de datos no es el deseado, o incluso si estamos heredando una base de datos sobre la que construir un nuevo proyecto, podemos usar una ACL para traducir del viejo modelo al nuevo evitando que el legacy contamine nuestro proyecto.
Casos de uso de ACL
Esta técnica es ampliamente utilizada en arquitecturas de microservicios donde existen distintas versiones de las APIs de comunicación e incluso de los modelos de datos (recordemos que cada microservicio tiene su propia base de datos con su propio esquema).
Un problema que puede derivar de la práctica de la migración mediante StranglerFigApplication es que en ocasiones será necesario que exista comunicación entre el proyecto legacy y el nuevo, por lo que el uso de esta técnica combinada con la anterior es más que recomendable.
Otro caso de uso que para el que he aplicado personalmente esta estrategia ha sido para lidiar con bases de datos NoSQL que tienen en una misma colección documentos con distinto esquema. Es especialmente útil el uso de una ACL que homogeneice el esquema de todos los documentos para que encajen adecuadamente con los objetos del dominio.
Conclusión
Aunque puede resultar tentador proponer una reescritura entera de un proyecto desfasado, es importante valorar nuestras opciones antes. No siempre va a ser posible abordar este tipo de proyectos, pero debemos de conocer qué otras alternativas tenemos.
Esto es solo una muestra de un par de estrategias que personalmente me han resultado útiles en el pasado, pero no son las únicas, en un futuro hablaremos de otras.
Saludos!