Adaptateurs de base de données

Comprenez le contrat de persistance collaborative et choisissez entre Memory, SQLite et un Database Adapter personnalisé.

Un Database Adapter persiste les données collaboratives centrales utilisées par Collaboration Service. Les développeurs peuvent implémenter IDatabaseAdapter pour connecter toute base capable de satisfaire son contrat comportemental complet. Le Service gère la sémantique des Units, de l’OT et des révisions ; l’Adapter la traduit en opérations de persistance atomiques et sûres à réessayer.

Vérifier la persistance

Arrêtez Quick Start puis lancez pnpm example:database-adapter depuis la racine. Ouvrez http://127.0.0.1:3010/?unit=persistent-sheet, modifiez une cellule et vérifiez-la dans un second navigateur. Arrêtez, relancez la même commande et rouvrez la même URL : la valeur doit rester. Le fichier est examples/database-adapter/.data/collaboration.sqlite ; ne le supprimez pas entre les lancements. Le serveur lit le Unit existant et appelle createUnitFromData() seulement pour UNIT_NOT_FOUND. Voir server/main.ts.

Données stockées par l’Adapter

L’Adapter central stocke :

  • l’enregistrement Unit et sa révision de tête ;
  • les instantanés révisionnés ;
  • les jeux de modifications confirmés ;
  • les blocs référencés par les instantanés Sheet ;
  • les enregistrements d’idempotence1 indexés par (unitID, sid, reqId) ;
  • l’état de suppression logique, de restauration et de suppression définitive des Units.

Par défaut, il ne stocke ni utilisateurs, ni rôles, ni ACL, ni répertoires, ni fichiers, ni métadonnées produit. History, Thread Comment et Worktree possèdent des contrats Adapter indépendants.

Sémantique requise

IDatabaseAdapter dépasse une interface CRUD. L’implémentation doit respecter les garanties de correction attendues par Collaboration Service :

ContratSignification
Création atomiqueL’instantané initial et les blocs Sheet deviennent visibles ensemble, ou pas du tout
CAS2 de révisionLa soumission suivante est confirmée seulement si la révision de tête correspond à la révision attendue
Idempotence des soumissionsRéessayer le même (unitID, sid, reqId) renvoie le même résultat déterminé
Modifications contiguësLes jeux de modifications confirmés et la révision Unit restent contigus
Visibilité des instantanésUn instantané n’est lisible qu’après stockage de toutes ses dépendances
Cycle de vie atomiqueToutes les Units d’un lot de suppression ou de restauration réussissent, ou aucune
Marqueur de suppression définitiveL’ID d’une Unit supprimée définitivement ne peut pas être recréé

Traduire chaque méthode en simple lecture ou écriture sans ces garanties de concurrence et d’atomicité casse l’OT et la restauration.

Memory Adapter

@univerjs-pro/collaboration-database-memory est une implémentation en mémoire du processus adaptée :

  • aux tests unitaires et d’intégration ;
  • aux exemples officiels ;
  • aux environnements de développement temporaires ;
  • à la validation rapide de Middleware personnalisés ou de l’assemblage client.
TypeScript
import { MemoryDatabaseAdapter } from "@univerjs-pro/collaboration-database-memory";import { UniverCollabService } from "@univerjs-pro/collaboration-service";const database = new MemoryDatabaseAdapter();const service = new UniverCollabService({ dbAdapter: database });

Memory Adapter respecte les mêmes contrats de révision, de CAS et d’idempotence que les implémentations persistantes, mais toutes les données résident dans le processus Node.js courant et disparaissent à son arrêt.

SQLite Adapter

@univerjs-pro/collaboration-database-sqlite est une implémentation SQLite persistante pour le développement, les tests, les applications locales et les usages à petite échelle.

TypeScript
import { mkdir } from "node:fs/promises";import { SQLiteDatabaseAdapter } from "@univerjs-pro/collaboration-database-sqlite";import { UniverCollabService } from "@univerjs-pro/collaboration-service";await mkdir("./data", { recursive: true });const database = new SQLiteDatabaseAdapter({  filename: "./data/collaboration.sqlite",  busyTimeoutMs: 5_000,});const service = new UniverCollabService({ dbAdapter: database });

SQLite Adapter utilise des clés étrangères et des transactions d’écriture BEGIN IMMEDIATE pour les commits atomiques. Il crée le schéma courant dans une base vide. Il refuse les schémas incomplets ou non pris en charge au lieu d’appliquer automatiquement des migrations inconnues.

Il ne modifie pas journal_mode et ne décide ni de l’emplacement du fichier, ni de la politique de sauvegarde, ni des autres réglages SQLite. SQLite vise le développement, les applications locales et la petite échelle.

Database Adapters personnalisés

Un Adapter personnalisé convient généralement si :

  • l’application utilise déjà PostgreSQL, MySQL ou une autre base métier ;
  • les soumissions collaboratives doivent s’intégrer à des mécanismes de base propres à l’application ;
  • les exigences d’exploitation ou de conformité dépassent le périmètre prévu de SQLite.

Implémentez IDatabaseAdapter et injectez-le dans le Service comme un Adapter intégré :

TypeScript
const database = new ApplicationCollaborationDatabase({ pool });const service = new UniverCollabService({ dbAdapter: database });

Le Service ne dépend pas d’une base particulière, mais ne peut pas ajouter transactions, CAS ou déduplication à votre implémentation. L’Adapter doit réaliser le contrat complet avec les capacités de sa base.

Context et frontière applicative

Chaque appel Service transmet son customData aux Middleware, au Database Adapter et aux Events associés, sans persistance automatique. Les décisions d’autorisation restent généralement dans les Middleware Service plutôt que d’être répétées dans chaque méthode Adapter :

text
Middleware Service  → autorise ou refuse l’opération métier  → Database Adapter  → stocke atomiquement l’état collaboratif faisant autorité

Memory, SQLite et les Adapters personnalisés peuvent ainsi partager les mêmes politiques applicatives.

Si les données métier doivent rester fortement cohérentes avec les données collaboratives, un Database Adapter personnalisé peut lire les valeurs nécessaires dans customData et écrire les deux ensembles dans sa transaction de base de données.

Si elles n’ont pas besoin de partager cette transaction, les Middleware ou Events peuvent les écrire dans d’autres tables ou systèmes externes. Ils ne partagent pas par défaut la transaction interne de l’Adapter. Les Events sont des notifications locales au processus, fournies au mieux après la réussite du commit collaboratif. L’application assume l’idempotence, les nouvelles tentatives et les compensations nécessaires.

Stockage des modules d’extension

History, Thread Comment et Worktree définissent des interfaces Database Adapter indépendantes. Leurs Adapters peuvent partager une base ou utiliser des stockages distincts. Une seule implémentation personnalisée peut aussi fournir plusieurs interfaces Adapter et être injectée dans les Services correspondants.

Ces interfaces étant indépendantes, le SDK ne fournit pas par défaut de transactions intermodules. Même avec une base partagée, la stratégie de cohérence intermodules appartient à l’application.

Choisir un Adapter

AdapterUsage adaptéContrainte principale
MemoryTests, exemples, développement temporaireDonnées perdues à l’arrêt du processus
SQLiteDéveloppement, tests, applications locales, petite échelleBase monofichier dont l’application gère les réglages
PersonnaliséBases existantes ou intégration métier spécifiqueL’application doit implémenter et vérifier le contrat complet

L’application possède chaque Adapter injecté. Libérez le Service avant l’Adapter :

TypeScript
await service.dispose();await database.dispose();

Consultez l’exemple Database Adapter exécutable pour comparer les implémentations.

Footnotes

  1. L’idempotence permet de réessayer une soumission sans appliquer deux fois ses modifications. Conservez le même identifiant : un contenu identique avec un nouvel identifiant constitue une nouvelle soumission. ↩

  2. Le CAS ne modifie une valeur que si elle correspond encore à la valeur attendue. La vérification de la révision et l’écriture suivante doivent être atomiques pour empêcher deux soumissions concurrentes de valider la même révision. ↩

Que pensez-vous de ce guide ?

© 2026 DreamNum Co., Ltd.