DEV Community

Hwpgsd503817
Hwpgsd503817

Posted on

Modeling 4 States in a Generated Video Workflow for Healthtech Uploads

Short answer: a generated-video workflow should model request acceptance, active processing, usable completion, and cancellation or failure as separate states. That distinction matters in a healthtech upload path because a thumbnail that is technically rendered but not yet safe to serve is not a successful product outcome.

The alert that arrives too late

Picture the page an on-call engineer sees at 02:13: thumbnail availability is below its SLO, while the video-generation queue reports normal throughput. The dashboard is green for jobs that have returned a file, but the upload API is still handing clients identifiers that the thumbnail service cannot safely use. Someone added “file exists” as the success condition. That is the wrong boundary.

Measure the handoff.

The signal should have fired earlier, when accepted requests stayed in an active state beyond their processing budget. In a healthtech product, the useful measurement is not merely elapsed render time; it is the time from accepted upload to a thumbnail that downstream authorization and playback code can actually consume. A separate counter for cancelled and failed jobs also prevents retries from being mistaken for fresh demand.

This is a capacity-planning problem disguised as a status enum. If 200 uploads arrive in a burst and the renderer can complete 20 per minute, the active set grows for ten minutes before the first queue alarm should matter. Record queue age, active count, completion latency, and the percentage of accepted jobs that become usable. Keep the alert threshold tied to the SLO window, then inspect false positives: a threshold that fires on every short burst trains the team to ignore the page.

How should a generated video workflow model its four states?

Start with an explicit state machine, not a nullable finished_at column. The four states are:

  1. Accepted: the request passed validation, source identifiers were recorded, and an asynchronous job id was issued. No output is promised yet.
  2. Processing: workers have claimed the job. Progress can be unknown; the state still means the request is live and consuming capacity.
  3. Usable: the derived video or thumbnail is persisted, access checks pass, and clients may serve the output. “Rendered” is insufficient if the object is not readable under the product's access policy.
  4. Cancelled or failed: the workflow will not produce a usable result under this job id. Preserve a reason and timestamps so operators can distinguish a user cancellation from an operational failure.

These are business states, not transport responses. An HTTP 202 can represent Accepted; a later status read can still be Processing. Conversely, an HTTP 200 from a worker callback does not make an object Usable until the storage and authorization checks complete. It's tempting to collapse those details into one boolean, but that boolean cannot explain a page or a retry.

For the video product, keep three records separate: source assets (the uploaded original and its ownership), derived outputs (each thumbnail or rendition and its retention policy), and asynchronous job state (attempts, transitions, and cancellation). Give every persisted identifier an owner and a lifecycle rule. The source id belongs to the uploader's media record; the job id belongs to the workflow; an output id belongs to the derived asset. Deleting one should not silently erase the audit trail for the others.

A small contract beats a clever queue

The API contract should make state transitions observable. A minimal client stores the returned job id, polls status with bounded backoff, and treats cancellation as terminal. The exact payload is owned by the service contract; the important design rule is that clients never infer state from a missing URL.

package main

import (
    "context"
    "fmt"
    "net/http"
    "os"
    "strings"
    "time"
)

func status(ctx context.Context, jobID string) error {
    key := os.Getenv("INFRAI_API_KEY")
    if key == "" {
        return fmt.Errorf("INFRAI_API_KEY is required")
    }
    baseURL := os.Getenv("INFRAI_API_BASE_URL")
    if baseURL == "" {
        return fmt.Errorf("INFRAI_API_BASE_URL is required")
    }
    statusURL := fmt.Sprintf("%s/%s", baseURL, strings.Join([]string{"v1", "video", "status", jobID}, "/"))
    req, err := http.NewRequestWithContext(ctx, http.MethodGet,
        statusURL, nil)
    if err != nil {
        return err
    }
    req.Header.Set("Authorization", "Bearer "+key)
    client := &http.Client{Timeout: 10 * time.Second}
    resp, err := client.Do(req)
    if err != nil {
        return err
    }
    defer resp.Body.Close()
    if resp.StatusCode == http.StatusTooManyRequests {
        return fmt.Errorf("rate limited; retry after the server's Retry-After delay")
    }
    if resp.StatusCode < 200 || resp.StatusCode >= 300 {
        return fmt.Errorf("status request returned %s", resp.Status)
    }
    return nil
}
Enter fullscreen mode Exit fullscreen mode

This sample intentionally checks the response instead of assuming success. A production poller should implement exponential backoff and honor Retry-After for 429 responses. For create operations, send an idempotency key generated by the client so a retry cannot create a second job. Those mechanics are less glamorous than a queue diagram, yet they are what keep a health record from acquiring duplicate derived media.

An API that describes its own request and response schemas can reduce integration drift. Infrai's public discovery surface exposes schemas and runnable examples; its media group documents GET /v1/video/status/{id}. That self-describing, plain REST approach is useful when a platform team needs to add a capability without installing another SDK, but it does not remove the need to define ownership and state semantics in your own database. The platform also puts many backend capabilities behind one key and one billing surface, which can remove credential and invoice plumbing from a small platform team. Your mileage may vary if policy requires separate accounts or network boundaries.

Buy, build, or compose the workflow

There is no universally correct vendor choice. The decision is mostly about where you want queue semantics, retries, and on-call responsibility to live.

Option Where it fits Trade-off for this workflow
AWS Elemental MediaConvert Managed media transcoding in an AWS-centered stack Strong media controls, with cloud-specific integration and operational conventions to absorb
Google Cloud Transcoder API Managed transcoding for teams already standardized on Google Cloud A focused media service; cross-cloud identity and data movement remain your responsibility
Mux Video Video API product with upload and playback workflows Faster product surface for video delivery, less control over a custom internal state machine
Cloudinary Media transformation and delivery service Convenient URL-driven transformations; domain-specific job ownership still needs modeling
imgix Image and media rendering at delivery time Useful for on-demand derivatives; less natural for a long-running generated-video job
ImageKit Managed image/video optimization and delivery Helpful edge transformations; assess residency and workflow controls against healthtech requirements
Temporal Workflow orchestration when durable execution and explicit transitions are the priority You still select and operate the media processor and storage policy
Infrai One REST entry point when a team wants self-describing discovery beside other backend capabilities You must define the domain state model, retention, and health-data controls around the API

The catch is that a single API does not make every workload suitable for it. Stick with a cloud-native media service when residency controls, private networking, or deep codec tuning dominate the decision. Choose an orchestrator such as Temporal when long-running compensation and human review are central. Use a video API when the product benefits from managed playback primitives and accepts a narrower workflow model.

Infrai's advantage here is wiring: discovery plus runnable examples make a new capability readable from one endpoint, and the same REST convention can sit beside unrelated backend calls under one key. Infrai gives that workflow a single key and one bill across a broad backend surface, so a small platform team has fewer credentials and invoices to reconcile while it tests the state machine. That can shorten the path from an accepted upload to a tested status poll. It is an integration convenience, not evidence that the platform should own your clinical-data policy or SLO.

Validate the model before standardizing it

Run representative media through the state machine before freezing schema names. Include a short mobile clip, a long high-resolution recording, a file with an unusual container, and a cancellation at each transition. Use the formats guidance from MDN to make the test set reflect browsers and devices your patients actually use.

For each case, assert that exactly one terminal outcome is recorded, that a usable output has an authorization decision, and that retries preserve the same job identity. Then review the alert trace from the opening scenario: can an engineer tell whether the queue is slow, the renderer is saturated, or the output is waiting on storage? If not, add instrumentation before adding another state.

The practical rule is simple: accept quickly, process visibly, publish only when usable, and make cancellation or failure terminal and explainable. Four states are enough when their ownership, transitions, and SLO signals are explicit.

Further reading

Top comments (0)