10 Aplicaciones reactivas y el aumento de la complejidad en el desarrollo de software
10.1 Entrevista
¿Quién es el Ing. Víctor Orozco y cuál es su experiencia relevante en el ámbito de la tecnología?
Me defino como un obrero del código con 15 años de experiencia como desarrollador de software. Actualmente, en Nabenik, trabajo como profesor universitario y consultor para sectores como banca, telecomunicaciones y gobierno. Me enfoco en arquitectura de software, buenas prácticas de desarrollo y capacitación de equipos. Además, participo en los programas Java Champions y Oracle, que fomentan comunidades de desarrolladores y buenas prácticas a nivel global.
¿Qué es Nabenik y qué papel desempeña en el sector tecnológico?
Fundamos la empresa como una consultoría de software. Después de que estuve en el extranjero de 2012 a 2014, regresé a Guatemala y junto a mi socio, Luis Pedro Estrada, expandimos el negocio para incluir tercerización de recursos, capacitaciones y representación de productos, siempre alineados con la demanda del mercado. Nabenik, que significa “conocimiento” y “viento” en mam, se enfoca principalmente en el mercado centroamericano, aunque también hemos trabajado en México, Latinoamérica, Estados Unidos y España. Nos definimos como una consultoría de software que acompaña todo el proceso de transformación digital.
¿Cuál fue su principal motivación para crear Nabenik? ¿Qué desafíos significativos enfrentó y cómo los superó?
Mi motivación para emprender en Guatemala fue accidental, surgió al desarrollar una aplicación móvil en un contexto donde había poco desarrollo en ese ámbito. Esto llevó a la necesidad de formalizar la empresa por razones fiscales y de facturación, permitiéndonos crecer y delegar funciones. Sin embargo, enfrentamos dificultades, como la falta de acceso a capital de riesgo, lo que nos obligó a optar por un periodo de bootstrap para reinvertir las ganancias. Aprendí que un negocio solo es viable si satisface necesidades reales y genera ingresos, lo cual fue un reto, especialmente por mi perfil técnico, lo que me llevó a aprender sobre la marcha.
¿Qué son las aplicaciones reactivas y cuáles son sus características distintivas?
Las aplicaciones reactivas surgen de la demanda actual de usuarios que esperan experiencias fluidas y eficientes. A diferencia de principios de los 2000, hoy las aplicaciones están diseñadas para un público amplio, enfrentando retos técnicos para manejar miles de usuarios. El Reactive Manifesto establece cuatro características esenciales: deben ser responsivas, elásticas (capaces de escalar con la demanda), resilientes (recuperarse automáticamente de fallos) y tener un enfoque de mensajería, donde las aplicaciones funcionan como un conjunto de pequeños servicios en lugar de un monolito. En resumen, las aplicaciones reactivas son una forma de construir sistemas distribuidos que optimizan recursos y costos para soportar un alto número de usuarios.
¿Cómo se diferencia el modelo de programación reactiva del modelo de programación de enfoque imperativo?
Es importante distinguir entre una aplicación reactiva y un lenguaje de programación con patrones reactivos; la primera se centra en la arquitectura, mientras que el segundo aborda cómo se genera el código. Tradicionalmente, lenguajes como Java y .NET utilizaban un modelo de programación blocking, donde pocos hilos atendían múltiples clientes, lo que podía causar cuellos de botella. La solución, popularizada por Node.js, fue adoptar paradigmas de programación reactivos, donde un solo hilo gestiona eventos en un bucle, permitiendo procesar múltiples solicitudes sin bloquear el sistema. Aunque esto resuelve muchos problemas de concurrencia, su complejidad requiere programadores capacitados, a diferencia de la programación imperativa.
¿Qué estrategias recomienda para gestionar la complejidad del código en aplicaciones reactivas?
Recomiendo dos estrategias para manejar la programación reactiva: primero, aprovechar las características de lenguajes como Kotlin y JavaScript, que ofrecen estructuras para gestionar callbacks eficientemente, como las corrutinas en Kotlin y async/await en JavaScript. La segunda estrategia es utilizar patrones de tipo RX, comunes en Angular y RX Java, que permiten ejecutar tareas en cola y encadenar funciones de manera reactiva. En cuanto a tendencias, creo que los hilos tradicionales quedarán en desuso, siendo reemplazados por virtual threads en nuevas versiones de la máquina virtual de Java, lo que permitirá una ejecución más eficiente del código imperativo. Las tres formas principales de implementar programación reactiva son: aprovechar las facilidades de los lenguajes, usar bibliotecas reactivas como RX y adoptar virtual threads.
¿Cómo influye la programación reactiva en la depuración y el manejo de errores dentro de una aplicación?
El mayor reto de la programación reactiva es la depuración, especialmente al trabajar con callbacks, ya que el flujo de ejecución es asíncrono y salta de una función a otra, dificultando la identificación de errores. En JavaScript, aunque las funciones son ciudadanos de primer nivel, es complicado rastrear fallos. En Java o C#, donde se utilizan expresiones lambda, los stack traces se vuelven más detallados, haciendo aún más difícil localizar el problema. Aunque herramientas como async/await o corrutinas pueden ayudar, depurar código reactivo sigue siendo más complejo que en la programación imperativa, debido a la falta de una secuencia clara de ejecución.
¿Cuáles son las mejores prácticas para gestionar estados y eventos en aplicaciones reactivas?
En aplicaciones pequeñas, la gestión suele hacerse a través de bibliotecas, pero en aplicaciones distribuidas grandes se introduce un bus de comunicación y patrones más complejos. Dos patrones comunes son el event sourcing, donde se registran eventos en un bus (como Kafka) para que sean consumidos por otros componentes, y CQRS, que separa las operaciones de lectura y escritura. En CQRS, los comandos alteran el estado sin requerir una respuesta inmediata, mientras que las consultas notifican al usuario sobre la finalización de esos comandos. Para implementar programación reactiva a gran escala, es crucial dominar la comunicación de eventos, utilizando herramientas como Kafka, Camel, o servicios en la nube como SQS o SNS. Es fundamental entender que se está programando actores (microservicios) que responden a eventos en lugar de seguir un enfoque imperativo tradicional.
¿Qué consideraciones deben tenerse en cuenta al definir requisitos y entregables en proyectos que utilizan programación reactiva y enfoques similares?
Es fundamental evaluar si realmente se necesita programación reactiva. Aunque se puede implementar un sistema reactivo con programación imperativa, no siempre es necesario. En sistemas con pocos usuarios (100 a 500), seguir con un enfoque blocking tradicional es más práctico y ha funcionado durante años. Incluso empresas como Netflix, que utilizan Java y Spring, prefieren el código imperativo por su simplicidad en la depuración y la observabilidad, a pesar de tener arquitecturas distribuidas.
En contextos donde se justifica, como la ingesta de datos, frameworks reactivos como Vert.x o Spring Reactor pueden ser útiles. Sin embargo, en sistemas simples, la programación reactiva puede complicar más que ayudar. Un buen arquitecto debe evaluar si la programación reactiva realmente aporta valor y, si es así, determinar qué partes del sistema deben ser reactivas y cuáles pueden seguir siendo síncronas, creando una arquitectura equilibrada.
¿Qué bibliotecas o frameworks son populares para el desarrollo de aplicaciones reactivas y cómo simplifican el proceso, dado su potencial para aumentar la complejidad?
En la actualidad, en el contexto de la JVM, las corrutinas son muy populares en Kotlin, mientras que para Java, Spring WebFlux, basado en Spring Reactor, es el framework más utilizado. Vert.x también es relevante, especialmente por su respaldo de RedHat y sus múltiples abstracciones, aunque programar a bajo nivel con Vert.x puede parecerse al uso de callbacks. Es importante considerar que las bases de datos deben contar con drivers reactivos compatibles.
La JVM ofrece APIs como Completable Futures para la programación reactiva sin necesidad de frameworks específicos. Si el backend es Java y el frontend es TypeScript, se recomienda explorar RX para mantener un paradigma de programación coherente entre ambos. Además, dominar async/await en TypeScript y JavaScript es fundamental, ya que es una herramienta clave para la mayoría de los programadores antes de abordar patrones más complejos que justificarían el uso de RX.
¿Cómo se integra la programación concurrente y distribuida en el diseño de aplicaciones reactivas?
La programación concurrente y la programación reactiva son conceptos complementarios. La concurrencia se refiere a la ejecución de hilos y su coordinación, a menudo enseñada a través de semáforos y modelos de concurrencia. En contraste, la programación reactiva abstrae estos conceptos, permitiendo que los programadores se centren en el envío de eventos a un bucle de procesamiento sin preocuparse por los hilos subyacentes.
Node.js, por ejemplo, utiliza un event loop y puede manejar múltiples hilos para diversas tareas. La programación reactiva se basa en esta concurrencia para funcionar eficazmente. Aunque la programación distribuida y la reactiva suelen estar relacionadas, no son condiciones mutuamente exclusivas; se puede crear una aplicación monolítica con APIs reactivas o una aplicación distribuida con código imperativo. La elección de usar uno u otro dependerá de las necesidades específicas del proyecto.
¿Cuáles son las principales dificultades en el mantenimiento de aplicaciones reactivas en comparación con aplicaciones tradicionales?
El seguimiento del código en aplicaciones legacy, que suelen tener muchos años de éxito, plantea desafíos significativos, especialmente en su mantenimiento. A menudo, los desarrolladores se enfocan en lanzar aplicaciones rápidamente sin considerar su sostenibilidad a largo plazo. Para mitigar problemas futuros, se recomienda usar lenguajes de programación tipados, como TypeScript, que facilitan la lectura y comprensión del código. A medida que los proyectos envejecen, la falta de tipado en lenguajes como JavaScript puede dificultar la comprensión del código original, que puede estar lleno de diversas estructuras. La clave es optar por lenguajes que promuevan un tipado explícito para mejorar la mantenibilidad y evitar que el código se convierta en “código espagueti”.
¿Se puede simplificar el mantenimiento de aplicaciones reactivas complejas sin comprometer la funcionalidad o el rendimiento?
La complejidad en el código a menudo surge del uso de callbacks. Se recomienda migrar a async/await si se utilizan callbacks explícitos, y adoptar sistemas de tipos cuando sea posible. Si un lenguaje ofrece opciones como corrutinas (por ejemplo, en Go), es preferible utilizarlas. Los lenguajes evolucionan constantemente; por ejemplo, los virtual threads en Java son una característica reciente. Mantenerse actualizado con las mejoras del lenguaje es crucial para facilitar el mantenimiento de aplicaciones y gestionar la deuda técnica. Es importante destinar tiempo en los proyectos para abordar esta deuda y evitar su acumulación.
Mensaje final para quienes estén interesados en el desarrollo de aplicaciones reactivas y tecnológicas avanzadas
Entre 2000 y 2010, tras la burbuja punto-com, Java y .NET dominaron como plataformas generales, aunque PHP y otras herramientas de software libre también ganaron popularidad. Sin embargo, con el avance de la tecnología y el uso de smartphones, este enfoque ha cambiado, y ya no existen plataformas únicas.
Los desarrolladores modernos deben ir más allá de simplemente aprender a programar. Es fundamental dominar patrones de diseño, patrones de programación y herramientas para gestionar sistemas distribuidos y arquitecturas, como la programación serverless, microservicios en Kubernetes e infraestructura como código.
El panorama actual es mucho más complejo que hace una década. Por ello, se recomienda especializarse en una tecnología, pero también adquirir conocimientos sobre arquitectura y mantenerse actualizado con las tendencias y soluciones en la ingeniería de software.