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
- Modéliser autour d'atomes de coordination - Un DO par salon/jeu/utilisateur, pas un DO global unique
- Utiliser
getByName()pour le routage déterministe - Même entrée = même instance DO - Utiliser le stockage SQLite - Configurer
new_sqlite_classesdans les migrations - Initialiser dans le constructeur - Utiliser
blockConcurrencyWhile()uniquement pour la configuration du schéma - Utiliser les méthodes RPC - Pas le gestionnaire fetch() (date de compatibilité >= 2024-04-03)
- Persister en premier, mettre en cache ensuite - Toujours écrire dans le stockage avant de mettre à jour l'état en mémoire
- 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
awaitentre des écritures de stockage liées (casse l'atomicité) - Maintenir
blockConcurrencyWhile()à traversfetch()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);
});
});