Adaptateurs de base de données
Comprenez le contrat de persistance collaborative et choisissez entre Memory, SQLite, PostgreSQL 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.
Les Database Adapters sont open source sous Apache-2.0 ; le code se trouve dans univer-collaboration-sdk-community, avec les mêmes noms de paquet et versions que les autres paquets.
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 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 :
| Contrat | Signification |
|---|---|
| Création atomique | L’instantané initial et les blocs Sheet deviennent visibles ensemble, ou pas du tout |
| CAS2 de révision | La soumission suivante est confirmée seulement si la révision de tête correspond à la révision attendue |
| Idempotence des soumissions | Réessayer le même (unitID, sid, reqId) renvoie le même résultat déterminé |
| Modifications contiguës | Les jeux de modifications confirmés et la révision Unit restent contigus |
| Visibilité des instantanés | Un instantané n’est lisible qu’après stockage de toutes ses dépendances |
| Cycle de vie atomique | Toutes les Units d’un lot de suppression ou de restauration réussissent, ou aucune |
| Marqueur de suppression définitive | L’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 environnements de développement temporaires ;
- à la validation rapide de Middleware personnalisés ou de l’assemblage client.
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.
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 });Il initialise le schéma dans une base vide et ne fait que vérifier une base existante. L’emplacement du fichier, les sauvegardes et les autres réglages SQLite relèvent de l’application.
PostgreSQL Adapter
@univerjs-pro/collaboration-database-postgres est une implémentation persistante basée sur node-postgres que plusieurs nœuds de collaboration peuvent partager.
import { PostgresDatabaseAdapter } from "@univerjs-pro/collaboration-database-postgres";import { UniverCollabService } from "@univerjs-pro/collaboration-service";import { Pool } from "pg";const pool = new Pool({ connectionString: process.env.DATABASE_URL });const database = await PostgresDatabaseAdapter.create({ pool });const service = new UniverCollabService({ dbAdapter: database });L’application possède le pool et le ferme après avoir libéré le Service. create() initialise le schéma collaboration dans une base vide et ne fait que vérifier un schéma existant.
Database Adapters personnalisés
Un Adapter personnalisé convient généralement si :
- l’application utilise déjà 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 des Adapters intégrés.
Implémentez IDatabaseAdapter et injectez-le dans le Service comme un Adapter intégré :
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. Les implémentations intégrées du dépôt communautaire montrent le même contrat sur différentes bases et servent de référence.
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 :
Middleware Service → autorise ou refuse l’opération métier → Database Adapter → stocke atomiquement l’état collaboratif faisant autoritéMemory, SQLite, PostgreSQL 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, Worktree et File 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
| Adapter | Usage adapté | Contrainte principale |
|---|---|---|
| Memory | Tests, exemples, développement temporaire | Données perdues à l’arrêt du processus |
| SQLite | Développement, tests, applications locales, petite échelle | Base monofichier dont l’application gère les réglages |
| PostgreSQL | Déploiements multi-nœuds et bases PostgreSQL existantes | L’application gère le pool et l’exploitation de la base |
| Personnalisé | Bases existantes ou intégration métier spécifique | L’application doit implémenter et vérifier le contrat complet |
L’application possède chaque Adapter injecté. Libérez le Service avant l’Adapter :
await service.dispose();await database.dispose();Consultez l’exemple Database Adapter exécutable pour comparer les implémentations.
Footnotes
-
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. ↩
-
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 ?