durable-objects

durable-objects

熱門

建立與審查 Cloudflare Durable Objects。適用於建構有狀態的協調(聊天室、多人遊戲、預約系統)、實作 RPC 方法、SQLite 儲存、鬧鐘、WebSocket,或審查 DO 程式碼以符合最佳實務。涵蓋 Workers 整合、wrangler 設定,以及使用 Vitest 進行測試。傾向從 Cloudflare 文件檢索而非依賴預先訓練的知識。

2012星標
180分支
更新於 2026/7/1
SKILL.md
readonlyread-only
name
durable-objects
description

建立與審查 Cloudflare Durable Objects。適用於建構有狀態的協調(聊天室、多人遊戲、預約系統)、實作 RPC 方法、SQLite 儲存、鬧鐘、WebSocket,或審查 DO 程式碼以符合最佳實務。涵蓋 Workers 整合、wrangler 設定,以及使用 Vitest 進行測試。傾向從 Cloudflare 文件檢索而非依賴預先訓練的知識。

Durable Objects

在 Cloudflare 邊緣建構有狀態、協調的應用程式,使用 Durable Objects。

檢索來源

你對 Durable Objects API 和設定的知識可能已過時。對於任何 Durable Objects 任務,優先檢索而非預先訓練。

資源 URL
文件 https://developers.cloudflare.com/durable-objects/
API 參考 https://developers.cloudflare.com/durable-objects/api/
最佳實務 https://developers.cloudflare.com/durable-objects/best-practices/
範例 https://developers.cloudflare.com/durable-objects/examples/

實作功能時,請擷取相關的文件頁面。

使用時機

  • 建立新的 Durable Object 類別以進行有狀態協調
  • 實作 RPC 方法、鬧鐘或 WebSocket 處理器
  • 審查現有 DO 程式碼是否符合最佳實務
  • 設定 wrangler.jsonc/toml 中的 DO 綁定與遷移
  • 使用 @cloudflare/vitest-pool-workers 撰寫測試
  • 設計分片策略與父子關係

參考文件

  • ./references/rules.md - 核心規則、儲存、並行、RPC、鬧鐘
  • ./references/testing.md - Vitest 設定、單元/整合測試、鬧鐘測試
  • ./references/workers.md - Workers 處理器、型別、wrangler 設定、可觀測性

搜尋:blockConcurrencyWhileidFromNamegetByNamesetAlarmsql.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,而非一個全域 DO
  2. 使用 getByName() 進行確定性路由 - 相同輸入 = 相同 DO 實例
  3. 使用 SQLite 儲存 - 在遷移中設定 new_sqlite_classes
  4. 在建構子中初始化 - 僅使用 blockConcurrencyWhile() 進行結構設定
  5. 使用 RPC 方法 - 而非 fetch() 處理器(相容日期 >= 2024-04-03)
  6. 先持久化,再快取 - 在更新記憶體狀態前,務必先寫入儲存
  7. 每個 DO 一個鬧鐘 - setAlarm() 會取代任何現有鬧鐘

反模式(絕對不要)

  • 單一全域 DO 處理所有請求(瓶頸)
  • 在每個請求上使用 blockConcurrencyWhile()(扼殺吞吐量)
  • 僅將關鍵狀態儲存在記憶體中(在回收/崩潰時遺失)
  • 在相關的儲存寫入之間使用 await(破壞原子性)
  • fetch() 或外部 I/O 期間持有 blockConcurrencyWhile()

Stub 建立

// 確定性 - 大多數情況較佳
const stub = env.MY_DO.getByName("room-123");

// 從現有 ID 字串
const id = env.MY_DO.idFromString(storedIdString);
const stub = env.MY_DO.get(id);

// 新的唯一 ID - 在外部儲存對應關係
const id = env.MY_DO.newUniqueId();
const stub = env.MY_DO.get(id);

儲存操作

// SQL(同步,建議使用)
this.ctx.storage.sql.exec("INSERT INTO t (c) VALUES (?)", value);
const rows = this.ctx.storage.sql.exec<Row>("SELECT * FROM t").toArray();

// KV(非同步)
await this.ctx.storage.put("key", value);
const val = await this.ctx.storage.get<Type>("key");

鬧鐘

// 排程(取代現有)
await this.ctx.storage.setAlarm(Date.now() + 60_000);

// 處理器
async alarm(): Promise<void> {
  // 處理排程工作
  // 可選擇重新排程:await this.ctx.storage.setAlarm(...)
}

// 取消
await this.ctx.storage.deleteAlarm();

測試快速入門

import { env } from "cloudflare:test";
import { describe, it, expect } from "vitest";

describe("MyDO", () => {
  it("應該正常運作", async () => {
    const stub = env.MY_DO.getByName("test");
    const result = await stub.addItem("test");
    expect(result).toBe(1);
  });
});