订阅以接收新文章的通知:

Dynamic Workers 中的 Durable Objects:为每个 AI 生成的应用提供独立的数据库

2026-04-13

阅读时间:4 分钟

几周前,我们宣布推出 Dynamic Workers,Workers 平台的一项全新特性,支持您将 Worker 代码实时加载至安全沙盒中。动态 Worker 加载器 API 本质上提供了对 Worker 底层一直以来所依赖的基础计算隔离组件的直接访问能力:即隔离环境(isolates),而非容器(containers)。Isolates 比容器轻量得多,因此加载速度可提升 100 倍,且仅占用 1/10 的内存。它们非常高效,可被视作“一次性”资源:启动一个实例运行几行代码,执行完毕即销毁。类似于安全版本的 eval () 函数。

Dynamic Workers 有很多用途。在最初的发布公告中,我们重点介绍了如何利用 Dynamic Workers 运行由 AI 智能体生成的代码,以此作为工具调用的替代方案。在此场景中,AI 智能体可根据您的请求,通过编写并执行几行代码来完成相应操作。此类代码为一次性使用,仅用于单次执行一项任务,且在执行完毕后会立即被销毁。

但如果您希望 AI 生成更具持久性的代码,又该如何实现?那如果您希望 AI 构建一款带有自定义 UI、可供用户交互的小型应用,又该如何实现?如果您希望该应用具备持久化状态,又该如何实现?但是,当然,您仍然希望它在安全的沙盒环境中运行。

其中一种方法是使用 Dynamic Workers,并且只需为 Worker 提供一个RPC API,让其可以访问存储。使用 绑定(bindings),您可以为 Dynamic Worker 提供一个 API,使其指回您的远程 SQL 数据库 (可能由 Cloudflare D1 支持,或您通过 Hyperdrive 访问的 Postgres 数据库,取决于您)。

不过,Workers 还提供了一种独特且性能极高的存储类型,它或许非常适用于该场景:Durable Objects。Durable Object 是一类特殊的 Worker,拥有唯一名称,且每个名称全局仅存在一个实例。该实例附加了一个 SQLite 数据库,该数据库位于运行 Durable Object 机器的本地磁盘上。这使得存储访问速度极快:接近零延迟

那么,也许您真正想要的是让 AI 为 Durable Object 编写代码,然后在 Dynamic Worker 中运行该代码。

但如何做到呢?

这就带来了一个奇怪的问题。通常,要使用 Durable Objects,您必须:

  1. 编写一个扩展 DurableObject 的类。

  2. 从 Worker 的主模块将其导出。

  3. 在 Wrangler 配置中指定应该需为此类配置存储。这将创建一个 Durable Object 命名空间,指向用于您处理传入请求的类。

  4. 声明 Durable Object 命名空间绑定指向您的命名空间 (或使用 ctx.exports),并使用它向您的 Durable Object 发出请求。

这种模式无法自然适配至 Dynamic Workers。首先,一个显而易见的问题是:代码是动态加载的。您无需调用任何 Cloudflare API 即可运行它。但 Durable Object 存储必须通过 API 配置开通,且命名空间必须指向一个实现类。它不能指向您的 Dynamic Worker。

但还有一个更深层的问题:即便您能通过某种方式将 Durable Object 命名空间直接指向一个 Dynamic Worker,您真的希望这样做吗?您是否希望您的智能体(或用户)能够创建一整个包含大量 Durable Object 的命名空间?随意使用遍布全球的无限制存储?

您很可能不希望这样做。您可能需要一些管控。您可能希望限制,或至少追踪其创建的 Durable Object 数量。也许您希望将其限制为一个对象(对于采用氛围编码的个人应用可能已经足够)。您可能希望添加日志记录及其他可观测性能力。指标,账单,等等。

要实现这一切,您真正想要的是:将发往这些 Durable Object 的请求首先转发至您的代码,由您的代码完成所有调度与管控逻辑处理后,将请求转发至智能体代码。您需要编写一个监管程序,使其作为每个 Durable Object 的一部分运行。

解决方案:Durable Object Facets

我们今天推出公测版的这一全新功能就是为了解决上述问题。

Durable Object Facets 允许您动态加载和实例化 Durable Object 类,同时为其提供 SQLite 数据库用于存储。通过 Facets:

  • 首先,创建一个普通的 Durable Object 命名空间,指向编写的一个类。

  • 在该类中,您将智能体的代码作为 Dynamic Worker 加载,并对其进行调用。

  • Dynamic Worker 的代码可以直接实现一个 Durable Object 类。换句话说,它导出了一个继承自 DurableObject 的类。

  • 您正在将该类实例化为您的 Durable Object 的一个facet(面)。

  • 该面拥有自己的 SQLite 数据库,可通过普通的 Durable Object 存储 API 使用该数据库。该数据库与监管程序的数据库相互独立,但二者作为同一整体 Durable Object 的组成部分共同存储。

工作原理

以下是一个简单且完整的应用平台实现示例,可动态加载并运行一个 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);
  }
}

在本例中:

  • AppRunner 是由平台开发人员(您)编写的“普通” Durable Object。

  • AppRunner 的每个实例管理一个应用。它存储应用的代码并按需加载。

  • 应用本身实现并导出一个 Durable Object 类,平台要求该类命名为 App

  • AppRunner 使用 Dynamic Workers 加载应用代码,然后作为一个 Durable Object Facet 执行代码。

  • AppRunner 的每个实例都是一个由 两个 SQLite 数据库组成的 Durable Object:一个属于父级 (AppRunner 本身),一个属于该 facet (App)。这些数据库是隔离的:应用无法读取 AppRunner 的数据库,只能读取自己的数据库。

要运行该示例,请将上面的代码复制到文件 worker.js 中,配置如下 wrangler.jsonc,并使用 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",
    },
  ],
}

开始构建

Facets 是 Dynamic Workers 的一项特性,测试版已向 Workers Paid 计划用户开放。

请查看文档以了解有关 Dynamic WorkersFacets 的更多信息。

我们保护整个企业网络,帮助客户高效构建互联网规模的应用程序,加速任何网站或互联网应用程序抵御 DDoS 攻击,防止黑客入侵,并能协助您实现 Zero Trust 的过程

从任何设备访问 1.1.1.1,以开始使用我们的免费应用程序,帮助您更快、更安全地访问互联网。要进一步了解我们帮助构建更美好互联网的使命,请从这里开始。如果您正在寻找新的职业方向,请查看我们的空缺职位
Agents Week开发人员平台开发人员Agents WeekCloudflare WorkersDurable Objects存储

在 X 上关注

Kenton Varda|@kentonvarda
Cloudflare|@cloudflare

相关帖子