Un marketplace, no un servicio de streaming. La diferencia importa a nivel de arquitectura.
El operador no estaba construyendo un servicio de streaming donde los artistas suben para ganar visibilidad y los ingresos llegan después como parte de la publicidad. Los artistas son dueños de su contenido. Los oyentes pagan por él. La plataforma es un marketplace: fichas de producto, carrito, checkout, historial de pedidos, informes de ventas. El modelo de negocio vive en ese flujo.
Esa distinción reconfigura la arquitectura. El problema más difícil de un servicio de streaming es el rendimiento de ingesta y la latencia de reproducción. El problema más difícil de un marketplace es la superficie de comercio: operaciones de carrito concurrentes, consistencia de pedidos, un checkout que no pierda una compra si un worker en segundo plano va lento. Si te equivocas en eso, pierdes transacciones reales de artistas que ya son escépticos ante una plataforma nueva.
El operador también tenía un plan para un cliente móvil —iOS y Android, construido más adelante— y quería que el nivel de API estuviera listo para él. No adaptado a posteriori. Listo desde el principio, con endpoints versionados basados en DTO y una clara separación entre Admin y Mobile. La app móvil no se publicó en la ventana de 2022. El nivel de API sí.
El encargo: una superficie de comercio completa para un catálogo propiedad de los artistas, un núcleo asíncrono que no bloquee el checkout por el trabajo en segundo plano y un nivel de API que no haya que repensar cuando llegue el cliente móvil.
Dos niveles, un bus de mensajes y tres almacenes con trabajos distintos.
El sistema se dividió en dos aplicaciones Symfony desde el primer día. El monorepo —la administración y la UI web— gestionaba la administración de perfiles de artista, las fichas de producto, las subidas de artwork y el back-office del operador. El nivel de API se construyó por separado, como un servicio REST versionado con DTO con los espacios de nombres de ruta /Mobile/v1/ y /Admin/v1/, 96 endpoints móviles y 130 endpoints de administración, documentado y listo para el cliente móvil cuando llegara.
Los flujos de carrito y pedido corren a través de Symfony Messenger, no a través de escrituras síncronas. AddToCartController despacha un mensaje; el manejador procesa la mutación del carrito de forma asíncrona. Los registros de pedido —Order y ProductOrder, con cantidad y coste total rastreados por línea— siguen el mismo patrón. El checkout no se bloquea por escrituras de telemetría, procesamiento de artwork ni nada más que corra en el bus. La base de datos operativa se mantiene rápida porque el fan-out de escrituras está gestionado.
Tres almacenes, cada uno con una única responsabilidad. PostgreSQL es el estado canónico: 25 entidades, más de 40 índices en las tablas calientes —usuarios, productos, pedidos, ítems de carrito, géneros, etiquetas, relaciones de artista a producto—. Redis ejecuta tanto el broker de la cola de Messenger como la capa de caché para lecturas calientes. InfluxDB guarda la telemetría de acciones de usuario. Los eventos de comportamiento de alta frecuencia nunca tocan la base de datos operativa.
Los perfiles de artista y las descripciones de producto —títulos de álbum, nombres de pistas, etiquetas de género, noticias— son traducibles a nivel de esquema mediante KnpLabs DoctrineBehaviors. Añadir un locale es una migración de datos, no un cambio de código. La UI web usaba Plyr para la reproducción de audio y vídeo; CropperJS gestionaba el recorte y subida de artwork en línea.
- F · 01Carrito y checkout vía Messenger
- Las mutaciones del carrito y el procesamiento de pedidos corren a través de Symfony Messenger, no por escrituras síncronas. El checkout acusa recibo de inmediato; el bus gestiona el fan-out de escrituras.
- F · 02Arquitectura de dos niveles
- Monorepo (administración y UI web) y un servicio de API versionado con DTO aparte con espacios de nombres /Mobile/v1/ y /Admin/v1/ —96 endpoints móviles y 130 endpoints de administración— construido antes de que existiera el cliente móvil.
- F · 03Separación en tres almacenes
- PostgreSQL para el estado canónico (25 entidades, más de 40 índices), Redis para el broker de cola y la caché, InfluxDB para la telemetría de acciones de usuario. Cada almacén tiene un trabajo.
- F · 04Contenido traducible a nivel de esquema
- KnpLabs DoctrineBehaviors en descripciones de producto, perfiles de artista, etiquetas de género y noticias. Añadir un locale es una migración, no un cambio de código.
- F · 05Pipeline de telemetría de InfluxDB
- Los eventos de acciones de usuario se enrutan a través de manejadores de Messenger hacia InfluxDB. El pipeline está diseñado para telemetría completa de reproducción, desplazamiento y salto sin tocar la base de datos operativa.
- F · 06Procesamiento de pedidos asíncrono
- Las entidades Order y ProductOrder rastrean las compras con cantidad y coste por línea. Las mutaciones de pedido fluyen a través del bus de mensajes para que las operaciones de comercio no compitan entre sí.
El carrito pasa por el bus. La API existe antes que la app que la llamará.
La decisión de enrutar las mutaciones del carrito a través de Symfony Messenger en lugar de escrituras síncronas no tenía que ver con la escala. Tenía que ver con la corrección. Las operaciones de carrito en un marketplace implican comprobaciones de inventario, validación del estado del producto y manejo de sesiones concurrentes. Ponerlas en el bus de mensajes desacopla el acuse de recibo de cara al usuario del fan-out de escrituras. El oyente recibe una respuesta; la plataforma se pone al día. La base de datos operativa nunca compite con la superficie de comercio.
El mismo principio se traslada a la reproducción. PlayFileHandler y PlayedFileService desacoplan los eventos de reproducción de la base de datos operativa. La telemetría de acciones de usuario escribe en InfluxDB a través del bus de mensajes: 21 manejadores en total a lo largo del nivel de API. El pipeline de InfluxDB está diseñado para telemetría completa de reproducción, desplazamiento y salto; cablear esos eventos adicionales es el siguiente paso que el operador puede dar sin rearquitecturar nada.
Tres almacenes es la respuesta cuando cada almacén se gana su lugar haciendo el trabajo en el que es mejor, y nada más.
El nivel de API se sobreconstruyó intencionadamente para 2022. /Mobile/v1/ existía con 96 endpoints antes de que se escribiera una sola línea de código de la app móvil. El cliente móvil no se publicó en la ventana de 2022, pero el operador heredó un servicio de API que ya estaba versionado, documentado y separado de la superficie de administración. El coste de hacerlo más tarde —con un producto en vivo, datos de usuario reales y un esquema que ha derivado— no es comparable al coste de hacerlo una sola vez al principio.
Ocho meses. Tres repos. Una superficie de comercio que el operador puede extender.
Ocho meses de desarrollo activo. 494 commits a lo largo de tres repositorios: el monorepo, el nivel de API y un sitio de aterrizaje mínimo en HTML/Gulp. El sistema se publicó con una superficie de comercio completa de artista a oyente: fichas de producto, carrito, checkout, historial de pedidos e informes de ventas. La arquitectura asíncrona —bus de Messenger, broker de Redis, pipeline de telemetría de InfluxDB— estuvo en producción desde la primera versión pública.
El cliente móvil fue la única superficie planificada que no se publicó en la ventana de 2022. El nivel de API construido para recibirlo está intacto. El espacio de nombres /Mobile/v1/, los contratos DTO, el manejo de autenticación: todo ello está esperando al cliente, y no al revés.
La arquitectura que el operador heredó tiene tres fronteras claras: Postgres para el estado, Redis para la cola y la caché, InfluxDB para las series temporales. Cada una puede fallar, actualizarse o escalarse de forma independiente. El siguiente ingeniero no necesita una orientación de seis horas. El problema interesante —la superficie de comercio asíncrona— está resuelto. Todo lo que viene después de esto es trabajo de producto.
Construimos para el cliente móvil antes de que existiera. Cuando se publique, la plataforma ni se enterará.
