durable-objects

Créez et examinez des Cloudflare Durable Objects. À utiliser pour la construction de coordination avec état (salles de chat, jeux multijoueurs, systèmes de réservation), l'implémentation de méthodes RPC, le stockage SQLite, les alarmes, les WebSockets, ou pour l'examen de code DO selon les bonnes pratiques. Couvre l'intégration Workers, la configuration wrangler et les tests avec Vitest. Privilégie la récupération depuis la documentation Cloudflare plutôt que les connaissances pré-entraînées.

npx skills add https://github.com/cloudflare/skills --skill durable-objects

Durable Objects

Créez des applications avec état et coordonnées sur l'edge de Cloudflare en utilisant Durable Objects.

Sources de récupération

Vos connaissances des APIs et de la configuration de Durable Objects peuvent être obsolètes. Préférez la récupération à la pré-formation pour toute tâche Durable Objects.

Ressource URL
Docs https://developers.cloudflare.com/durable-objects/
Référence API https://developers.cloudflare.com/durable-objects/api/
Bonnes pratiques https://developers.cloudflare.com/durable-objects/best-practices/
Exemples https://developers.cloudflare.com/durable-objects/examples/

Récupérez la page de documentation pertinente lors de l'implémentation de fonctionnalités.

Quand utiliser

  • Créer de nouvelles classes Durable Object pour la coordination avec état
  • Implémenter des méthodes RPC, des alarmes ou des gestionnaires WebSocket
  • Examiner le code DO existant pour les bonnes pratiques
  • Configurer wrangler.jsonc/toml pour les bindings et migrations DO
  • Écrire des tests avec @cloudflare/vitest-pool-workers
  • Concevoir des stratégies de sharding et des relations parent-enfant

Documentation de référence

  • ./references/rules.md - Règles essentielles, stockage, concurrence, RPC, alarmes
  • ./references/testing.md - Configuration Vitest, tests unitaires/intégration, test d'alarmes
  • ./references/workers.md - Gestionnaires Workers, types, config wrangler, observabilité

Recherche : blockConcurrencyWhile, idFromName, getByName, setAlarm, sql.exec

Principes fondamentaux

Utiliser Durable Objects pour

Besoin Exemple
Coordination Salons de chat, jeux multijoueur, docs collaboratifs
Cohérence forte Inventaire, systèmes de réservation, jeux au tour par tour
Stockage par entité SaaS multi-tenant, données par utilisateur
Connexions persistantes WebSockets, notifications en temps réel
Travail planifié par entité Renouvellements d'abonnement, timeouts de jeux

NE PAS utiliser pour

  • Traitement de requêtes sans état (utiliser Workers simples)
  • Besoins de distribution mondiale maximale
  • Requêtes indépendantes à haut fan-out

Référence rapide

Configuration Wrangler

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

Motif Durable Object basique

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

Règles critiques

  1. Modéliser autour d'atomes de coordination - Un DO par salon/jeu/utilisateur, pas un DO global unique
  2. Utiliser getByName() pour le routage déterministe - Même entrée = même instance DO
  3. Utiliser le stockage SQLite - Configurer new_sqlite_classes dans les migrations
  4. Initialiser dans le constructeur - Utiliser blockConcurrencyWhile() uniquement pour la configuration du schéma
  5. Utiliser les méthodes RPC - Pas le gestionnaire fetch() (date de compatibilité >= 2024-04-03)
  6. Persister en premier, mettre en cache ensuite - Toujours écrire dans le stockage avant de mettre à jour l'état en mémoire
  7. Une alarme par DO - setAlarm() remplace toute alarme existante

Anti-motifs (NE JAMAIS)

  • Un DO global unique gérant toutes les requêtes (goulot d'étranglement)
  • Utiliser blockConcurrencyWhile() à chaque requête (tue le débit)
  • Stocker l'état critique uniquement en mémoire (perdu en cas d'éviction/crash)
  • Utiliser await entre des écritures de stockage liées (casse l'atomicité)
  • Maintenir blockConcurrencyWhile() à travers fetch() ou des I/O externes

Création de stub

// Déterministe - préféré dans la plupart des cas
const stub = env.MY_DO.getByName("room-123");

// À partir d'une chaîne ID existante
const id = env.MY_DO.idFromString(storedIdString);
const stub = env.MY_DO.get(id);

// Nouvel ID unique - stocker le mapping en externe
const id = env.MY_DO.newUniqueId();
const stub = env.MY_DO.get(id);

Opérations de stockage

// SQL (synchrone, recommandé)
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");

Alarmes

// Planifier (remplace l'existant)
await this.ctx.storage.setAlarm(Date.now() + 60_000);

// Gestionnaire
async alarm(): Promise<void> {
  // Traiter le travail planifié
  // Optionnellement rescheduler : await this.ctx.storage.setAlarm(...)
}

// Annuler
await this.ctx.storage.deleteAlarm();

Démarrage rapide du test

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

Skills similaires