Spring BootFastAPISpring ModulithWebhooksFFmpeg

Elevideo: cómo separé Spring Boot del procesamiento de video con FastAPI

Caso técnico sobre el contrato de jobs, la separación Java/Python, Cloudinary, webhooks idempotentes y el punto exacto en que una cola empezaría a aportar valor.

Edgar Camberos
Edgar Camberos
25 jul 202613 min de lectura

Código y evidencia relacionada

Enlaces directos a implementaciones, repositorios o recursos desplegados.

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:

  • Operaciones transaccionales: usuarios, permisos, proyectos, videos y estados.
  • Procesamiento intensivo: lectura de frames, MediaPipe, OpenCV y FFmpeg.
  • 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

    textedgar.dev
    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 renditions

    Spring 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:

    textedgar.dev
    PENDING → PROCESSING → COMPLETED
                       ↘ FAILED
    PENDING / PROCESSING → CANCELLED

    La 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ó:

  • Mantener Spring Boot enfocado en dominio y seguridad.
  • Desplegar dependencias de video sin aumentar la imagen Java.
  • Escalar procesamiento de forma independiente.
  • Experimentar con algoritmos sin alterar el contrato público.
  • 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:

    javaedgar.dev
    @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:

  • Picos que superen la capacidad inmediata del worker.
  • Necesidad de reintentos con backoff.
  • Priorización de trabajos.
  • Varios workers compitiendo de forma coordinada.
  • Backpressure y métricas de consumo.
  • 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.
  • Estado anterior y nuevo.
  • Duración de procesamiento.
  • Identificador de callback o intento.
  • Error técnico separado del mensaje de negocio.
  • 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:

  • Java posee el dominio y el estado.
  • Python posee el algoritmo multimedia.
  • Cloudinary posee los binarios.
  • El contrato de job conecta las partes.
  • 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.

    Edgar Camberos

    Java Backend Developer · Spring Boot