Ha pasado un tiempo desde la última entrega sobre KPIAds.app y te voy a ser sincero: los proyectos con mis clientes me han absorbido el 200% del tiempo y he tenido que priorizar.
Es por ello que hace también ya algún tiempo que las entregas de la Newsletter están siendo de una a la semana, en lugar de las 2 que teníamos con el desarrollo del SaaS.
Pero, como te dije al principio, esto es sin prisa, pero sin pausa. El desarrollo de mi SaaS sigue adelante y hoy retomamos donde lo dejamos.
Hay un punto en el desarrollo de cualquier producto donde el código ya no cabe en la cabeza.
Al principio todo es sencillo: un par de controladores, algunos modelos, unas cuantas vistas… tienes la sensación de que controlas todos los hilos.
Pero, a medida que el producto crece, la realidad te explota en la cara:
-
Ingestas de datos que tardan varios minutos.
-
Peticiones a APIs externas que fallan o van lentas.
-
Cálculos de métricas que devoran la CPU.
-
Lógicas de negocio que dependen del estado de otros módulos (suscripciones, facturas…).
Si intentas meter todo eso en controladores, el sistema se convierte en lo de siempre: un monolito que funciona… pero que es un infierno mantener.
En esta fase de KPIAds, el objetivo es otro: que el monolito se hable bien por dentro. Que cada parte se entere de lo que pasa sin acoplarse a las demás y que lo pesado se mueva a un segundo plano.
Para lograrlo, vamos a usar cuatro piezas de artillería: Domain Events, Listeners, Jobs y Colas con Redis + Horizon.
Domain Events: contar lo que ha pasado
La idea de un Domain Event es sencilla: representa algo que ya ha ocurrido en el negocio. No es una orden directa (haz esto), es un hecho (esto ha pasado).
En KPIAds, he definido eventos que cuentan la historia del sistema: UserRegistered, ProjectCreated, IngestionRunStarted. En Laravel, esto es una clase limpia que lanza un mensaje al aire:
“Oye, se ha iniciado una ingesta. Quien necesite reaccionar, que reaccione”.
Separar la lógica: Listeners y Jobs
Aquí es donde separamos el grano de la paja. El Listener es el estratega y el Job es el obrero.
-
El Listener: responde al evento. Su única responsabilidad es decidir qué hay que hacer. Si empieza una ingesta, el Listener decide qué fuentes preparar y manda a alguien a trabajar.
-
El Job: es la unidad de trabajo pesada. Se ejecuta en background y no bloquea al usuario. En KPIAds, los Jobs se encargan de lo sucio: llamar a APIs de Ads o recalcular métricas.
El motor: Redis + Horizon
Si intentas hacer todo esto dentro de la petición del usuario, la web irá lenta y se romperá. Por eso usamos Redis como base de datos en memoria para gestionar las colas y Laravel Horizon para vigilarlo todo.
El flujo real en KPIAds cuando lanzas una ingesta es este:
-
Action: crea el registro en la DB y dispara el evento. El usuario recibe un “OK” al instante.
-
Listener: escucha el evento y despacha el ExecuteIngestionJob a la cola.
-
Worker (Background): el Job se despierta, llama a las APIs, guarda los datos y, al terminar, dispara un nuevo evento IngestionCompleted.
Resultado: el usuario nunca vio una pantalla de carga infinita.
Anatomía de un flujo asíncrono: Del evento al trabajo real
Para un Senior, el código no son solo instrucciones, son contratos entre capas. En KPIAds, hemos estructurado esta comunicación para que sea totalmente ciega: el que lanza el evento no sabe quién lo escucha, y el que hace el trabajo no sabe quién lo pidió.
Así se traduce esto al editor:
1. El Evento: es un simple objeto de transporte de datos (DTO). No tiene lógica, solo contiene la información mínima necesaria para que los demás entiendan el contexto.
2. El Listener: su trabajo es puramente orquestador. Aquí es donde desacoplamos módulos. Si mañana queremos que al empezar una ingesta también se envíe un mensaje a Slack, solo tenemos que añadir otro Listener, sin tocar una sola línea del código original.
3. El Job: aquí es donde vive la fuerza bruta. Este código se ejecuta en un proceso aparte gracias a Redis. Si la API de Google Ads tarda 30 segundos en responder, no importa: el usuario ya está navegando por otra parte de la App.
La diferencia entre un programador Junior y uno Senior no está en cuántas líneas escribe, sino en cómo se hablan las piezas de su sistema.
-
Un monolito bien diseñado es un conjunto de contextos que colaboran.
-
Los Domain Events cuentan la historia de tu negocio.
-
Las Colas hacen que el sistema sea rápido para el usuario sin dejar de ser potente por dentro.
Diseñar así es lo que te asegura que tu software esté preparado para escalar y, sobre todo, para que cuando vuelvas al código dentro de tres meses (después de estar absorbido por otros clientes), entiendas perfectamente qué está pasando.
¿Te ha quedado clara esta separación de conceptos? ¿Quieres que la semana que viene veamos cómo monitorizamos con Horizon que todo esto no explote en producción?
Nos vemos en la próxima entrega.
Julián
Publicado originalmente en CERO a SENIOR.



