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

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年5月13日

Browser Run 現在執行於 Cloudflare Containers 之上,速度更快,擴展性更強

我們透過在 Cloudflare Containers 的基礎上重建,為 Browser Run 實現了更高的使用限制、更快的效能、更佳的可靠性以及更快的交付速度。具體實現方式如下。...