A lo largo del módulo hemos tratado los eventos como unidades discretas: un pedido se crea, un pago se confirma. Pero hay una clase de problemas en los que los eventos llegan de forma continua y a gran velocidad —clics de usuarios, lecturas de sensores IoT, transacciones de tarjeta, líneas de log— y el valor está en procesarlos según fluyen, sin esperar a acumularlos. Esto es el streaming de datos en tiempo real: tratar los datos como un flujo infinito (unbounded) sobre el que aplicamos transformaciones, agregaciones y detección de patrones en cuestión de milisegundos.
El streaming es la base de casos de uso como la detección de fraude al instante, los cuadros de mando en vivo, las recomendaciones en tiempo real o la monitorización de sistemas. En esta lección entenderás la diferencia fundamental entre procesamiento por lotes (batch) y por flujos (streaming), el papel de las ventanas temporales, y cómo Kafka Streams nos permite implementar todo esto con código relativamente sencillo.
Contenido
- Batch vs Streaming: la diferencia fundamental
- Conceptos del procesamiento de flujos
- Ventanas (windows): agrupar el tiempo
- Kafka Streams: procesamiento sobre Kafka
- Ejemplo: detección de fraude por ventana
- Casos de uso reales
- Errores comunes y consejos
- Ejercicios y soluciones
- Conclusión
- Batch vs Streaming: la diferencia fundamental
El procesamiento por lotes (batch) opera sobre un conjunto de datos finito y completo (por ejemplo, "todas las ventas de ayer") en un momento programado. El procesamiento por flujos (streaming) opera sobre un flujo infinito de eventos a medida que llegan.
| Aspecto | Batch | Streaming |
|---|---|---|
| Datos | Finitos, acotados | Infinitos, continuos |
| Latencia | Alta (minutos/horas) | Baja (ms/segundos) |
| Cuándo procesa | En un momento programado | Según llegan los eventos |
| Visión de los datos | Completa | Parcial (lo visto hasta ahora) |
| Ejemplo | Nómina mensual, informe diario | Detección de fraude, alertas en vivo |
| Herramientas típicas | Spark batch, ETL nocturno | Kafka Streams, Flink, Spark Streaming |
La frase clave: en batch tienes todos los datos antes de empezar; en streaming nunca los tienes todos, porque siempre puede llegar uno más. Esto obliga a razonar de otra forma, especialmente con el tiempo.
- Conceptos del procesamiento de flujos
Antes de programar, fijemos el vocabulario:
- Stream: secuencia infinita de eventos ordenados (aproximadamente) en el tiempo.
- Transformaciones sin estado (stateless): procesan cada evento de forma independiente. Ejemplos:
filter(descartar),map(transformar). - Transformaciones con estado (stateful): necesitan recordar información de eventos anteriores. Ejemplos: contar, sumar, agregar por clave. Requieren un almacén de estado (state store).
- Tiempo del evento (event time) vs tiempo de procesamiento (processing time):
- Event time: cuándo ocurrió el evento (marca en el propio mensaje).
- Processing time: cuándo lo procesa el sistema.
- Pueden diferir mucho (un evento de un móvil sin cobertura puede llegar minutos tarde). El procesamiento serio se basa en event time.
- Datos tardíos (late data): eventos que llegan después de lo esperado. El sistema debe decidir si los incorpora o los descarta.
- Ventanas (windows): agrupar el tiempo
Como el flujo es infinito, no podemos "sumar todo". En su lugar, agrupamos los eventos en ventanas temporales y calculamos sobre cada ventana. Hay tres tipos principales:
flowchart TB
subgraph Tumbling["Tumbling (fijas, sin solape)"]
direction LR
T1["[0-5min]"] --- T2["[5-10min]"] --- T3["[10-15min]"]
end
subgraph Hopping["Hopping/Sliding (solapadas)"]
direction LR
H1["[0-5min]"] --- H2["[2-7min]"] --- H3["[4-9min]"]
end
subgraph Session["Session (por actividad)"]
direction LR
S1["actividad...gap...nueva sesión"]
end| Tipo de ventana | Descripción | Caso de uso |
|---|---|---|
| Tumbling (fija) | Intervalos fijos sin solape. Cada evento cae en una sola ventana | "Ventas cada 5 minutos" |
| Hopping / Sliding (deslizante) | Intervalos fijos que se solapan (avanzan por saltos) | "Media móvil de los últimos 5 min, actualizada cada minuto" |
| Session (de sesión) | Se cierra tras un periodo de inactividad (gap) | "Actividad de un usuario hasta que deja de interactuar" |
Ejemplo: para detectar fraude buscamos "más de 3 pagos en 1 minuto desde la misma tarjeta". Eso es una agregación con estado sobre una ventana de 1 minuto agrupada por tarjeta.
- Kafka Streams: procesamiento sobre Kafka
Kafka Streams es una librería Java que consume de topics de Kafka, procesa los datos (filtrado, agregación, ventanas, joins) y escribe los resultados en otros topics. No necesita un clúster aparte: es código que despliegas como una aplicación normal y que escala añadiendo instancias.
Conceptos de su API:
- KStream: representa un flujo de eventos (cada registro es un hecho independiente).
- KTable: representa una tabla de estado (cada clave tiene su último valor; los nuevos registros actualizan).
- State store: almacén local (respaldado en Kafka) donde se guardan las agregaciones con estado, tolerante a fallos.
# Configuración mínima de una app Kafka Streams application.id: detector-fraude # identifica la app y sus state stores bootstrap.servers: localhost:9092 # clúster de Kafka default.key.serde: ...StringSerde # cómo serializar las claves processing.guarantee: exactly_once_v2 # garantía de procesamiento
application.ides crítico: identifica la aplicación y nombra sus topics internos y state stores. Dos instancias con el mismoapplication.idforman un grupo y se reparten el trabajo.processing.guarantee: exactly_once_v2activa el procesamiento exactly-once dentro de Kafka (entre topics), apoyándose en transacciones.
- Ejemplo: detección de fraude por ventana
Detectemos tarjetas con más de 3 pagos en una ventana de 1 minuto:
StreamsBuilder builder = new StreamsBuilder();
// 1. Leemos el flujo de pagos del topic de entrada
KStream<String, Pago> pagos = builder.stream("pagos");
pagos
// 2. Reagrupamos por tarjeta (la clave pasa a ser la tarjeta)
.groupBy((clave, pago) -> pago.tarjetaId(),
Grouped.with(Serdes.String(), pagoSerde))
// 3. Definimos una ventana TUMBLING de 1 minuto
.windowedBy(TimeWindows.ofSizeWithNoGrace(Duration.ofMinutes(1)))
// 4. Contamos los pagos por tarjeta dentro de cada ventana
.count(Materialized.as("conteo-pagos-por-tarjeta"))
// 5. Convertimos la tabla de resultados de nuevo en flujo
.toStream()
// 6. Nos quedamos solo con las que superan el umbral
.filter((tarjetaVentana, conteo) -> conteo > 3)
// 7. Emitimos una alerta al topic de salida
.map((tarjetaVentana, conteo) ->
KeyValue.pair(tarjetaVentana.key(),
new AlertaFraude(tarjetaVentana.key(), conteo)))
.to("alertas-fraude", Produced.with(Serdes.String(), alertaSerde));
KafkaStreams streams = new KafkaStreams(builder.build(), config);
streams.start();Explicación paso a paso:
builder.stream("pagos")crea unKStreamque lee cada pago del topic de entrada.groupByreagrupa portarjetaId: ahora los eventos de la misma tarjeta comparten clave y se procesan juntos. Es la base de cualquier agregación con estado.windowedBy(TimeWindows.ofSize... 1 minuto)define ventanas tumbling de un minuto.withNoGracesignifica que no esperamos datos tardíos.count(...)mantiene, en un state store tolerante a fallos, cuántos pagos lleva cada tarjeta en la ventana actual.toStream()convierte laKTablede conteos en un flujo de cambios para poder seguir procesando.filter(... conteo > 3)deja pasar solo las combinaciones tarjeta-ventana que superan el umbral de fraude.mapconstruye unaAlertaFraudeyto("alertas-fraude")la publica en el topic de salida, donde otro servicio podrá bloquear la tarjeta o avisar al cliente.
El resultado es un detector de fraude que reacciona en tiempo real, sin lotes nocturnos.
- Casos de uso reales
- Detección de fraude: patrones anómalos de transacciones al instante (como el ejemplo).
- Monitorización y alertas: detectar caídas de servicio o picos de errores en los logs en vivo.
- Cuadros de mando en tiempo real: ventas por minuto, usuarios activos ahora mismo.
- Recomendaciones: ajustar sugerencias según la navegación actual del usuario.
- IoT y telemetría: agregar lecturas de miles de sensores y detectar umbrales (temperatura, vibración).
- Enriquecimiento de eventos: unir (join) un flujo de pedidos con una tabla de clientes para añadir datos al vuelo.
Errores Comunes y Consejos
- Confundir event time con processing time. Si agregas por processing time, un evento retrasado cae en la ventana equivocada y los resultados son incorrectos. Basa las ventanas en el tiempo del evento siempre que el orden importe.
- Ignorar los datos tardíos. Decide explícitamente tu política: periodo de gracia, descarte o reproceso. No decidir es decidir mal.
- Olvidar que el estado crece. Las agregaciones con estado consumen memoria/disco. Usa ventanas con retención acotada y limpia el estado obsoleto.
- Esperar "exactly-once" de extremo a extremo. Kafka Streams lo garantiza entre topics de Kafka, pero al escribir en sistemas externos vuelves a necesitar idempotencia (lección 05-02).
- Usar streaming cuando bastaba un batch. Si la latencia de horas es aceptable, un batch es más simple y barato. El streaming añade complejidad operativa.
- Consejo: empieza con transformaciones sin estado (
filter,map); introduce estado y ventanas solo cuando el caso de uso lo exija.
Ejercicios
- Para cada caso, indica si usarías batch o streaming y por qué: (a) informe contable de cierre mensual; (b) alerta cuando un sensor supera 80 °C; (c) cálculo nocturno de comisiones; (d) contador de usuarios conectados en directo.
- Quieres calcular una "media móvil de temperatura de los últimos 10 minutos, actualizada cada 2 minutos". ¿Qué tipo de ventana usarías y por qué?
- Explica con tus palabras la diferencia entre
KStreamyKTabley pon un ejemplo de dato adecuado para cada uno.
Soluciones
- (a) Batch: datos finitos del mes, sin urgencia. (b) Streaming: requiere reacción inmediata. (c) Batch: proceso programado nocturno sobre datos completos. (d) Streaming: métrica en vivo que cambia continuamente.
- Una ventana hopping/sliding (deslizante) de tamaño 10 minutos con un salto (hop) de 2 minutos. Es deslizante porque las ventanas se solapan: cada 2 minutos se emite un nuevo resultado que cubre los últimos 10 minutos, que es justo lo que pide una media móvil.
- Un
KStreammodela un flujo de hechos independientes (cada registro es un evento que se suma a los anteriores), por ejemplo cada pago o cada clic. UnKTablemodela un estado por clave donde cada nuevo registro actualiza el valor anterior de esa clave, por ejemplo el saldo actual de cada cuenta o el último precio de cada producto.
Conclusión
El streaming de datos trata los eventos como un flujo infinito y los procesa según llegan, logrando latencias de milisegundos frente a las horas del batch. Aprendimos a razonar sobre el tiempo (event time vs processing time), a agrupar eventos en ventanas (tumbling, deslizantes y de sesión) y a implementar agregaciones con estado en Kafka Streams mediante un detector de fraude real. También vimos cuándo conviene streaming y cuándo basta un batch más sencillo.
Con esta lección cerramos el Módulo 5: Arquitecturas Dirigidas por Eventos y Mensajería. Has recorrido el camino completo: desde los fundamentos de los eventos y su mensajería asíncrona, pasando por el almacenamiento con Event Sourcing y CQRS, la coordinación de transacciones distribuidas con el patrón Saga, hasta el procesamiento de flujos en tiempo real. Estos patrones constituyen la columna vertebral de los sistemas distribuidos modernos y te preparan para diseñar arquitecturas escalables, resilientes y reactivas.
Curso de Arquitectura de Aplicaciones
Módulo 1: Fundamentos de la Arquitectura de Aplicaciones
- ¿Qué es la Arquitectura de Aplicaciones?
- El Rol del Arquitecto de Software
- Atributos de Calidad y Requisitos No Funcionales
- Decisiones Arquitectónicas y Compromisos (Trade-offs)
- Documentación de Arquitectura: Vistas y el Modelo C4
Módulo 2: Principios y Tácticas de Diseño
- Acoplamiento, Cohesión y Separación de Responsabilidades
- Principios SOLID Aplicados a la Arquitectura
- DRY, KISS, YAGNI y Otros Principios de Diseño
- Tácticas Arquitectónicas para los Atributos de Calidad
- Gestión de la Deuda Técnica
Módulo 3: Estilos y Patrones Arquitectónicos
- Arquitectura Monolítica
- Arquitectura en Capas (N-Tier)
- Arquitectura Cliente-Servidor
- Arquitectura Hexagonal (Puertos y Adaptadores)
- Arquitectura Limpia y Cebolla (Clean & Onion)
Módulo 4: Arquitecturas Distribuidas y Microservicios
- Introducción a los Sistemas Distribuidos
- Arquitectura de Microservicios
- Descomposición de Servicios y Bounded Contexts
- API Gateway, Service Discovery y Comunicación entre Servicios
- Patrones de Resiliencia: Circuit Breaker, Retry y Bulkhead
- El Teorema CAP y la Consistencia de Datos
Módulo 5: Arquitecturas Dirigidas por Eventos y Mensajería
- Fundamentos de la Arquitectura Orientada a Eventos
- Mensajería Asíncrona: Colas y Brokers
- Patrones de Eventos: Event Sourcing y CQRS
- Gestión de Transacciones Distribuidas: Patrón Saga
- Streaming de Datos en Tiempo Real
Módulo 6: Diseño Dirigido por el Dominio (DDD)
- Conceptos Fundamentales del DDD
- Diseño Estratégico: Bounded Contexts y Lenguaje Ubicuo
- Diseño Táctico: Entidades, Agregados y Repositorios
- Mapeo de Contextos (Context Mapping)
Módulo 7: Datos y Persistencia
- Estrategias de Persistencia: SQL vs NoSQL
- Patrones de Acceso a Datos: Repository, Unit of Work y DAO
- Base de Datos por Servicio y Gestión de Datos Distribuidos
- Caché y Estrategias de Invalidación
Módulo 8: Arquitectura en la Nube y Despliegue
- Fundamentos del Cloud Computing (IaaS, PaaS, SaaS)
- Contenedores y Orquestación con Docker y Kubernetes
- Arquitectura Serverless
- Patrones de Diseño Cloud-Native
- Infraestructura como Código (IaC)
Módulo 9: Calidad, Seguridad y Observabilidad
- Escalabilidad: Horizontal vs Vertical y Balanceo de Carga
- Alta Disponibilidad y Tolerancia a Fallos
- Seguridad por Diseño y Autenticación/Autorización
- Observabilidad: Logging, Métricas y Trazabilidad
- Rendimiento y Pruebas de Carga
