Suscríbete para recibir notificaciones de nuevas publicaciones:

Durable Objects en Dynamic Workers: Dale a cada aplicación generada por IA su propia base de datos

2026-04-13

4 min de lectura
Esta publicación también está disponible en English, 繁體中文, Français, Deutsch, 日本語, 한국어, Español y 简体中文.

Hace unas semanas, anunciamos Dynamic Workers, una nueva función de la plataforma Workers que permite cargar código de Worker de manera dinámica en un entorno seguro de sandbox. La API de Dynamic Worker Loader, en esencia, brinda acceso directo a la primitiva básica de aislamiento de cómputo sobre la que Workers se ha basado todo el tiempo: aislamientos, no contenedores. Los aislamientos son mucho más ligeros que los contenedores y, por lo tanto, pueden cargarse 100 veces más rápido utilizando solo una décima parte de la memoria. Son tan eficientes que pueden tratarse como "desechables": se inician para ejecutar unas pocas líneas de código y luego se descartan. Como una versión segura de eval().

Los Dynamic Workers tienen muchos usos. En el anuncio original destacamos cómo aprovecharlos para ejecutar código generado por agentes de IA, planteado como una alternativa al uso de llamadas a herramientas. En este caso de uso, un agente de IA lleva a cabo acciones solicitadas por el usuario escribiendo unas pocas líneas de código y ejecutándolas.El código es de un solo uso, está diseñado para hacer una sola tarea y se desecha inmediatamente después de ejecutarse.

Pero ¿qué pasa si quieres que una IA genere código más persistente?¿Y si lo que quieres es que tu IA cree una pequeña aplicación con una interfaz personalizada con la que el usuario pueda interactuar?¿Y si lo que quieres es que esa aplicación tenga un estado persistente de larga duración?Pero, por supuesto, todavía quieres que se ejecute dentro de un sandbox seguro.

Una manera de hacerlo sería utilizar Dynamic Workers, y simplemente proporcionar al Worker una API de RPC que le dé acceso al almacenamiento. Usando enlaces, podrías darle a Dynamic Worker una API que apunte a tu base de datos SQL remota (quizás respaldada por Cloudflare D1, o una base de datos de Postgres a la que accedes mediante Hyperdrive — tú decides).

Pero Workers también tiene un tipo de almacenamiento único y sumamente rápido que puede ser ideal para este caso: Durable Objects. Un Durable Object es un tipo especial de Worker que tiene un nombre único, con una instancia global por nombre. Esa instancia tiene una base de datos SQLite adjunta, que se encuentra en el disco local de la máquina donde se ejecuta Durable Object. Esto hace que el acceso al almacenamiento sea increíblemente rápido: en la práctica, hay latencia cero.

Quizás, entonces, lo que realmente quieres es que tu IA escriba código para un Durable Object, y luego ejecutar ese código en un Dynamic Worker.

Pero, ¿cómo?

Esto plantea un problema curioso. Normalmente, para utilizar Durable Objects tienes que hacer lo siguiente:

  1. Escribir una clase que extienda DurableObject.

  2. Exportarlo desde el módulo principal de tu Worker.

  3. Especificar en tu configuración de Wrangler que el almacenamiento debe asignarse a esta clase. Esto crea un espacio de nombre de Durable Object que apunta a tu clase para gestionar las solicitudes entrantes.

  4. Declarar un enlace de espacio de nombre de Durable Object que apunte a tu espacio de nombre (o usar ctx.exports), y usarlo para enviar solicitudes a tu Durable Object.

Esto no se extiende naturalmente a Dynamic Workers. Primero, está el problema evidente: el código es dinámico. Lo ejecutas sin invocar en absoluto la API de Cloudflare. Pero el almacenamiento de Durable Object tiene que configurarse a través de la API, y el espacio de nombres tiene que apuntar a una clase que lo implemente. No puede apuntar a tu Dynamic Worker.

Pero hay un problema de fondo: incluso si de alguna manera pudieras configurar un espacio de nombres de Durable Object para que se conecte directamente con un Dynamic Worker, ¿sería realmente conveniente hacerlo? ¿Quieres que tu agente (o usuario) pueda crear un espacio de nombres completo con múltiples instancias de Durable Objects? ¿Quieres usar almacenamiento ilimitado distribuido por todo el mundo?

Probablemente no lo hagas. Probablemente quieras mantener cierto control. Es posible que quieras limitar la cantidad de objetos que crean, o al menos hacer un seguimiento.Tal vez quieras restringirlos a un único objeto (probablemente suficiente para aplicaciones personales codificadas con estilo propio). Es posible que quieras incorporar registro de eventos y otras herramientas de observabilidad. Métricas. Facturación. etc.

Para lograr todo esto, lo que realmente se busca es que las solicitudes a estos Durable Objects se dirijan a tu código primero, donde puedes encargarte de toda la "logística", y luego reenviar la solicitud al código del agente. Lo que se busca es desarrollar un supervisor que funcione integrado en cada Durable Object.

Solución: Durable Object Facets

Hoy lanzamos, en versión beta abierta, una función que resuelve este problema.

Durable Object Facets te permite cargar y poner en funcionamiento una clase de Durable Object de manera dinámica, al tiempo que le asignan una base de datos SQLite para gestionar el almacenamiento. Con Facets:

  • Primero, se crea un espacio de nombres de Durable Object estándar, que se vincula a una clase que tú desarrollas.

  • En esa clase, se carga el código del agente como un Dynamic Worker y se lo ejecuta directamente.

  • El código de Dynamic Worker puede implementar una clase de Durable Object directamente. Es decir, exporta literalmente una clase declarada como extends DurableObject.

  • Estás creando esa clase como una "faceta" de tu propio Durable Object.

  • La faceta recibe su propia base de datos SQLite, que puede utilizar a través de las API de almacenamiento habituales de Durable Object. Esta base de datos es independiente de la del supervisor, pero ambas se almacenan conjuntamente como parte del mismo Durable Object.

Cómo funciona

Esta es una implementación simple y completa de una plataforma de aplicaciones que carga y ejecuta dinámicamente una clase de Durable Object:

import { DurableObject } from "cloudflare:workers";

// For the purpose of this example, we'll use this static
// application code, but in the real world this might be generated
// by AI (or even, perhaps, a human user).
const AGENT_CODE = `
  import { DurableObject } from "cloudflare:workers";

  // Simple app that remembers how many times it has been invoked
  // and returns it.
  export class App extends DurableObject {
    fetch(request) {
      // We use storage.kv here for simplicity, but storage.sql is
      // also available. Both are backed by SQLite.
      let counter = this.ctx.storage.kv.get("counter") || 0;
      ++counter;
      this.ctx.storage.kv.put("counter", counter);

      return new Response("You've made " + counter + " requests.\\n");
    }
  }
`;

// AppRunner is a Durable Object you write that is responsible for
// dynamically loading applications and delivering requests to them.
// Each instance of AppRunner contains a different app.
export class AppRunner extends DurableObject {
  async fetch(request) {
    // We've received an HTTP request, which we want to forward into
    // the app.

    // The app itself runs as a child facet named "app". One Durable
    // Object can have any number of facets (subject to storage limits)
    // with different names, but in this case we have only one. Call
    // this.ctx.facets.get() to get a stub pointing to it.
    let facet = this.ctx.facets.get("app", async () => {
      // If this callback is called, it means the facet hasn't
      // started yet (or has hibernated). In this callback, we can
      // tell the system what code we want it to load.

      // Load the Dynamic Worker.
      let worker = this.#loadDynamicWorker();

      // Get the exported class we're interested in.
      let appClass = worker.getDurableObjectClass("App");

      return { class: appClass };
    });

    // Forward request to the facet.
    // (Alternatively, you could call RPC methods here.)
    return await facet.fetch(request);
  }

  // RPC method that a client can call to set the dynamic code
  // for this app.
  setCode(code) {
    // Store the code in the AppRunner's SQLite storage.
    // Each unique code must have a unique ID to pass to the
    // Dynamic Worker Loader API, so we generate one randomly.
    this.ctx.storage.kv.put("codeId", crypto.randomUUID());
    this.ctx.storage.kv.put("code", code);
  }

  #loadDynamicWorker() {
    // Use the Dynamic Worker Loader API like normal. Use get()
    // rather than load() since we may load the same Worker many
    // times.
    let codeId = this.ctx.storage.kv.get("codeId");
    return this.env.LOADER.get(codeId, async () => {
      // This Worker hasn't been loaded yet. Load its code from
      // our own storage.
      let code = this.ctx.storage.kv.get("code");

      return {
        compatibilityDate: "2026-04-01",
        mainModule: "worker.js",
        modules: { "worker.js": code },
        globalOutbound: null,  // block network access
      }
    });
  }
}

// This is a simple Workers HTTP handler that uses AppRunner.
export default {
  async fetch(req, env, ctx) {
    // Get the instance of AppRunner named "my-app".
    // (Each name has exactly one Durable Object instance in the
    // world.)
    let obj = ctx.exports.AppRunner.getByName("my-app");

    // Initialize it with code. (In a real use case, you'd only
    // want to call this once, not on every request.)
    await obj.setCode(AGENT_CODE);

    // Forward the request to it.
    return await obj.fetch(req);
  }
}

En este ejemplo:

  • AppRunner es un Durable Object "estándar" escrito por el desarrollador de la plataforma (es decir, tú).

  • Cada instancia de AppRunner gestiona una aplicación. Almacena el código de la aplicación y lo carga a pedido.

  • La propia aplicación implementa y exporta una clase de Durable Object, que la plataforma espera que se denomine App.

  • AppRunner carga el código de la aplicación mediante Dynamic Workers y, a continuación, ejecuta el código como Durable Object Facet.

  • Cada instancia de AppRunner es un Durable Object compuesto por dos bases de datos SQLite: una asociada al contenedor (el propio AppRunner) y otra asociada a la faceta (App). Estas bases de datos están aisladas: la aplicación no puede leer la base de datos de AppRunner, solo la suya propia.

Para ejecutar el ejemplo, copia el código anterior en un archivo worker.js, emparéjalo con el siguiente wrangler.jsonc, y ejecútalo localmente con npx wrangler dev.

// wrangler.jsonc for the above sample worker.
{
  "compatibility_date": "2026-04-01",
  "main": "worker.js",
  "migrations": [
    {
      "tag": "v1",
      "new_sqlite_classes": [
        "AppRunner"
      ]
    }
  ],
  "worker_loaders": [
    {
      "binding": "LOADER",
    },
  ],
}

Comienza a crear

Las facetas son una función de Dynamic Workers, disponible de inmediato en versión beta para los usuarios del plan de pago de Workers.

Consulta la documentación para obtener más información sobre Dynamic Workers y Facets.

La conectividad cloud de Cloudflare protege redes corporativas completas, ayuda a los clientes a desarrollar de forma eficiente aplicaciones a escala de Internet, acelera cualquier sitio web o aplicación de Internet, previene contra los ataques DDoS, mantiene a raya a los hackers, y te puede ayudar en tu recorrido hacia la seguridad Zero Trust.

Visita 1.1.1.1 desde cualquier dispositivo para empezar a utilizar nuestra aplicación gratuita y beneficiarte de una navegación más rápida y segura.

Para saber más sobre nuestra misión para ayudar a mejorar Internet, empieza aquí. Si estás buscando un nuevo rumbo profesional, consulta nuestras ofertas de empleo.
Plataforma para desarrolladoresDesarrolladoresAgents WeekCloudflare WorkersDurable ObjectsAlmacenamiento

Síguenos en X

Kenton Varda|@kentonvarda
Cloudflare|@cloudflare

Publicaciones relacionadas