wandres.dev
DURABLE OBJECTS · el modelo

Qué es un Durable Object: el actor con identidad

Un Worker es efímero y sin estado: nace con la petición, responde y muere, y corre a la vez en cientos de ubicaciones. Un Durable Object es lo contrario: una instancia única, direccionable por un nombre y con estado consistente que vive en un solo lugar del mundo. Es cómputo, almacenamiento e identidad fundidos en un objeto vivo, el actor que posee su estado y atiende sus mensajes de uno en uno. Vemos por qué el Worker no recuerda, qué significa que un objeto sea único en todo el planeta, cómo el modelo de actor reordena el problema del estado y por qué no sustituye a un almacén sino que lo corona.

⏱ 20 min

Todo lo que has visto hasta aquí celebra que un Worker no tenga estado: por eso arranca sin cold start, por eso escala a millones de peticiones, por eso corre a la vez cerca de cada usuario. Pero esa misma virtud es una amnesia. Un Worker no recuerda nada entre una petición y la siguiente, y hay problemas —una sala de chat, un contador exacto, un cerrojo— que son pura memoria compartida. El Durable Object es la respuesta de Cloudflare a esa carencia: no un almacén al que consultas, sino un objeto vivo, único en todo el mundo, con nombre propio y con estado que persiste. Es el actor que posee su estado y lo defiende.

🎯 Al terminar esta lección sabrás
  • Definir un Durable Object como una instancia única y direccionable globalmente con estado consistente.
  • Contrastar el Worker efímero y sin estado con el Durable Object con identidad y memoria.
  • Reconocer el modelo de actor: un objeto que posee su estado privado y atiende sus mensajes.
  • Situar el almacenamiento del objeto en ctx.storage y su backend SQLite, ya disponible en el plan Free.

El Worker no recuerda

Repasa el modelo que ya dominas. Un Worker es una función que la plataforma instancia por petición: nace, lee su entrada, produce una respuesta y se descarta. No hay un “entre peticiones” donde guardar nada, y aunque lo hubiera, la petición siguiente podría atenderla otro isolate en otro continente. El estado, si existe, vive fuera: en KV, en D1, en R2. El Worker es puro tránsito.

Esa arquitectura sin estado es la fuente de casi todas sus virtudes, pero deja un hueco con forma exacta. Hay problemas que no son “consultar un dato” sino “coordinar en un punto”: dos personas editando el mismo documento, un marcador que varias peticiones incrementan a la vez, una sala de juego donde todos ven el mismo tablero, un cerrojo que solo uno puede sostener. Todos comparten una esencia: necesitan un lugar único donde converjan y donde el estado sea consistente.

Un almacén de lecturas globalmente replicado no lo da, porque su fuerza —estar en todas partes— es justo lo contrario de lo que aquí hace falta: estar en un solo sitio, y que ese sitio decida el orden en que ocurren las cosas. Cuando el problema es coordinar, la replicación deja de ser una ventaja y se vuelve el obstáculo, porque no hay un árbitro que diga quién va primero.

Una instancia única en todo el mundo

Un Durable Object invierte cada supuesto del Worker. Donde el Worker es efímero, el objeto persiste; donde el Worker corre en cientos de copias, el objeto es uno solo; donde el Worker es anónimo, el objeto tiene nombre. Defines una clase que extiende DurableObject, y cada instancia identificada por un nombre es única en todo el planeta: da igual desde qué continente la invoques, siempre aterrizas en la misma, con su misma memoria.

import { DurableObject } from "cloudflare:workers";

export class Contador extends DurableObject {
  async incrementar(): Promise<number> {
    const actual = (await this.ctx.storage.get<number>("v")) ?? 0;
    const nuevo = actual + 1;
    await this.ctx.storage.put("v", nuevo);
    return nuevo;
  }
}

Una clase no es nada hasta que la plataforma sabe de ella. La declaras en wrangler.jsonc con un binding —el namespace por el que la alcanzarás— y una migración que registra la clase y estrena su backend SQLite:

{
  "durable_objects": {
    "bindings": [{ "name": "CONTADOR", "class_name": "Contador" }]
  },
  "migrations": [
    { "tag": "v1", "new_sqlite_classes": ["Contador"] }
  ]
}

El binding es el nombre por el que el objeto aparecerá en env; la migración con new_sqlite_classes le dice a la plataforma que esa clase usa el backend SQLite. Sin esas dos piezas, la clase es solo código muerto que nadie puede invocar.

Mira lo que trae de serie. this.ctx es el contexto del objeto, y this.ctx.storage es su almacenamiento privado y transaccional: nadie más lo toca, y sobrevive a reinicios y despliegues. this.env le da los mismos bindings que a un Worker. La clase reúne en una sola pieza las tres cosas que antes vivían separadas —el cómputo, el estado y la identidad—, y esa fusión es toda la idea.

Fíjate también en lo que no aparece: ninguna cadena de conexión, ningún cliente de base de datos, ningún pool. El estado del objeto está literalmente a su lado, y accede a él como quien lee una variable. Esa cercanía no es cosmética: es lo que le permite razonar sobre su propio estado sin pagar un viaje de red por cada lectura.

Y hay una economía elegante detrás. El objeto no consume recursos mientras nadie lo usa: cuando queda inactivo, la plataforma lo hiberna —libera su memoria— y lo despierta en la siguiente llamada reconstruyendo su estado desde el almacenamiento. Para ti es transparente; escribes como si el objeto viviera siempre, y la plataforma se ocupa de que solo ocupe memoria cuando de verdad trabaja.

ℹ️
SQLite de serie, y también en el plan Free

Desde 2026, el almacenamiento de un Durable Object se apoya por defecto en un backend SQLite embebido en el propio objeto: ctx.storage sigue ofreciendo la interfaz clave-valor de siempre, pero por debajo hay una base de datos relacional consultable con SQL, coubicada con el cómputo. Y lo más importante para aprender: los Durable Objects con backend SQLite ya no son exclusivos de los planes de pago. Están disponibles también en el plan Free, con un margen de 100K peticiones al día, así que puedes construir salas, contadores y cerrojos reales sin tarjeta.

El actor: estado con identidad

El patrón tiene un nombre con medio siglo de historia: el modelo de actor. Un actor es una entidad que encapsula un estado privado y se comunica con el mundo solo a través de mensajes; nadie lee ni escribe su interior directamente, sino que le pide cosas y él responde a su ritmo. Un Durable Object es exactamente eso llevado al edge: sala-42 no es una fila en una tabla que cualquiera modifica, es un objeto que posee el estado de esa sala y decide qué hacer con cada mensaje que le llega.

De ahí sale la propiedad que da nombre a todo: es direccionable. Cualquier Worker, en cualquier parte, puede nombrar el objeto sala-42 y la plataforma lo enruta a la única instancia que lleva ese nombre. No hay un registro central que consultar ni una elección de líder que negociar: el nombre es la dirección, y la dirección es única. Convergen en un punto sin haberse puesto de acuerdo, porque todos calculan la misma identidad a partir del mismo nombre.

🎯

Identidad

Cada objeto tiene un nombre propio y estable. Nombrar sala-42 desde cualquier lugar te lleva siempre a la misma instancia, sin registro central que consultar.

🔒

Estado consistente

Su almacenamiento es privado y transaccional. Lo que el objeto escribe, lo vuelve a leer tal cual; nadie más pisa su memoria.

📍

Un solo lugar

A diferencia de un Worker que corre en todas partes, el objeto vive en una ubicación física concreta y todas las llamadas convergen allí.

🎭

Un mensaje a la vez

El objeto atiende sus peticiones de una en una. Esa serialización, que veremos en detalle, elimina de raíz las condiciones de carrera.

flowchart TB
W1[Worker en Madrid] --> DO
W2[Worker en Tokio] --> DO
W3[Worker en Lima] --> DO
DO[Durable Object sala-42] --> ST[storage privado]
style DO fill:#cba6f7,color:#11111b
style ST fill:#a6e3a1,color:#11111b

Ese dibujo es la tesis entera. Tres Workers en tres continentes, todos sin estado, todos convergiendo en un único objeto que sí lo tiene. El Worker resuelve “cerca del usuario”; el Durable Object resuelve “un punto de verdad con nombre”. Juntos cierran el círculo que la plataforma llevaba abriendo desde el primer nivel.

Hablarle es tan simple como calcular su identidad a partir de un nombre y pedirle un método; la plataforma enruta la llamada a la única instancia que lo lleva, esté donde esté:

const id = env.CONTADOR.idFromName("global");
const stub = env.CONTADOR.get(id);
const total = await stub.incrementar(); // llega al objeto unico

Dedicaremos lecciones enteras a cómo se acuña esa identidad y cómo viaja la llamada. Por ahora quédate con la forma: nombras, obtienes un stub y le hablas como a un objeto local, aunque viva a un océano de distancia.

El objeto no sustituye al almacén

Un error común al conocer los Durable Objects es tratarlos como una base de datos más y preguntarse cuál “gana”. No compiten: responden preguntas distintas. KV responde “dónde guardo datos que muchos leen igual y en todas partes”; D1 responde “dónde consulto con SQL un conjunto grande de datos”; R2 responde “dónde pongo archivos sin pagar egress”. El Durable Object responde otra: “dónde coordino, con consistencia, una porción concreta de estado que muchos tocan a la vez”.

Por eso el objeto a menudo se apoya en el almacenamiento en lugar de reemplazarlo. Es el coordinador que serializa las escrituras de una entidad —una sala, un usuario— y persiste en su propio SQLite, o incluso orquesta escrituras hacia D1. La regla práctica: si el dato es de lectura masiva y compartida, KV; si necesitas consultas relacionales sobre mucho volumen, D1; si necesitas un punto único que ponga orden en las escrituras de una entidad, un Durable Object.

Un ejemplo lo fija. Imagina un marcador de partido en vivo: el estado que cambia sin parar —el resultado, quién acaba de anotar— vive en un Durable Object por partido, que serializa cada punto y garantiza que nadie vea una cuenta a medias. Pero el histórico de miles de partidos ya cerrados, que solo se lee y se filtra, vive en D1. Coordinación caliente en el objeto, consulta fría en el almacén: la misma aplicación usa las dos piezas para lo que cada una hace mejor.

📝
Un objeto por entidad, nunca uno solo para todo

La tentación del principiante es crear un único Durable Object global que lo coordine todo. Es un antipatrón: como el objeto es de un hilo, ese único objeto se convierte en el cuello de botella de tu aplicación entera. El arte está en elegir bien el grano —la entidad por la que particionas— para que existan millones de objetos pequeños e independientes en vez de uno gigante y saturado. Nombrar bien es repartir la carga.

El Durable Object es la pieza que le faltaba al edge para tener memoria

Durante años, la promesa del edge cargó con una tensión no resuelta. El cómputo se volvió ubicuo y barato —un isolate cerca de cada usuario, sin cold start— pero el estado se quedó atrás, empujado siempre hacia afuera: a un almacén global de lecturas eventualmente consistentes, a una base de datos regional que reintroducía la latencia que el edge prometía abolir. Podías estar a veinte milisegundos del usuario y a ciento cincuenta de la única copia de la verdad. El Durable Object disuelve esa tensión con un movimiento conceptual audaz: en lugar de replicar el estado en todas partes y rezar por la consistencia, coloca una sola copia con nombre y hace que el mundo venga a ella. Es la inversión de la CDN. Una CDN acerca datos inmutables a todos; un Durable Object crea un punto de estado mutable al que todos se dirigen. Y al fundir en un mismo objeto el cómputo, el almacenamiento y la identidad, hace desaparecer la distancia entre la lógica y los datos que gobierna: el estado no está “en la base de datos a la que el servidor llama”, está dentro del actor que lo razona. Esta es la idea que hay que dejar asentar antes de tocar una línea más de código, porque reordena la pregunta fundamental del diseño distribuido. Deja de ser “cómo mantengo consistentes muchas copias de este dato” —el problema más difícil de la informática— y pasa a ser “cuál es la unidad natural de coordinación de mi dominio, y qué nombre le doy”. Una sala, un usuario, un documento, un pedido: si puedes nombrar la cosa que necesita un punto único de verdad, puedes darle un Durable Object, y la consistencia deja de ser una batalla y se vuelve una consecuencia de la identidad. Quien interioriza esto deja de ver una base de datos exótica y empieza a ver lo que de verdad es: la forma que toma el estado cuando se le da identidad y el cómputo vive en todas partes.

⚔️ Nombra tu primer actor
  1. Escribe una clase Contador que extienda DurableObject con un método incrementar que lea, sume uno y guarde en ctx.storage. Despliégalo y compruébalo desde un Worker.
  2. Enumera tres problemas de tu dominio que sean “coordinar en un punto” y no “consultar un dato”. Para cada uno, propón el nombre que tendría su objeto.
  3. Explica con tus palabras por qué un almacén global de lecturas, bueno para servir lo mismo a todos, es la herramienta equivocada para un contador exacto.
  4. Argumenta por qué fundir cómputo, estado e identidad en un solo objeto cambia la pregunta de “cómo mantengo copias consistentes” a “cuál es mi unidad de coordinación”.