Paremos las migraciones de sistemas legados, lidiemos con ellos.

Muchas empresas buscan migrar sus sistemas a una arquitectura moderna estrangulando aplicaciones legadas y adoptando microservicios. Estas empresas están enfrentando la migración como el problema a ser resuelto haciendo grandes inversiones, sin embargo están dejando de lado el problema real que los sistemas legados están ocasionando en relación a la incapacidad de agregar nuevas funcionalidades y acelerar el time to market.

“Migramos y luego nos movemos rápido”.

Aunque a primera vista luce como una solución atinada, el problema de las migraciones buscando un “feature parity” es que el beneficio es percibido únicamente cuando la migración se haya completado, porque solo es en ese momento cuando se pueden agregar las nuevas funcionalidad que el negocio requería, sin embargo el objetivo es obtener los beneficios desde el primer momento de la inversión, y no al final cuando quizás ya no exista el presupuesto, o la prioridad de las iniciativas haya cambiado.

Migrar sistemas buscando feature parity a una nueva arquitectura implica, muchas veces, un camino sin fin, en donde el momento en el que la migración “finalice”, habrán aparecido tantas nuevas herramientas y tecnologías que la nueva versión será obsoleta o legada incluso antes de ver la luz.

El tema no está en cómo migramos los sistemas para que luego nos den resultados; el foco debería ser en cómo lidiamos con estos sistemas legados para que sean un catalizador y no un obstáculo al momento de atender los requerimientos de negocio.

Con el uso adecuado de patrones de arquitectura como autonomous buble, capas de anticorrupción, open host, sincronización de fuentes de datos mediante eventos, etc. se puede enfrentar los problemas típicos sin una necesidad imperiosa de migrar los sistemas por completo para obtener resultados positivos, sino generando valor de negocio desde etapas tempranas, haciendo implementaciones pequeñas con beneficios rápidos evitando grandes inversiones que dilatan la generación de valor con apuestas futuras que no siempre llegan a ver la luz.

La pregunta no está en cómo migramos sino cómo lidiamos con los sistemas legados para que puedan apalancar nuevas ideas e implmentaciones en un ecosistema mixto.

Advertisement

Funciones de Aptitud (Fitness Functions)

Cuando creamos una arquitectura, lo hacemos con un propósito, y muchas (o todas) las veces pensamos que la arquitectura que estamos ideando antes de iniciar el desarrollo es la que va a cumplir el propósito, que puede ser asegurarse del rendimiento, auditabilidad, escalabilidad, mantenibilidad, etc. (los llamados requerimientos no funciones)

El hecho es que muy pocas veces (o nunca) en realidad verificamos que el propósito de la arquitectura ideada se esté cumpliendo. Así mismo cuando nos encontramos desarrollando las aplicaciones y evolucionando la arquitectura (todas las arquitecturas evolucionan), no sabemos en realidad si el desarrollo de una solución está “respetando” o preservando la arquitectura o los principios de arquitectura, y nos damos cuenta cuando ya es muy tarde.

Parte de los conceptos de una arquitectura evolutiva son las funciones de aptitud (fitness functions) que en esencia, tratan de hacer verificables las características principales de arquitectura que guían una solución de manera constante e incremental.

Es así que cuando estamos diseñando una arquitectura nos preocupamos de que la solución sea escalable o mantenible (bonitas palabras), pero que significa que sea escalable, que tenga “buen” rendimiento o que sea mantenible, justamente es aquí donde entran las fitness functions, buscando hacer más objetivas estas características mediante mecanismos de verificación constantes.

Por ejemplo: si decimos que una arquitectura busca que la solución tenga “buen” rendimiento, entonces necesitamos crear una(s) función de aptitud que defina que significa “buen rendimiento”, creando un mecanismo de control que verifique en cada nueva versión de la aplicación que las operaciones o transacciones más importantes no tomen más de medio segundo en ser ejecutadas; esta función de aptitud puede ser ejecutada en el proceso de integración o entrega continua antes de liberar un nueva versión a producción.

Si buscamos que sea mantenible y entendemos que una de los aspectos para que la solución sea mantenible es crear una arquitectura en capas dónde buscamos que solo existan dependencias en una dirección (figura 1), entonces debemos crear una función de aptitud que verifique las dependencias entre paquetes sean solamente las que hemos definido (figura 2).

Captura de pantalla 2018-02-27 a la(s) 15.41.53

figura 1 –  Arquitectura en Capas

Captura de pantalla 2018-02-27 a la(s) 15.57.12

figura 2 – Función de aptitud que analiza dependencia entre capas

Dado que cuando estamos construyendo arquitecturas evolutivas buscamos soportar un cambio incremental y guiado a lo largo de múltiples dimensiones, es importante saber hacia donde estamos guiando el cambio y es aquí donde las funciones de aptitud juegan un papel principal, ya que son controles que nos permiten identificar si las características principales de arquitectura se están preservando al mismo tiempo que la arquitectura evoluciona, o en otras palabras: Las funciones de aptitud nos permiten identificar si la arquitectura está evolucionando hacia donde queremos que evolucione.

Ley de Conway en la Arquitectura de Software

División de equipos por capacidades de negocio

En 1967 M. Conway enunció una frase en la conclusión de su publicación How do committees invent? a la que Fred Brooks, la popularizó con el nombre de Ley de Conway en su libro The Mythical Man-Month.

“… las organizaciones que diseñan sistemas … están limitadas a producir diseños que son copias de las estructuras de comunicación de estas organizaciones.” M. Conway – 1967

El hecho es que a pesar de no ser una ley científica, es una proposición que es válida en muchos entornos y se la puede evidenciar en nuestro lugar de trabajo o en otras empresas que producen software.

La división organizacional de una empresa se verá reflejada en los sistemas que esta organización produce; si existe un departamento de cobranzas, de seguro existirá el sistema de cobranzas, si existe un área de notificaciones habrá un sistema llamado SISNOT (sí que somos originales para los nombres), el departamento financiero a su vez tendrá sus SISFIN, etc. y los sistemas se integrarán o se comunicarán entre ellos tal como se comunican las divisiones de la organización.

A su vez esta ley también entra en vigor al momento de la construcción de cada uno de estos sistemas, la arquitectura de dichos productos reflejará la estructura de comunicación de los equipos que forman parte de este proceso.

Es así que si el departamento de tecnología está dividido por conceptos técnicos, encontrando grupos de trabajo destinados a la interfaz de usuario, otro grupo a la base de datos, otro a la infraestructura, otro al manejo de procesos, otro a la implementación de lógica en el lado del servidor (figura 1); la arquitectura de estos sistemas (figura 2) será muy similar a estas estructuras de comunicación.

Screen Shot 2016-07-26 at 11.13.44 AM
figura 1 – División técnica de los equipos de trabajo
Arquitectura guiada por aspectos técnicos
figura 2 – Arquitectura guiada por aspectos técnicos

Los sistemas son dinámicos, siempre existen cambios y generalmente estos son ocasionados por cambios en las definiciones del negocio, si se favorece una división organizacional por capacidades técnicas, cada cambio en la definición del negocio exigirá trabajo, asignación de presupuesto, tiempo, etc. en cada una de las áreas técnicas que componen el grupo de trabajo; esta es una de las razones por las que en algunas organizaciones se tiende a agrupar requerimientos para “optimizar” el tiempo y los recursos, alargando el proceso de desarrollo hasta que “justifique” todo el trabajo que se debe llevar a cabo, lo que lleva a la insatisfacción del negocio con respecto al departamento de tecnología.

Por otro lado podemos encontrar organizaciones que favorecen equipos multidisciplinarios o multifuncionales que son compuestos por diferentes roles y guiados por las capacidades del negocio (figura 3) donde los cambios producidos por nuevas definiciones del negocio son ejecutados de principio a fin por un solo equipo evitando la sobre gestión de estos procesos, produciendo arquitecturas diferentes (figura 4), generalmente más distribuidas y con mayor capacidad de evolución.

División de equipos por capacidades de negocio
figura 3 – División de equipos por capacidades de negocio
Arquitectura guiada por el negocio
figura 4 – Arquitectura guiada por el negocio

A la final el software es producto de un proceso intelectual colaborativo, por lo que reflejará las ideas de las personas que intervienen en este proceso y como hemos visto de las estructuras de comunicación entre equipos; es por ello que tomar en cuenta esta ley nos vendría bien a la hora de estructurar los equipos dependiendo de qué queremos conseguir.

Architecture Process – Example

The business problem

Recently I had a requirement because of a new retention policy regulation: we need to store certain files the company generate among all the business units, into one single repository, this repository is a third-party out of our control tool.

Additionally we have few applications where files are generated, uploaded and saved; a couple of mechanisms are used, for instance in one application the files are saved into the database (I don´t know why but they are), in other system the files are stored in the Distributed File System, and another one uses ftp to load files.

We saw a good opportunity to change the heterogeneous way to deal with files across all the applications, looking for a reusable solution based on the business capabilities related with files and document management in an homogeneous way.

Drilling down features and restrictions

After a couple of sessions with the stakeholders and the BA’s, we could determine what are the minimum features that will enable the usability of the solution, as well we defined which restrictions we should keep in mind.

Business requirement / Restriction
Required Capability
The business could change eventually the third party tool for other tool.
Decoupling of implementation
The third party tool is the final repository for certain documents not all of them, we have to handle diverse final repositories as DFS, DB, FTP, SharePoint, etc.
Decoupling of implementation
Files will be stored in one repository or other depending on certain rules like the type of document, business unit, date, etc.
Extensibility
We don’t have any access to configure, start or stop services in the third party tool or any other tool.
Decoupling of implementation
<Conformist Approach, Anticorruption layer>
Just the entitled users can access to send and retrieve documents.
Secure
Users should always be able to upload files no matter if is the third party tool selected or any other tool is unavailable.
Reliability
Rules change always, the business need a fast answers to change.
Agility, Extensibility, Maintainability
Something can go wrong with third party tools, we need to handle errors in a friendly way to the user.
Reliability
We should avoid change the user experience for loading and retrieving files.
Synchronous
Loading and retrieving files duration time shouldn’t increase.
Efficiency

Designing before coding

Designing before coding? Yes, I know I say that; maybe some of my agile colleagues will fight with me just for the phrase, however I truthfully think that we must analyze the problem and of course even create diagrams to transmit the idea and represent the solution somehow before the implementation (responding to change over following a plan, is not the same as responding to change and not have a plan).

Decoupling and reusing

We decided to create a new software component to encapsulate all the business logic to manage documents (see Figure 1); in this way we can implement the new requirement on top of this artifact, and other new related requirements, reducing the changes in the applications that will use it.

Screen Shot 2016-07-22 at 10.31.33 AM
Figure 1: The very first design

Creating the architecture

“A software architecture begins as the result of the collective set of design choices made by the team that created the very first version of a system” [Beyond Software Architecture]

In the same way we need a MVP, we need a MVA (Minimum Viable Architecture) to build the product.

Among dev meetings and conversation with the business stakeholders, through the creation of sketches (See Figure 2) and whiteboard drawings, we ended up with an initial plan.

Screen Shot 2016-07-22 at 10.31.43 AM
Figure 2: One of the architecture sketches

The plan was presented validating the requirements and restrictions in a high level perspective, walking through different scenarios based on BA’s experiences.

We need to keep in mind that this is a plan, and plans can change, because requirements can change, technology can change, we are not Nostradamus “to know certainly the future” and assert that the proposal will cover all the requirements before implementing, however having a plan allows us to start building some components with a vision of the product.

Screen Shot 2016-07-22 at 10.32.04 AM
Figure 3: The solution

Explaining the architecture

The solution (see Figure 3) is based on queues and asynchronous processing of document and files, it is based as well in independent deployable components. These decisions were made based on maximizing the reliability, scalability and decoupling over other requirements.

One of the first things we need to understand is that we have two concepts here: Documents and Files. A Document is a concept that will contain basic business information like the business unit, type, application that generated and the file. A File is the actual binary file that will be stored in one external storage.

We need to know that the only big purpose of this is creating and retrieving documents in a secure and reliable way.

Document Service

It is the only entry point for the petitions of the different consumers, consumers can’t access to files nor documents without going through the services that this component expose (Security). It has its own data base to store and retrieve the information of the documents, and the only necessary information to ask for retrieving and storing files.
In order to attach files to the created document, this component will send messages to the “newDocuments” queue to be processed asynchronously for the “DocumentProcessorManager” component.
In order to retrieve files this component will use the “FileStorageService” component.

Document Processor Manager

This component reads the messages delivered in the “newDocuments” queue, through the “FileCompass” determine where the file should be stored, and delegates the storing implementation to the “FileStorageManager” component. FileCompass is a lightweight component which knows, based on a set of rules (persistent rules, maybe a csv file or DB), where the file should be stored.

File Storage Manager

The responsibility of this component is communicate with the different external storage clients for retrieving and creating files, as well handling the errors when external storages won’t be available.

External Storage Clients

With the “FileStorageManager”, they will conform the anti corruption layer [Domain Driven Design] to decouple the solution of the external storages.

Error Handling queues

The purpose of these components is assure the reliability of the application; these queues will handle the errors when the external storages won’t be available. When is not possible to retrieve a file, the request will be sent to “deliverFiles” queue, so when the service is available again the file will be sent through an email or other mechanism. When is not possible to store a file, the file will be saved in a local FileSystem and the request will be sent to “movingFiles” queue, so when the service is available again the processor will read the messages in the queue and will store the files where it was suppose at the beginning.

Explaining the interaction between the consumers and the Document Service allows us summarize the solution from the consumers point of view, this is interesting for devs who will only use the new solution, and for the BA’s to understand the effort that will require add more consumers (Extensibility)

Creating a Document

A document will be stored synchronously and will send the message to attach the correspondent file asynchronously, this way we can guaranty that the request is saved and eventually the file will be attached. (see Figure 4)
Screen Shot 2016-07-22 at 10.32.17 AM
Figure 4: Document Creation Sequence

Retrieving a Document

To retrieve just the information of the document without a file :

HTTP GET …/documents/7895

To retrieve a File inside a document:

HTTP GET …/documents/7895/file

Conclusions

Everything is a trade off

We tried to generate a solution to satisfy all of required capabilities, of course that was not possible, in pos to get some capability we minimize other ones, in software, everything is a trade off. e.g. In order to assurance the reliability we need to sacrifice the efficiency or the synchronous processing. In order to decouple components we need to sacrifice the efficiency again. So we started to prioritize the required capabilities with the stakeholders really quick, doing questions like: “if you have to choose between, having a 2 seconds respond when the file is uploaded, against not be sure if the file can be uploaded because eventually some of the third party repositories won’t be available, Which one would you choose?”, we could get answers like we must to create a solution that won’t depend on the third party repositories, even if that means more time in the uploading or even if we need to change the user experience to not process files synchronously; in that sense we could identify the “must to do” requirements/capabilities.

Benefits

  • Easy to scale
  • Reliability – Error handling
  • Decoupled from third party storages
  • We have 2 (DocumentService, DocumentProcessor) deployable artifacts
    • Scale independently
    • Deploy independently
  • There is no need to deploy the consumers when a change is introduced in the document services
  • Responsibilities are segregated into different artifacts

Pitfalls

  • Change current error handling (new or unknown scenarios)
  • Current user experience will change
  • Eventual Consistency

Capas de Anticorrupción

Ese momento de pánico cuando se decide cambiar la aplicación X de la que depende nuestro sistema.

Cuando construimos sistemas, la mayoría de las veces, estos dependen o necesitan de aplicaciones externas o de terceros. El momento en que una de esas aplicaciones externas necesita ser cambiada o se necesita hacer una actualización es cuando los problemas no cesan.

El mayor inconveniente muchas veces, no es que la nueva aplicación o versión no tenga las mismas funcionalidades que la anterior (de hecho muchas veces tiene más), el problema radica en que nuestro sistema está invadido de dependencias de esta aplicación externa; el problema es que nuestro dominio se ha visto corrompido por un dominio externo.

Screen Shot 2016-06-29 at 3.59.57 PM

Por ejemplo: Tenemos un sistema (cajas verdes) que maneja entre otras funcionalidades,  archivos y documentos, para lo cuál se ha decidido utilizar un repositorio de archivos como Alfresco (caja amarilla). Alfresco (o cualquier otra herramienta) provee librerías o API’s que son utilizadas por nuestro sistema para la integración. En este punto nuestro sistema/dominio tiene una dependencia de la herramienta externa a lo largo de varios componentes, por lo que decimos que nuestro dominio está corrompido ya que la herramienta externa (que puede ser un dominio diferente) esta “regada” por todo lado. El momento en que decidamos cambiar la herramienta Amarilla por una Roja se deberá buscar todas las dependencias en nuestro dominio y hacer el cambio hacia las nuevas dependencias.

Podemos evitar este problema introduciendo una Capa de Anticorrupción (término acuñado en el libro DDD) que nos permiten aislar las dependencias externas o de terceros, sean estas herramientas, sistemas, dominios diferentes, etc.

Screen Shot 2016-06-29 at 4.14.17 PM

La idea es construir una capa intermedia entre nuestro dominio y la dependencia externa, esta capa es la responsable de hacer las transformaciones en ambos sentidos, es decir; transformar los componentes de nuestro dominio en componentes que pueda entender la dependencia externa y viceversa. De esta forma logramos aislar esta dependencia en un solo punto, de tal modo que nuestro dominio (cajas verdes) esté enfocado en resolver el problema puntual para el que fue diseñado y que cualquier cambio en la dependencia externa no afecte mayormente al comportamiento de nuestro sistema.