訂閱以接收新文章的通知:

Dynamic Workers 中的 Durable Objects:為每個 AI 產生的應用程式提供其自己的資料庫

2026-04-13

閱讀時間:4 分鐘
本貼文還提供以下語言版本:EnglishFrançaisDeutschItaliano日本語한국어Español (Latinoamérica)Español简体中文

幾週前,我們宣佈推出 Dynamic Workers,這是 Workers 平台的一項新功能,可讓您將 Worker 程式碼動態載入至安全的沙箱中。本質上,Dynamic Worker Loader API 提供對 Workers 一直以來作為基礎的基本運算隔離原語的直接存取:隔離,而非容器。隔離環境比容器要輕得多,因此能夠使用 1/10 的記憶體將載入速度提升 100 倍。它們非常高效,並且可視為「一次性產品」:啟動一個並執行幾行程式碼,然後將其丟棄。就像 eval() 的安全版本。

Dynamic Workers 有多種用途。在最初的公告中,我們重點介紹了如何將其用於執行 AI 智慧體生成的程式碼,以替代工具呼叫。在此使用案例中,AI 智慧體可編寫並執行幾行程式碼,藉此依據使用者的請求來執行動作。該程式碼是一次性的,預期一次執行一項任務,執行後立即將其丟棄。

但是,如果您希望 AI 生成更持久的程式碼,該怎麼辦?如果您希望 AI 建置一個具有使用者可與之互動的自訂 UI 的小型應用程式,該怎麼辦?如果您希望應用程式具有長期存留狀態,該怎麼辦?但是,當然您仍希望其在安全的沙箱中執行。

一種方法是使用 Dynamic Workers,並為 Worker 提供一個 RPC API,讓其能夠存取儲存體。您可使用繫結,為 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 Objects 的完整命名空間?要使用分佈於世界各地的無限儲存體嗎?

您可能不需要。您可能需要某些控制權。您可能想要限制或至少追蹤其建立的物件數量。也許您想要將其限制為僅一個物件(對於 Vibe 編碼的個人應用程式可能已經足夠)。您可能想要新增記錄和其他可觀測性,包括指標、計費等等

為此,您真正需要的是,讓這些針對 Durable Objects 的請求首先到達您的程式碼,在此您可完成所有的「後勤作業」,然後將請求轉傳至代理程式的程式碼中。您想要編寫一個在每個 Durable Object 中執行的監督員

解決方案:Durable Object Facets

今天,我們以公開測試版的形式發布了可解決此問題的功能。

Durable Object Facets 可讓您動態載入及具現化 Durable Object 類別,同時為其提供 SQLite 資料庫以用於儲存。使用 Facet:

  • 首先,您建立一個普通的 Durable Object 命名空間,指向編寫的一個類別。

  • 在該類別中,您將代理程式的程式碼作為 Dynamic Worker 載入,並對其進行呼叫。

  • Dynamic Worker 的程式碼可直接實作 Durable Object 類別。也就是說,其可逐字彙出一個宣告為延伸 DurableObject 的類別。

  • 您正在將該類別具現化為您自己的 Durable Object 的「面向」。

  • 該面向有自己的 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 的每個執行個體都是一個 Durable Object,由 兩個 SQLite 資料庫組成:一個屬於父項(AppRunner 本身),另一個屬於面向(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",
    },
  ],
}

開始建置

Facet 是 Dynamic Workers 的一項功能,目前提供測試版,可供 Workers 付費方案使用者使用。

請查閱文件以瞭解有關 Dynamic WorkersFacets 的詳細資訊。

我們保護整個企業網路,協助客戶有效地建置網際網路規模的應用程式,加速任何網站或網際網路應用程式抵禦 DDoS 攻擊,阻止駭客入侵,並且可以協助您實現 Zero Trust

從任何裝置造訪 1.1.1.1,即可開始使用我們的免費應用程式,讓您的網際網路更快速、更安全。

若要進一步瞭解我們協助打造更好的網際網路的使命,請從這裡開始。如果您正在尋找新的職業方向,請查看我們的職缺
Agents Week開發人員平台開發人員Agents WeekCloudflare WorkersDurable Objects儲存

在 X 上進行關注

Kenton Varda|@kentonvarda
Cloudflare|@cloudflare

相關貼文

2026年4月30日

Agents can now create Cloudflare accounts, buy domains, and deploy

Starting today, agents can now be Cloudflare customers. They can create a Cloudflare account, start a paid subscription, register a domain, and get back an API token to deploy code right away. Humans can be in the loop to grant permission, but there’s no need to go to the dashboard, copy and paste API tokens, or enter credit card details. ...

2026年4月22日

Making Rust Workers reliable: panic and abort recovery in wasm‑bindgen

Panics in Rust Workers were historically fatal, poisoning the entire instance. By collaborating upstream on the wasm‑bindgen project, Rust Workers now support resilient critical error recovery, including panic unwinding using WebAssembly Exception Handling....

2026年4月20日

The AI engineering stack we built internally — on the platform we ship

We built our internal AI engineering stack on the same products we ship. That means 20 million requests routed through AI Gateway, 241 billion tokens processed, and inference running on Workers AI, serving more than 3,683 internal users. Here's how we did it. ...