durable-objects

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Durable Objects

Durable Objects

Build stateful, coordinated applications on Cloudflare's edge using Durable Objects.
使用Durable Objects在Cloudflare边缘构建有状态的协调型应用。

When to Use

适用场景

  • Creating new Durable Object classes for stateful coordination
  • Implementing RPC methods, alarms, or WebSocket handlers
  • Reviewing existing DO code for best practices
  • Configuring wrangler.jsonc/toml for DO bindings and migrations
  • Writing tests with
    @cloudflare/vitest-pool-workers
  • Designing sharding strategies and parent-child relationships
  • 为有状态协调系统创建新的Durable Object类
  • 实现RPC方法、告警或WebSocket处理器
  • 审核现有DO代码以遵循最佳实践
  • 配置wrangler.jsonc/toml以实现DO绑定与迁移
  • 使用
    @cloudflare/vitest-pool-workers
    编写测试
  • 设计分片策略与父子对象关系

Reference Documentation

参考文档

  • ./references/rules.md
    - Core rules, storage, concurrency, RPC, alarms
  • ./references/testing.md
    - Vitest setup, unit/integration tests, alarm testing
  • ./references/workers.md
    - Workers handlers, types, wrangler config, observability
Search:
blockConcurrencyWhile
,
idFromName
,
getByName
,
setAlarm
,
sql.exec
  • ./references/rules.md
    - 核心规则、存储、并发、RPC、告警
  • ./references/testing.md
    - Vitest配置、单元/集成测试、告警测试
  • ./references/workers.md
    - Workers处理器、类型定义、wrangler配置、可观测性
搜索关键词:
blockConcurrencyWhile
,
idFromName
,
getByName
,
setAlarm
,
sql.exec

Core Principles

核心原则

Use Durable Objects For

Durable Objects的适用场景

NeedExample
CoordinationChat rooms, multiplayer games, collaborative docs
Strong consistencyInventory, booking systems, turn-based games
Per-entity storageMulti-tenant SaaS, per-user data
Persistent connectionsWebSockets, real-time notifications
Scheduled work per entitySubscription renewals, game timeouts
适用场景示例
协调系统聊天室、多人游戏、协作文档
强一致性需求库存管理、预订系统、回合制游戏
单实体存储多租户SaaS、用户专属数据
持久化连接WebSocket、实时通知
单实体定时任务订阅续费、游戏超时处理

Do NOT Use For

Durable Objects的不适用场景

  • Stateless request handling (use plain Workers)
  • Maximum global distribution needs
  • High fan-out independent requests
  • 无状态请求处理(使用普通Workers即可)
  • 需要全局最大程度分发的场景
  • 高扇出的独立请求

Quick Reference

快速参考

Wrangler Configuration

Wrangler配置

jsonc
// wrangler.jsonc
{
  "durable_objects": {
    "bindings": [{ "name": "MY_DO", "class_name": "MyDurableObject" }]
  },
  "migrations": [{ "tag": "v1", "new_sqlite_classes": ["MyDurableObject"] }]
}
jsonc
// wrangler.jsonc
{
  "durable_objects": {
    "bindings": [{ "name": "MY_DO", "class_name": "MyDurableObject" }]
  },
  "migrations": [{ "tag": "v1", "new_sqlite_classes": ["MyDurableObject"] }]
}

Basic Durable Object Pattern

基础Durable Object模式

typescript
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 });
  },
};
typescript
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 });
  },
};

Critical Rules

核心规则

  1. Model around coordination atoms - One DO per chat room/game/user, not one global DO
  2. Use
    getByName()
    for deterministic routing
    - Same input = same DO instance
  3. Use SQLite storage - Configure
    new_sqlite_classes
    in migrations
  4. Initialize in constructor - Use
    blockConcurrencyWhile()
    for schema setup only
  5. Use RPC methods - Not fetch() handler (compatibility date >= 2024-04-03)
  6. Persist first, cache second - Always write to storage before updating in-memory state
  7. One alarm per DO -
    setAlarm()
    replaces any existing alarm
  1. 围绕协调原子建模 - 每个聊天室/游戏/用户对应一个DO实例,而非使用全局单一DO
  2. 使用
    getByName()
    实现确定性路由
    - 相同输入对应同一个DO实例
  3. 使用SQLite存储 - 在迁移中配置
    new_sqlite_classes
  4. 在构造函数中完成初始化 - 仅在Schema设置时使用
    blockConcurrencyWhile()
  5. 使用RPC方法 - 而非fetch()处理器(兼容性日期需≥2024-04-03)
  6. 先持久化,后缓存 - 始终先写入存储,再更新内存状态
  7. 每个DO最多一个告警 -
    setAlarm()
    会替换已存在的告警

Anti-Patterns (NEVER)

反模式(严禁使用)

  • Single global DO handling all requests (bottleneck)
  • Using
    blockConcurrencyWhile()
    on every request (kills throughput)
  • Storing critical state only in memory (lost on eviction/crash)
  • Using
    await
    between related storage writes (breaks atomicity)
  • Holding
    blockConcurrencyWhile()
    across
    fetch()
    or external I/O
  • 使用单一全局DO处理所有请求(会造成性能瓶颈)
  • 在每个请求中都使用
    blockConcurrencyWhile()
    (严重降低吞吐量)
  • 仅在内存中存储关键状态(实例被回收/崩溃时会丢失)
  • 相关存储写入操作之间使用
    await
    (破坏原子性)
  • fetch()
    或外部I/O操作期间持有
    blockConcurrencyWhile()

Stub Creation

Stub创建

typescript
// 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);
typescript
// 确定性路由 - 大多数场景的首选方式
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);

Storage Operations

存储操作

typescript
// 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");
typescript
// 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");

Alarms

告警

typescript
// 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();
typescript
// 调度告警(会替换已存在的告警)
await this.ctx.storage.setAlarm(Date.now() + 60_000);

// 告警处理器
async alarm(): Promise<void> {
  // 处理定时任务
  // 可选:重新调度告警: await this.ctx.storage.setAlarm(...)
}

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

Testing Quick Start

测试快速开始

typescript
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);
  });
});
typescript
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);
  });
});