Arquitectura Event-driven en Kotlin: por qué no es solo una moda pasajera
\nImagina que tu aplicación es un mecanismo perfectamente ajustado. Cada engranaje sabe cuándo girar, cada pistón cuándo moverse. Pero, ¿qué pasa si un día necesitas agregar una pieza nueva sin detener toda la máquina? En la arquitectura síncrona tradicional, esto a menudo se convierte en una pesadilla: reescribir módulos enteros, probar cadenas interminables de llamadas y noches sin dormir.
\nAquí entra en escena la arquitectura event-driven en Kotlin — un paradigma donde los componentes se comunican a través de eventos, no mediante llamadas directas. Kotlin, con sus corrutinas y streams reactivos, ofrece herramientas simplemente ideales para esto. En este artículo no solo hablaremos de teoría — escribiremos código real, analizaremos Kotlin Flow y Channels, y te mostraré cómo construir un sistema flexible y escalable que no se desmorone ante la primera carga.
\n\nQué es la arquitectura orientada a eventos en palabras sencillas
\nEn el enfoque clásico (imagina microservicios que se llaman entre sí por REST), cada solicitud es un vínculo rígido. El servicio A llama al servicio B, espera una respuesta, y si B falla, todo se rompe. En la arquitectura event-driven es diferente: los componentes publican eventos (por ejemplo, "Pedido creado") y se suscriben a ellos. Nadie espera a nadie, nadie llama a nadie directamente.
\nLa belleza de Kotlin es que permite implementar este modelo de forma nativa, sin necesidad de frameworks pesados. Las corrutinas proporcionan hilos ligeros, y Channel y Flow son buses de eventos listos para usar.
\nVeamos un ejemplo muy simple: nuestro propio EventBus en Kotlin:
\n\nimport kotlinx.coroutines.*\nimport kotlinx.coroutines.channels.*\n\nclass SimpleEventBus {\n private val events = Channel<Any>(Channel.UNLIMITED)\n\n suspend fun publish(event: Any) {\n events.send(event)\n }\n\n suspend fun subscribe(handler: suspend (Any) -> Unit) {\n for (event in events) {\n handler(event)\n }\n }\n}\n\n// Uso\nfun main() = runBlocking {\n val bus = SimpleEventBus()\n \n launch {\n bus.subscribe { event ->\n println("Evento recibido: $event")\n }\n }\n \n delay(100)\n bus.publish("¡Hola, Event-Driven!")\n delay(100)\n}\n\nEste código es la base. Creamos un canal que funciona como una cola asíncrona. El publicador coloca un evento, el suscriptor lo recoge. Todo es simple, pero aquí ya se ve la fuerza de la arquitectura event-driven en Kotlin: el suscriptor y el publicador no saben nada el uno del otro.
\n\nKotlin Flow: streams reactivos para eventos
\nChannel está bien, pero para proyectos reales a menudo se necesita más: streams fríos y calientes, transformaciones, manejo de errores. Aquí entra en juego Kotlin Flow — una biblioteca reactiva integrada directamente en el lenguaje.
\nFlow es ideal para la programación asíncrona en Kotlin, cuando los eventos se generan constantemente. Por ejemplo, actualizaciones del tipo de cambio, feeds de noticias o estados de la interfaz de usuario.
\n\nStreams fríos vs calientes
\nEs importante entender la diferencia:
\n- \n
- Stream frío (Cold Stream) — solo comienza a funcionar cuando alguien se suscribe. Cada suscriptor recibe su propio flujo de datos. \n
- Stream caliente (Hot Stream) — funciona constantemente, independientemente de los suscriptores. Los suscriptores tardíos se pierden los eventos antiguos. \n
Para la arquitectura event-driven generalmente se necesitan streams calientes. En Kotlin se implementan mediante SharedFlow o StateFlow.
Ejemplo — un sistema de notificaciones:
\n\nimport kotlinx.coroutines.*\nimport kotlinx.coroutines.flow.*\n\ndata class Notification(val userId: String, val message: String)\n\nclass NotificationService {\n private val _notifications = MutableSharedF