Dynamic Workers 中的 Durable Objects:為每個 AI 產生的應用程式提供其自己的資料庫
這篇文章亦提供 English、Deutsch、Español、Español (Latinoamérica)、Français、Italiano、日本語、한국어、简体中文和Nederlands.

幾週前,我們宣佈推出 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,您必須:
- 編寫一個擴展
DurableObject的類別。 - 從 Worker 的主模組中將其匯出。
- 在 Wrangler 組態中指定,應為此類別佈建儲存體。這會建立一個指向類別的 Durable Object 命名空間,以處理傳入的請求。
- 宣告 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 Workers 和 Facets 的詳細資訊。