La experiencia Bancolombia en tiempos de revolución

Carlos Andres Agudelo
Bancolombia Tech
Published in
10 min readOct 19, 2022

“La fe es dar el primer paso, incluso cuando no ves la escalera completa: Martin Luther King”.

Llegar a Bancolombia representa, en la mayoría de los casos, dar un salto de fe en la búsqueda por acercarnos a una buena fuente de conocimiento y verdad en temas tecnológicos. Al ser una de las entidades financieras más grande de Colombia y una de las mejores en el continente, nuestras tecnologías han tenido que ser revolucionarias con el fin de poder atender la alta demanda que el mercado actual exige.

Debido a esto, es probable que desde el primer instante comencemos a escuchar términos que quizás no hayan sido mencionados en nuestro proceso formativo, lo cual suele ser abrumador e intimidante… todo cambio lo es. Allí es cuando la plasticidad de nuestro cerebro juega un papel vital para el éxito, pues cuando hablamos en términos de tecnología, estar a la vanguardia significa enfrentar una curva de aprendizaje cuya pendiente parece ser infinita. En este artículo, no tan técnico, pero sí muy estratégico, descubrirás cómo nuestra organización reacciona ante los cambios que nos propone la cuarta revolución industrial.

Para comenzar y tener un gran entendimiento del enfoque que aquí se plantea, será necesario que el lector guarde en su memoria la siguiente premisa: todo cambia”. Justo como se explica en el manifiesto de sistemas reactivos, los requerimientos de las aplicaciones han evolucionado drásticamente en los últimos años, la cantidad de usuarios que solicitan nuestros servicios crece de manera exponencial, haciendo necesario adoptar estrategias de despliegue en cualquier tipo de ambiente, desde dispositivos móviles, hasta grandes clusters en la nube corriendo en miles de procesadores.

Abrazando al cambio, en Bancolombia adoptamos la postura de la reactividad, desde el lenguaje en el cual codificamos nuestras soluciones, hasta la manera en la cual definimos estratégicamente las tecnologías que emplearemos para soportarlas.

En ese orden de ideas, hemos impulsado el uso y creación de iniciativas de Open Source, pues comprendemos que la mantenibilidad del software debe partir desde el trabajo colaborativo, siguiendo los lineamientos de un modelo de desarrollo global y descentralizado, que le apunta a garantizar, entre tantos beneficios, la innovación.

Para conocer un poco más de ésta iniciativa y su importancia, puedes referirte la siguiente lectura: Open Source: ¿qué es y cuál es su importancia?.

El cambio a nivel de diseño, construcción y documentación.

Un buen punto de partida relaciona dos conceptos que son vitales para la construcción de nuestras aplicaciones: Clean Architecture y Domain Driven Design. Una gran arquitectura procura que las decisiones que se tomen a nivel de tecnología tengan la menor inferencia posible sobre el resultado final del producto. En otras palabras, la arquitectura limpia promueve el alto desacople de los componentes que conforman el todo de una solución, teniendo como objetivo, separar la complejidad accidental (relacionada a la tecnología empleada), de la complejidad esencial, que hace parte de la naturaleza misma del problema a resolver.

Es esa complejidad esencial la que podremos ver plasmada en los flujos funcionales identificados en los talleres de Event Storming que se realizan durante las fases de diseño táctico; los cuales, al leerlos, nos darán un entendimiento a nivel funcional de lo que pretende resolver el proyecto en cuestión; y por otro lado, nos familiarizará con ese lenguaje ubicuo (que hace referencia a las palabras claves que definen la solución misma) que podremos encontrar explícitamente codificado en la capa de dominio.

En la imagen anterior observamos un flujo funcional obtenido tras la realización de un taller de Event Storming táctico. Para el ejemplo en cuestión, tomamos el caso de un sistema de reserva de asientos en una sala de cine y su posterior implementación en código utilizando el plugin de Clean Architecture. Allí se resaltan de manera respectiva, los comandos, entidades y eventos con los colores azul, amarillo y naranja (código netamente ilustrativo); distinguiendo claramente en verde las diferentes capas que conforman el arquetipo del microservicio: aplicación, infraestructura y dominio.

Si quieres profundizar más acerca de estos términos anteriormente mencionados, te invitamos a realizar las siguientes lecturas: Clean Architecture -Aislando los detalles y Domain driven design en Bancolombia — De lo estratégico a lo táctico.

Las prácticas antes mencionadas trabajarán en conjunto con un estándar de escritura (code style), que permita garantizar la mantenibilidad de las aplicaciones en el tiempo, es por esto que hacemos énfasis en la manera en la cual se declaran las clases, funciones y constantes. Las palabras empleadas deben ser claras, expresivas, que reflejen la intención o el objetivo que cumple la función. El tamaño de las clases debe ser pequeño; contar sólo con los métodos necesarios; con una complejidad ciclomática reducida (variantes en el flujo de ejecución de un programa); no superar cierta cantidad de líneas de código; con el objetivo de cumplir con el principio de responsabilidad única que garantice el aislamiento, un bajo acoplamiento y una alta cohesión.

Tener un código legible busca que el proceso de mantener soluciones sea agradable y fácil de trabajar. Esto, en conjunto con el resultado del Event Storming; los diagramas de arquitectura y de secuencia, representan la adecuada Documentación de los proyectos, y a su vez, la mejor fuente de estudio para conocer el equipo y la solución de la cual haremos parte.

Otro punto interesante para destacar en la imagen es la palabra Mono, que proviene de la implementación de la librería reactor de Java. El uso de programación reactiva y funcional en computación distribuida para el desarrollo de los microservicios se acopla fácilmente a lo que el manifiesto resalta: elasticidad, resiliencia, responsividad y orientación a mensajes, como se expone en el artículo Potenciando la Arquitectura de Microservicios Reactivos. Es esa orientación a mensajes la que permite la comunicación asincrónica entre diferentes funcionalidades, eliminando dependencias como la estabilidad en la red, que es requerida cuando empleamos métodos tradicionales de comunicación como RPC (Remote Procedure Call) y REST (Representational State Transfer).

¿Cómo ser reactivos a nivel de aplicación?

Hasta el momento hemos analizado como empleamos el Domain Driven Desing para encontrar el núcleo de las funcionalidades, y como nos apalancamos de Clean Arquitecture para proteger esa lógica de dominio de los efectos producidos por la tecnología empleada. Hemos visto como estas técnicas nos han traído versatilidad en los desarrollos, como a nivel de diseño y código podemos integrar nuevos cambios facilitando el mantenimiento. Ahora es el momento de subir al siguiente nivel: ¿cómo aprovechamos las bondades de la computación distribuida en la nube para seguir garantizando la evolución ante el cambio?

El uso de contenedores ha permitido que las aplicaciones puedan correr casi en cualquier ambiente o dispositivo al empaquetar toda la lógica, sistema operativo y demás recursos necesarios en una sola unidad. En Bancolombia, hacemos uso del servicio de EKS (Elastic Kubernetes Service) de AWS para orquestar correctamente el ciclo de vida de esos contenedores, a fin de que nuestras aplicaciones puedan responder de manera adecuada ante cualquier aumento o disminución de la carga concurrente.

La capacidad de escalamiento de un sistema determina cual será el rendimiento de esa aplicación, lo que finalmente se verá reflejado en la experiencia del usuario. Horizontal Pod AutoScaler o HPA es un método de computación en la nube, propio de Kubernetes, que determina la cantidad de recursos computacionales medida en términos de servidores activos que pueden ser aprovisionados para atender las necesidades de una solución. HPA escala el número de réplicas de un pod, donde la CPU y la memoria actúan como factores determinantes para decidir si aumentar o disminuir la cantidad de replicas.

HPA de forma predeterminada escanea cada 15 segundos la utilización de los recursos de los pods que dirige y lo compara con la media aritmética con el objetivo de realizar el ajuste. Cabe resaltar que hasta el día de hoy no se tiene una fórmula general que determine los valores referencia para el escalamiento de una aplicación; en su lugar, lo que hacemos es levantar el servicio con un sólo pod sin límites, para determinar las características de consumo en stand by.

Posteriormente, ejecutamos pruebas con cargas variantes, apoyados de herramientas como el Performance Analyzer, con el objetivo de identificar ese punto exacto en el que la concurrencia comienza a degradar el servicio, como lo dicta la ley universal de escalabilidad , para luego realizar un análisis teniendo en cuenta los requisitos no funcionales que espera el negocio, a fin de determinar los parámetros que garanticen una solución costo-eficiente y que serán configurados en el mecanismo de autoscaling.

Por otro lado, el servicio de EKS nos permite realizar el monitoreo de los pods que se encuentran en funcionamiento, a fin de determinar el estado de salud (Health Check) de estos y poder tomar medidas de acción en caso de ocurra un fallo. Con esta medida buscamos que el usuario final nunca se vea afectado por la indisposición del servicio. Allí es donde aparecen dos conceptos que son claves: liveness probe y readiness probe.

Liveness probe realiza las pruebas de vida para determinar cuándo un pod se está ejecutando pero no puede avanzar. Al detectar ese estado, kubelet (agente que se ejecuta en cada nodo de un clúster) se encarga de lanzar la operación para reiniciar el contenedor a través de una política previamente definida.

Por otro, para evitar que el balanceador de carga lance transacciones a un pod que se encuentra en fase de reinicio, se utiliza la prueba de Readiness probe, que nos indicará cuando el contenedor se encuentra preparado para atender peticiones.

Existe un tercer concepto: Startup probe, el cual es usado en aquellas aplicaciones que poseen un tiempo de inicio elevado. El Liveness y el Readiness dependerán del resultado de ésta prueba.

Con este conjunto de medidas, al menos a nivel de aplicación, garantizamos que nuestros servicios siempre se encuentren disponibles, logrando la alta resiliencia propia de sistemas reactivos. Estas iniciativas, deben ir acompañas de una adecuada política de despliegue, que permita que los cambios puedan ser integrados de manera casi transparente para los clientes; por esta razón, empleamos técnicas como Feature Flags o Canary Deployment; la primera para liberar una nueva característica en una aplicación y que sea visible solo por cierto público objetivo; la segunda para direccionar el tráfico de una aplicación entre la versión estable y la nueva, garantizando, que en caso de un fallo, solo se afecte a un porcentaje muy reducido de clientes.

En el artículo Feature Flags: velocidad y reducción de riesgos, encontrarás todas las ventajas que trae consigo la implementación de esta práctica.

¿Y qué pasa con las bases de datos?

Para entender como llevamos el cambio a nivel de bases de datos, se debe mencionar el teorema CAP (Consistency, Availability y Partition Tolerance), en el cual se plantea, que los motores de bases de datos solo pueden cumplir con dos de las tres características que hacen alusión a sus siglas.

En sistemas financieros, garantizar la consistencia y la disponibilidad de la información son quizás los factores más determinantes a la hora de elegir el tipo de bases de datos que acompañara nuestra solución, pues cuando de dinero y de números se trata, la seguridad y la exactitud en las transacciones se hace necesaria. Es entonces cuando nos preguntamos: ¿Cómo podemos flexibilizar las bases de datos relacionales para que puedan trabajar correctamente en sistemas distribuidos?

La consistencia eventual (Strong Eventual Consistency) es el resultado de la evolución del acrónimo ACID 2.0 (Associative, Commutative, Idempotent y Distributed), que definen las características de las bases de datos SQL para trabajar en sistemas distribuidos. La propuesta del patrón arquitectónico Command Query Responsabilty Segregation (CQRS), que es la que más se acomoda para llegar a la consistencia eventual, trata básicamente de realizar una segregación a nivel de transacciones, con el fin de que las operaciones de lectura (que no causan afectación en caso de fallo) puedan transitar por un camino menos restrictivo y tolerante a la partición; diferente al que siguen las operaciones de escritura, que implica un manejo más cuidadoso y riguroso.

Es así como podremos contar con un sistema de persistencia distribuido, en el cual, tendremos una base de datos maestra, encargada de atender las peticiones de escritura (comandos), y que posteriormente proyectará el resultado final sobre sus réplicas, siendo éstas las empleadas para ejecutar las peticiones de lectura.

Estas características y filosofía de diseño parten desde la definición de la palabra idempotencia, el cual es un término y propiedad utilizado en matemáticas y ciencias de la computación, para definir ciertas operaciones pueden ser aplicadas múltiples veces sin cambiar el resultado después de la primera interacción. Su implementación dependerá del escenario y de las reglas de negocio que apliquen a cada proyecto.

De esta manera, hemos realizado un viaje que ha abordado las principales Prácticas para el Desarrollo de Software desde un punto de vista muy general, basado en la experiencia obtenida tras enfrentar la curva de aprendizaje relatada. En Bancolombia buscamos que el proceso para el desarrollador sea el más ameno posible, pues entendemos que las personas son la razón de ser dentro de nuestra cultura ágil.

--

--