ClaudeCloudflare2026/04/13 13:08

Durable Objects in Dynamic Workers: Give each AI-generated app its own database

要点だけを先に読めるように短く再構成したセクションです。

元記事

Quick Digest

要約

要点だけを先に読めるように短く再構成したセクションです。

claudejamodel: claude-sonnet-4-20250514

Dynamic WorkersでDurable Objects Facets機能がベータリリース - AI生成アプリに専用データベースを提供

Key Points

  • AI生成アプリに専用SQLiteデータベースを動的に提供
  • Durable Object Facets機能がオープンベータで利用可能
  • ゼロレイテンシでセキュアなアプリケーション実行環境

Summary

CloudflareがDynamic WorkersにDurable Object Facets機能をオープンベータでリリースしました。この機能により、AI生成コードを動的にDurable Objectとして実行し、専用のSQLiteデータベースを提供できるようになります。

Key Points

  • Dynamic Workers + Durable Objects: AI生成コードをDurable Objectクラスとして動的にロード・実行可能
  • Facets機能: 1つのDurable Object内で複数の独立したSQLiteデータベースを管理
  • セキュリティと制御: スーパーバイザーコードがリクエストを仲介し、ログ記録や制限を実装可能
  • ゼロレイテンシストレージ: ローカルディスク上のSQLiteによる高速データアクセス
  • 簡単な実装: this.ctx.facets.get()でファセットを取得し、通常のDurable Object APIを使用

使用例

// AppRunnerクラス内でファセットを取得
let facet = this.ctx.facets.get("app", async () => {
  let worker = this.#loadDynamicWorker();
  let appClass = worker.getDurableObjectClass("App");
  return { class: appClass };
});

Full Translation

翻訳

原文の流れを保ったまま読める翻訳セクションです。

claudejamodel: claude-sonnet-4-20250514

Dynamic WorkersにおけるDurable Objects:AI生成アプリにそれぞれ独自のデータベースを提供

数週間前、私たちはDynamic Workersを発表しました。これはWorkersプラットフォームの新機能で、Workerコードをセキュアなサンドボックスにオンザフライで読み込むことができます。Dynamic Worker Loader APIは、Workersがずっと基盤としてきた基本的なコンピュート分離プリミティブ(コンテナではなくisolates)への直接アクセスを本質的に提供します。isolatesはコンテナよりもはるかに軽量で、100倍高速に読み込み、メモリ使用量は1/10です。非常に効率的なため、「使い捨て」として扱うことができます:数行のコードを実行するために起動し、その後破棄します。セキュアなeval()のようなものです。

Dynamic Workersには多くの用途があります。最初の発表では、ツール呼び出しの代替として、AIエージェントが生成したコードを実行する方法に焦点を当てました。この使用例では、AIエージェントがユーザーのリクエストに応じて数行のコードを書いて実行することでアクションを実行します。コードは単発使用で、一度だけ一つのタスクを実行することを意図しており、実行後すぐに破棄されます。

より永続的なコードの生成

しかし、AIにより永続的なコードを生成させたい場合はどうでしょうか?AIに、ユーザーが操作できるカスタムUIを持つ小さなアプリケーションを構築させたい場合は?そのアプリケーションに長期間存続する状態を持たせたい場合は?もちろん、それでもセキュアなサンドボックスで実行させたいでしょう。

これを行う一つの方法は、Dynamic Workersを使用し、単純にWorkerにストレージへのアクセスを提供するRPC APIを提供することです。bindingsを使用して、Dynamic WorkerにリモートのSQLデータベース(Cloudflare D1でサポートされているか、Hyperdrive経由でアクセスするPostgresデータベース - 選択はあなた次第)を指すAPIを提供できます。

しかし、Workersには、この使用例に完璧に適合する可能性のある、ユニークで非常に高速なストレージタイプもあります:Durable Objectsです。

Durable Objectsとは

Durable Objectは、一意の名前を持つ特別な種類のWorkerで、名前ごとに世界中で一つのインスタンスがあります。そのインスタンスには、Durable Objectが実行されるマシンのローカルディスクに存在するSQLiteデータベースが接続されています。これにより、ストレージアクセスが驚くほど高速になります:実質的にレイテンシがゼロです。

おそらく、あなたが本当に望んでいるのは、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でいっぱいのネームスペース全体を作成できるようにしたいですか?世界中に分散された無制限のストレージを使用できるようにしたいですか?

おそらくそうではありません。おそらく何らかの制御が必要でしょう。作成するオブジェクトの数を制限したい、または少なくとも追跡したいかもしれません。一つのオブジェクトに制限したいかもしれません(個人的なアプリには十分でしょう)。ログ記録やその他の可観測性を追加したいかもしれません。メトリクス。課金。など。

これらすべてを行うために、本当に望んでいるのは、これらのDurable Objectsへのリクエストが最初にあなたのコードに送られ、そこですべての「ロジスティクス」を行い、その後リクエストをエージェントのコードに転送することです。すべてのDurable Objectの一部として実行されるスーパーバイザーを書きたいのです。

解決策:Durable Object Facets

本日、この問題を解決する機能をオープンベータでリリースします。Durable Object Facetsにより、Durable Objectクラスを動的に読み込んでインスタンス化し、ストレージ用のSQLiteデータベースを提供できます。

Facetsでは:

  1. まず、あなたが書いたクラスを指す通常のDurable Objectネームスペースを作成します
  2. そのクラスで、エージェントのコードをDynamic Workerとして読み込み、それを呼び出します
  3. Dynamic Workerのコードは、Durable Objectクラスを直接実装できます。つまり、文字通りextends DurableObjectとして宣言されたクラスをエクスポートします
  4. そのクラスを自分のDurable Objectの「facet」としてインスタンス化します
  5. facetは独自のSQLiteデータベースを取得し、通常のDurable Objectストレージ APIを介してそれを使用できます
  6. このデータベースはスーパーバイザーのデータベースとは分離されていますが、両方は同じ全体的なDurable Objectの一部として一緒に保存されます

動作方法

以下は、Durable Objectクラスを動的に読み込んで実行するアプリプラットフォームの簡単で完全な実装です:

import { DurableObject } from "cloudflare:workers";

// この例では、この静的なアプリケーションコードを使用しますが、
// 実際の世界では、これはAI(または人間のユーザー)によって
// 生成される可能性があります。
const AGENT_CODE = `
import { DurableObject } from "cloudflare:workers";

// 呼び出された回数を記憶して返すシンプルなアプリ
export class App extends DurableObject {
  fetch(request) {
    // 簡単にするためにstorage.kvを使用していますが、
    // storage.sqlも利用可能です。両方とも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は、アプリケーションを動的に読み込み、
// リクエストを配信する責任を持つ、あなたが書くDurable Objectです。
// AppRunnerの各インスタンスには異なるアプリが含まれます。
export class AppRunner extends DurableObject {
  async fetch(request) {
    // HTTPリクエストを受信しました。これをアプリに転送したいと思います。
    
    // アプリ自体は「app」という名前の子facetとして実行されます。
    // 一つのDurable Objectは異なる名前を持つ任意の数のfacetを
    // 持つことができます(ストレージ制限の対象)が、
    // この場合は一つだけです。
    // this.ctx.facets.get()を呼び出してそれを指すスタブを取得します。
    let facet = this.ctx.facets.get("app", async () => {
      // このコールバックが呼ばれた場合、facetがまだ開始されていない
      // (または休止状態になっている)ことを意味します。
      // このコールバックで、読み込みたいコードをシステムに伝えることができます。
      
      // Dynamic Workerを読み込みます。
      let worker = this.#loadDynamicWorker();
      
      // 興味のあるエクスポートされたクラスを取得します。
      let appClass = worker.getDurableObjectClass("App");
      
      return { class: appClass };
    });
    
    // リクエストをfacetに転送します。
    // (または、ここでRPCメソッドを呼び出すこともできます。)
    return await facet.fetch(request);
  }
  
  // クライアントがこのアプリの動的コードを設定するために
  // 呼び出すことができるRPCメソッド
  setCode(code) {
    // コードをAppRunnerのSQLiteストレージに保存します。
    // 各ユニークなコードは、Dynamic Worker Loader APIに渡すための
    // ユニークなIDを持つ必要があるため、ランダムに生成します。
    this.ctx.storage.kv.put("codeId", crypto.randomUUID());
    this.ctx.storage.kv.put("code", code);
  }
  
  #loadDynamicWorker() {
    // 通常通りDynamic Worker Loader APIを使用します。
    // 同じWorkerを何度も読み込む可能性があるため、
    // load()ではなくget()を使用します。
    let codeId = this.ctx.storage.kv.get("codeId");
    return this.env.LOADER.get(codeId, async () => {
      // このWorkerはまだ読み込まれていません。
      // 自分のストレージからコードを読み込みます。
      let code = this.ctx.storage.kv.get("code");
      return {
        compatibilityDate: "2026-04-01",
        mainModule: "worker.js",
        modules: {
          "worker.js": code
        },
        globalOutbound: null, // ネットワークアクセスをブロック
      };
    });
  }
}

// これはAppRunnerを使用するシンプルなWorkers HTTPハンドラーです。
export default {
  async fetch(req, env, ctx) {
    // "my-app"という名前のAppRunnerのインスタンスを取得します。
    // (各名前は世界中で正確に一つのDurable Objectインスタンスを持ちます。)
    let obj = ctx.exports.AppRunner.getByName("my-app");
    
    // コードで初期化します。(実際の使用例では、
    // これを一度だけ呼び出したいでしょう。すべてのリクエストでではなく。)
    await obj.setCode(AGENT_CODE);
    
    // リクエストを転送します。
    return await obj.fetch(req);
  }
}

この例について

  • AppRunnerは、プラットフォーム開発者(あなた)によって書かれた「通常の」Durable Objectです
  • AppRunnerの各インスタンスは一つのアプリケーションを管理します。アプリコードを保存し、オンデマンドで読み込みます
  • アプリケーション自体は、プラットフォームがAppという名前であることを期待するDurable Objectクラスを実装してエクスポートします
  • AppRunnerはDynamic Workersを使用してアプリケーションコードを読み込み、そのコードをDurable Object Facetとして実行します
  • AppRunnerの各インスタンスは、2つのSQLiteデータベースで構成される一つのDurable Objectです:一つは親(AppRunner自体)に属し、もう一つはfacet(App)に属します
  • これらのデータベースは分離されています:アプリケーションはAppRunnerのデータベースを読み取ることはできず、自分のもののみです

実行方法

例を実行するには、上記のコードをworker.jsファイルにコピーし、以下のwrangler.jsoncと組み合わせて、npx wrangler devでローカルで実行してください。

// 上記のサンプルworker用のwrangler.jsonc
{
  "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 WorkersとFacetsについて詳しく学ぶには、ドキュメントをご確認ください。

Cloudflareの接続クラウドは、企業ネットワーク全体を保護し、顧客がインターネット規模のアプリケーションを効率的に構築するのを支援し、あらゆるWebサイトやインターネットアプリケーションを高速化し、DDoS攻撃を防ぎ、ハッカーを寄せ付けず、Zero Trustへの道のりをサポートします。任意のデバイスから1.1.1.1にアクセスして、インターネットをより高速で安全にする無料アプリを始めましょう。より良いインターネットの構築を支援するという私たちの使命について詳しく学ぶには、こちらから始めてください。新しいキャリアの方向性をお探しの場合は、私たちの求人情報をご確認ください。