La creciente complejidad de los sistemas distribuidos y la adopción de modelos de programación asíncronos y reactivos hacen que la comprensión del flujo de ejecución sea un desafío fundamental. Este artículo aborda el problema de la observabilidad y la depuración en sistemas de ejecución duradera donde el flujo de control no es estático ni declarativo, sino que emerge de la ejecución de código arbitrario. Al transformar el código fuente en una representación gráfica estática, se busca mejorar la capacidad de los ingenieros para razonar sobre el comportamiento de sistemas distribuidos, un problema que se remonta a los primeros días de la programación concurrente y los diagramas de flujo.

La necesidad de visualizar el comportamiento de los programas no es nueva; los diagramas de flujo y los grafos de llamadas han sido herramientas estándar. Sin embargo, en un entorno de ejecución duradera y distribuida, donde las operaciones pueden pausarse, reanudarse, fallar y reintentarse, y donde el paralelismo implícito es común, la simple inspección del código no es suficiente. La tesis central es que, incluso con código dinámico, es posible y necesario derivar una representación estática y visual que capture las relaciones de dependencia y paralelismo, ofreciendo una 'radiografía' del sistema que facilita la depuración y el entendimiento arquitectónico.

Arquitectura del Sistema

La arquitectura para generar los diagramas de Cloudflare Workflows se centra en el análisis estático del código fuente. Cuando un Worker se despliega, el servicio de configuración interno lo empaqueta (usando esbuild o rspack) y lo minifica. En este punto, el script minificado es capturado y enviado a un servicio interno que utiliza oxc-parser, una biblioteca de Rust compilada a WebAssembly y ejecutada en un Cloudflare Worker. Este Worker de Rust es el componente clave para el análisis léxico y sintáctico.

El proceso comienza con la conversión del código JavaScript minificado en un Abstract Syntax Tree (AST). Este AST es una representación jerárquica del código fuente que permite al sistema entender la estructura del programa sin ejecutarlo. A partir del AST, el servicio genera un grafo intermedio que identifica los 'WorkflowEntrypoints' y las llamadas a los pasos del workflow (step.do, step.waitForEvent, etc.). El desafío principal es inferir las relaciones de control de flujo, como Promise.all, await, bucles (for...of, while, map, forEach), bifurcaciones (if/else, switch/case, ternario) y manejo de errores (try/catch/finally), que definen el paralelismo y la secuencia de ejecución.

Para manejar el paralelismo dinámico, el sistema rastrea las relaciones Promise y await. Cada nodo en el grafo resultante tiene campos starts y resolves que indican cuándo una promesa comienza y termina su ejecución en relación con otras. Esto permite al sistema determinar qué pasos se ejecutan en paralelo y cuándo se completan, lo que se traduce en el posicionamiento vertical en el diagrama. Se definen tipos de nodos específicos (StepSleep, StepDo, LoopNode, ParallelNode, TryNode, IfNode, etc.) para representar las construcciones del workflow. Finalmente, este grafo se traduce a una representación visual que se renderiza en el dashboard de Cloudflare.

Flujo de Despliegue y Generación de Diagramas de Workflow

  1. 1 Despliegue de Worker El desarrollador despliega un Cloudflare Worker con código de Workflow.
  2. 2 Bundling y Minificación El servicio de configuración interno empaqueta (esbuild/rspack) y minifica el...
  3. 3 Envío a Cola El ID del script minificado se envía a una Cloudflare Queue.
  4. 4 Procesamiento por Worker Rust Un Cloudflare Worker (Rust/WebAssembly) consume el mensaje de la cola.
  5. 5 Generación de AST oxc-parser convierte el JS minificado en un Abstract Syntax Tree (AST).
  6. 6 Generación de Grafo Intermedio El servicio interno genera un grafo con WorkflowEntrypoints y llamadas a pasos.
  7. 7 Inferencia de Flujo de Control Se rastrean relaciones Promise/await para determinar paralelismo y secuencia ...
  8. 8 Renderizado de Diagrama El grafo final se traduce a una representación visual y se muestra en el dash...
CapaTecnologíaJustificación
compute Cloudflare Workers Plataforma de ejecución serverless para los workflows de usuario y para el servicio de análisis de código (Rust Worker).
data-processing oxc-parser (JavaScript Oxidation Compiler) Biblioteca de Rust para parsear JavaScript minificado y generar Abstract Syntax Trees (ASTs).
orchestration Cloudflare Workflows Engine (Durable Object) Motor de ejecución duradera que supervisa la lógica de ejecución del workflow y persiste el estado.
messaging Cloudflare Queues Mecanismo para enviar IDs de scripts a procesar por el Worker de Rust, desacoplando la generación del diagrama del despliegue.
data-processing esbuild / rspack / vite Bundlers utilizados para empaquetar y minificar el código JavaScript/TypeScript de los Workers.
export class ImplicitParallelWorkflow extends WorkflowEntrypoint<Env, Params> {
  async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
    const branchA = async () => {
      const a = step.do("task a", async () => "a"); //starts 1
      const b = step.do("task b", async () => "b"); //starts 1
      const c = await step.waitForEvent("task c", { type: "my-event", timeout: "1 hour" }); //starts 1 resolves 2
      await step.do("task d", async () => JSON.stringify(c)); //starts 2 resolves 3
      return Promise.all([a, b]); //resolves 3
    };
    const branchB = async () => {
      const e = step.do("task e", async () => "e"); //starts 1
      const f = step.do("task f", async () => "f"); //starts 1
      return Promise.all([e, f]); //resolves 2
    };
    await Promise.all([branchA(), branchB()]);
    await step.sleep("final sleep", 1000);
  }
}
Ejemplo de cómo se definen tareas paralelas utilizando Promise.all y step.do dentro de un Cloudflare Workflow, y cómo el sistema infiere su ejecución concurrente.

Fundamentos Teóricos

El concepto de analizar estáticamente el código para comprender su comportamiento tiene profundas raíces en la informática teórica y la ingeniería de software. La generación de Abstract Syntax Trees (ASTs) es un pilar fundamental en el diseño de compiladores, como se describe en obras clásicas como 'Compilers: Principles, Techniques, and Tools' de Aho, Sethi y Ullman (conocido como el 'Dragon Book'). Los ASTs permiten una representación estructurada del código que es independiente de la sintaxis superficial y facilita el análisis semántico.

La inferencia del flujo de control y las dependencias en programas asíncronos y concurrentes se relaciona con el campo del análisis de programas y la verificación formal. Trabajos como los de Dijkstra sobre la concurrencia y la sincronización, o los estudios sobre grafos de dependencia de datos y control, son precursores de la lógica aplicada aquí. Aunque el artículo no cita directamente un paper específico, la problemática de visualizar y depurar sistemas distribuidos y concurrentes ha sido objeto de investigación desde los años 70 y 80, buscando herramientas que ayuden a los desarrolladores a manejar la complejidad inherente a estos sistemas. La capacidad de derivar un grafo de ejecución a partir de un AST para un lenguaje dinámico como JavaScript, especialmente en su forma minificada, es una aplicación moderna de estos principios fundamentales.