durable-objects について
Cloudflare Durable Objectsを使ったステートフルなアプリケーションの構築とレビューを支援する開発スキルです。調整、ストレージ、RPC、アラーム、設定、テストを扱い、実装向けの資料を案内します。
主な機能
- 01Durable Objects設計のガイダンス
- 02ストレージ・RPC・アラームのパターン
- 03Wranglerのバインディングと移行設定
- 04Vitest pool for Workersによるテスト
- 05実装詳細を参照する資料
活用シーン
- 01チャットやゲームの状態調整
- 02Durable ObjectsのRPCやアラーム実装
- 03バインディングと移行の設定
- 04Durable Objectsコードのテスト
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ cloudflare/ cloudflare-docs --skill durable-objectsインストール後、Claude Code で/durable-objectsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/rules.mdtesting.mdworkers.md詳細
Durable Objects
Cloudflare のエッジで Durable Objects を使用して、状態を保持し、連携するアプリケーションを構築します。
取得優先の開発
Durable Objects に関するタスクでは、事前学習済みの知識よりも、公式ドキュメントからの情報取得を優先してください。
機能を実装する際は、関連するドキュメントページを取得してください。
使用する場面
- 状態を保持する連携処理のために、新しい Durable Object クラスを作成する
- RPC メソッド、アラーム、WebSocket ハンドラーを実装する
- 既存の DO コードをベストプラクティスに照らしてレビューする
- DO バインディングとマイグレーションのために
wrangler.jsonc/tomlを設定する @cloudflare/vitest-pool-workersでテストを作成する- シャーディング戦略と親子関係を設計する
参考ドキュメント
./references/rules.md- 基本ルール、ストレージ、並行処理、RPC、アラーム./references/testing.md- Vitest のセットアップ、単体テスト/統合テスト、アラームのテスト./references/workers.md- Workers のハンドラー、型、wrangler の設定、可観測性
検索対象:blockConcurrencyWhile、idFromName、getByName、setAlarm、sql.exec
基本原則
Durable Objects を使用する場面
| 必要性 | 例 |
|---|---|
| 連携 | チャットルーム、マルチプレイヤーゲーム、共同編集ドキュメント |
| 強整合性 | 在庫、予約システム、ターン制ゲーム |
| エンティティごとのストレージ | マルチテナント SaaS、ユーザーごとのデータ |
| 持続的な接続 | WebSocket、リアルタイム通知 |
| エンティティごとのスケジュール処理 | サブスクリプションの更新、ゲームのタイムアウト |
使用しないでください
- ステートレスなリクエスト処理(通常の Workers を使用してください)
- グローバルに最大限分散する必要がある場合
- ファンアウトの多い独立したリクエスト
クイックリファレンス
Wrangler の設定
// wrangler.jsonc
{
"durable_objects": {
"bindings": [{ "name": "MY_DO", "class_name": "MyDurableObject" }]
},
"migrations": [{ "tag": "v1", "new_sqlite_classes": ["MyDurableObject"] }]
}
基本的な Durable Object パターン
import { DurableObject } from "cloudflare:workers";
export interface Env {
MY_DO: DurableObjectNamespace<MyDurableObject>;
}
export class MyDurableObject extends DurableObject<Env> {
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env);
ctx.blockConcurrencyWhile(async () => {
this.ctx.storage.sql.exec(`
CREATE TABLE IF NOT EXISTS items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
data TEXT NOT NULL
)
`);
});
}
async addItem(data: string): Promise<number> {
const result = this.ctx.storage.sql.exec<{ id: number }>(
"INSERT INTO items (data) VALUES (?) RETURNING id",
data
);
return result.one().id;
}
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const stub = env.MY_DO.getByName("my-instance");
const id = await stub.addItem("hello");
return Response.json({ id });
},
};
重要なルール
- 連携の単位を中心にモデル化する - グローバルに 1 つの DO を使うのではなく、チャットルーム/ゲーム/ユーザーごとに 1 つの DO を使用します
- 決定的なルーティングには
getByName()を使用する - 同じ入力からは同じ DO インスタンスが得られます - SQLite ストレージを使用する - マイグレーションで
new_sqlite_classesを設定します - コンストラクターで初期化する - スキーマ設定に限って
blockConcurrencyWhile()を使用します - RPC メソッドを使用する - fetch() ハンドラーは使用しません(互換性日付が 2024-04-03 以降の場合)
- 先に永続化し、後からキャッシュする - メモリー内の状態を更新する前に、必ずストレージへ書き込みます
- DO ごとにアラームは 1 つ -
setAlarm()は既存のアラームを置き換えます
アンチパターン(絶対に避けること)
- すべてのリクエストを 1 つのグローバル DO で処理する(ボトルネックになります)
- すべてのリクエストで
blockConcurrencyWhile()を使用する(スループットが低下します) - 重要な状態をメモリー内だけに保存する(エビクションやクラッシュで失われます)
- 関連するストレージ書き込みの間に
awaitを使用する(アトミック性が損なわれます) fetch()または外部 I/O の間、blockConcurrencyWhile()を保持する
スタブの作成
// Deterministic - preferred for most cases
const stub = env.MY_DO.getByName("room-123");
// From existing ID string
const id = env.MY_DO.idFromString(storedIdString);
const stub = env.MY_DO.get(id);
// New unique ID - store mapping externally
const id = env.MY_DO.newUniqueId();
const stub = env.MY_DO.get(id);
ストレージ操作
// SQL (synchronous, recommended)
this.ctx.storage.sql.exec("INSERT INTO t (c) VALUES (?)", value);
const rows = this.ctx.storage.sql.exec<Row>("SELECT * FROM t").toArray();
// KV (async)
await this.ctx.storage.put("key", value);
const val = await this.ctx.storage.get<Type>("key");
アラーム
// Schedule (replaces existing)
await this.ctx.storage.setAlarm(Date.now() + 60_000);
// Handler
async alarm(): Promise<void> {
// Process scheduled work
// Optionally reschedule: await this.ctx.storage.setAlarm(...)
}
// Cancel
await this.ctx.storage.deleteAlarm();
テストのクイックスタート
import { env } from "cloudflare:test";
import { describe, it, expect } from "vitest";
describe("MyDO", () => {
it("should work", async () => {
const stub = env.MY_DO.getByName("test");
const result = await stub.addItem("test");
expect(result).toBe(1);
});
});
コメント
Markdown 対応