El problema arquitectónico
Elevideo recibe videos horizontales, detecta rostros, calcula un reencuadre y genera versiones verticales. Ese flujo mezcla dos tipos de trabajo muy diferentes:
Ejecutar todo dentro del backend Java habría unido el ciclo de vida del producto con una carga de CPU y dependencias multimedia especializadas.
La separación de responsabilidades
React
→ Spring Boot / Spring Modulith
→ registra Video y ProcessingJob
→ solicita procesamiento a FastAPI
→ descarga original desde Cloudinary
→ MediaPipe detecta el sujeto
→ FFmpeg genera renditions
→ guarda resultados en Cloudinary
← webhook con resultado
← progreso y renditionsSpring Boot es la fuente de verdad del estado. FastAPI ejecuta trabajo y devuelve un resultado, pero no decide quién puede ver un proyecto ni qué transición de negocio es válida.
El job como contrato de negocio
Un job no es solo una llamada HTTP larga. Tiene identidad y estados explícitos:
PENDING → PROCESSING → COMPLETED
↘ FAILED
PENDING / PROCESSING → CANCELLEDLa API crea el job antes de delegarlo. Así el frontend puede consultar progreso incluso si el procesamiento tarda, falla o se reinicia.
Cada transición valida el estado anterior. Un callback tardío no debe convertir un job cancelado en completado.
Por qué FastAPI
Python ofrece integración directa con MediaPipe, OpenCV y herramientas multimedia. Separarlo permitió:
El costo es real: dos runtimes, autenticación entre servicios, trazabilidad distribuida y más puntos de fallo.
Cloudinary como frontera de archivos
El backend no transporta el video completo entre servicios en cada paso. Cloudinary conserva originales, resultados y miniaturas; las APIs intercambian identificadores y URLs controladas.
Esto reduce presión sobre memoria y ancho de banda del backend, pero exige limpieza de archivos huérfanos y coordinación cuando una carga externa funciona y la transacción local falla.
Webhook seguro e idempotente
El callback de procesamiento debe tratarse como una entrada no confiable. Valido credenciales del servicio, job esperado y transición permitida.
La idempotencia importa porque un worker puede reintentar. Recibir dos veces el mismo resultado no debe duplicar renditions ni notificaciones.
Una estrategia conceptual es:
@Transactional
void completeJob(UUID jobId, ProcessingResult result) {
ProcessingJob job = repository.lockById(jobId);
if (job.isCompletedWith(result.requestId())) {
return;
}
job.complete(result);
renditionService.replaceFor(job, result.renditions());
}Polling hoy, eventos después
El frontend consulta el estado del job porque es simple, visible y suficiente para el volumen actual. No afirmo que sea la solución final para cualquier escala.
Una cola empezaría a aportar valor cuando aparezcan:
En ese punto, Spring Boot publicaría una solicitud y los workers consumirían de forma desacoplada. El contrato del job podría mantenerse, evitando cambiar el frontend.
Observabilidad necesaria
Para diagnosticar un flujo distribuido, cada solicitud necesita correlación. Registro al menos:
jobId y videoId.Sin esa información, un error entre Java, Python y Cloudinary se convierte en una investigación manual.
Qué aprendí
La decisión importante no fue usar dos lenguajes. Fue asignar propiedad clara:
Cuando cada componente tiene una responsabilidad defendible, la distribución añade capacidad. Cuando se separa solo por moda, añade coordinación.
Conclusión
Elevideo utiliza HTTP y webhooks porque resuelven el volumen actual con menor complejidad operativa. La arquitectura deja preparado el límite para introducir una cola si aparecen picos, reintentos o workers horizontales, pero no paga ese costo antes de necesitarlo.
