Solid Objects apporte le modèle des Durable Objects à Node.js et aux bases SQL
Inspiré du modèle d'acteurs d'état popularisé par Cloudflare, le projet open source Solid Objects permet d'exécuter des objets durables monothread directement sur une base de données classique comme PostgreSQL, MySQL ou SQLite. Disponible sous forme de package TypeScript pour Node.js et le navigateur, cette solution élimine la gestion complexe des verrous et des files de messages.
Fil « Implémentation open source des Durable Objects sur base de données classique »
Pendant longtemps, la gestion d'états répartis complexes exigeait d'associer une base de données, un cache d'état comme Redis, une file de messages et des mécanismes de verrouillage explicites. Cloudflare a popularisé une alternative élégante avec ses Durable Objects (un modèle d'acteurs dans lequel chaque entité possède une identité unique, un état persistant et un traitement séquentiel garanti). Le projet open source Solid Objects (publié sous licence MIT) adapte désormais ce concept sous forme de simple bibliothèque applicative intégrée à un projet Node.js ou Ruby, en appuyant son stockage sur la base de données relationnelle déjà en place (PostgreSQL, MySQL ou SQLite).
Un modèle d'acteurs sur de simples lignes SQL
Dans Solid Objects, chaque entité métier (un panier d'achat, une table de jeu ou une session de réservation) est représentée par une classe dérivée d'un acteur. Lors d'une requête, le message d'action est consigné de façon durable dans la base de données SQL. Un worker prend alors en charge l'acteur au moyen d'un bail temporaire (lease) assorti d'un jeton d'isolement (fencing token, un identifiant séquentiel empêchant un processus ralenti par un arrêt d'exécution de valider une écriture obsolète).
Les appels adressés à une même identité sont exécutés strictement les uns après les autres, interdisant toute corruption d'état concurrentielle. À l'inverse, les transactions destinées à des identités différentes s'exécutent en parallèle. Un acteur inactif ne consomme aucun processus ni démon (daemon, un service tournant en tâche de fond) : son état reste stocké sous forme de simples lignes en base de données.
Un code concis avec planification intégrée
La bibliothèque prend en charge nativement la persistance de l'état, le séquencement des messages et la planification de rappels (reminders ou alarmes). Ces rappels sont stockés dans la même base SQL et survivent aux redémarrages de l'application sans nécessiter de tâche planifiée de type cron. Voici un exemple concret de gestion de vente de billets avec expiration automatique au bout de 10 minutes :
import { Actor, createRuntime } from "solid-objects"
import { sqlite } from "solid-objects/database/sqlite"
class TicketSale extends Actor {
static override readonly actorType = "TicketSale"
remaining = 100
holds: Record<string, number> = {}
reserve({ buyer }: { buyer: string }): boolean {
if (this.remaining === 0 || buyer in this.holds) return false
this.remaining -= 1
this.holds = { ...this.holds, [buyer]: Date.now() }
this.schedule({ at: new Date(Date.now() + 600_000), key: buyer }).expire!({ buyer })
return true
}
expire({ buyer }: { buyer: string }): void {
if (!(buyer in this.holds)) return
const rest = { ...this.holds }
delete rest[buyer]
this.holds = rest
this.remaining += 1
}
}
Du serveur au navigateur avec SQLite WASM
Depuis la version 0.14, Solid Objects permet d'exécuter l'intégralité du runtime (gestionnaire de boîte de réception, baux et alarmes) directement au sein du navigateur web. Le stockage repose sur SQLite WASM (la version du moteur SQLite compilée en WebAssembly) couplée à l'API OPFS (Origin Private File System, le système de fichiers privé persistant du navigateur). L'élection d'un onglet maître gérant la base de données est orchestrée via l'API Web Locks et les communications inter-onglets passent par un canal BroadcastChannel.
Pour les architectures favorisant le fonctionnement hors-ligne (offline-first), la méthode transmit() permet de planifier des appels réseau sortants dans la même transaction atomique que la modification d'état locale. Une fois la connexion rétablie, un travailleur de transmission livre les messages au serveur backend Node.js ou Rails de manière ordonnée et avec une garantie de livraison d'au moins une fois (at-least-once delivery).
En réunissant la gestion de la concurrence, de la persistance et du temps au niveau applicatif, Solid Objects simplifie considérablement l'outillage backend traditionnel. Une démonstration rapide simulant 25 appels concurrents est exécutable directement via la commande npm exec --yes --package=solid-objects@latest -- solid-objects quickstart.