Адаптеры баз данных

Изучите контракт хранения данных совместной работы и выберите Memory, SQLite или собственный Database Adapter.

Database Adapter сохраняет основные данные совместной работы, используемые Collaboration Service. Разработчик может реализовать IDatabaseAdapter для любой базы, способной выполнить полный контракт поведения. Service отвечает за семантику Unit, OT и ревизий. Adapter превращает её в атомарные операции хранения, безопасные при повторе.

Проверка сохранения после перезапуска

Остановите Quick Start и выполните pnpm example:database-adapter из корня. Откройте http://127.0.0.1:3010/?unit=persistent-sheet, измените ячейку и проверьте её в другом браузере. Остановите сервер, повторите команду и откройте тот же URL: значение должно сохраниться. Файл — examples/database-adapter/.data/collaboration.sqlite; не удаляйте его между запусками. Сервер читает существующий Unit и вызывает createUnitFromData() только при UNIT_NOT_FOUND. См. server/main.ts.

Что хранит Adapter

Основной Adapter совместной работы хранит:

  • запись Unit и текущую головную ревизию;
  • снимки с ревизиями;
  • подтверждённые наборы изменений;
  • блоки, на которые ссылаются снимки Sheet;
  • записи идемпотентности1 отправок с ключом (unitID, sid, reqId);
  • состояние логического удаления, восстановления и окончательного удаления Unit.

По умолчанию он не хранит пользователей, роли, ACL, каталоги, файлы и метаданные продукта. History, Thread Comment и Worktree используют собственные независимые контракты Adapter.

Обязательная семантика

IDatabaseAdapter — не просто CRUD-интерфейс. Реализация должна сохранять гарантии корректности, ожидаемые Collaboration Service:

КонтрактЗначение
Атомарное созданиеНачальный снимок и блоки Sheet становятся видимыми вместе либо не становятся вовсе
CAS2 ревизииСледующая отправка подтверждается лишь при совпадении головной ревизии с ожидаемой
Идемпотентность отправкиПовтор одного (unitID, sid, reqId) возвращает тот же определённый результат
Непрерывные измененияПодтверждённые наборы изменений и ревизия Unit остаются непрерывными
Видимость снимковСнимок доступен для чтения лишь после сохранения всех зависимостей
Атомарный жизненный циклВсе Units пакета удаления или восстановления обрабатываются успешно либо ни одна
Метка окончательного удаленияОкончательно удалённый ID Unit нельзя создать заново

Простое сопоставление методов обычным операциям чтения и записи без этой семантики конкурентности и атомарности нарушит OT и восстановление.

Memory Adapter

@univerjs-pro/collaboration-database-memory — реализация в памяти процесса для:

  • модульных и интеграционных тестов;
  • официальных примеров;
  • временных сред разработки;
  • быстрой проверки собственных Middleware или сборки клиента.
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 соблюдает те же контракты ревизий, CAS и идемпотентности, что и постоянные реализации, но все данные находятся в текущем процессе Node.js и теряются при его завершении.

SQLite Adapter

@univerjs-pro/collaboration-database-sqlite — постоянное хранилище SQLite для разработки, тестов, локальных приложений и небольших нагрузок.

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 использует внешние ключи и транзакции записи BEGIN IMMEDIATE для атомарных коммитов. В пустой базе он создаёт актуальную схему. Неполные или неподдерживаемые схемы он отказывается открывать, а не применяет неизвестные миграции автоматически.

Он не меняет journal_mode и не определяет расположение файла, политику резервного копирования и другие настройки SQLite. SQLite предназначен для разработки, локальных приложений и небольших масштабов.

Собственные Database Adapters

Собственный Adapter обычно подходит, когда:

  • приложение уже использует PostgreSQL, MySQL или другую бизнес-базу;
  • отправки совместной работы должны интегрироваться со специфическими механизмами базы приложения;
  • эксплуатационные или нормативные требования выходят за предполагаемый масштаб SQLite.

Реализуйте IDatabaseAdapter и внедрите его в Service так же, как встроенный Adapter:

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

Service не зависит от конкретной базы, но и не может добавить транзакции, CAS или дедупликацию в вашу реализацию. Adapter должен выполнить полный контракт средствами своей базы.

Context и граница приложения

Каждый вызов Service передаёт customData в Middleware, Database Adapter и связанные Events, но SDK не сохраняет его автоматически. Решения об авторизации обычно остаются в Middleware Service, а не повторяются в методах Adapter:

text
Middleware Service  → разрешает или отклоняет бизнес-операцию  → Database Adapter  → атомарно сохраняет эталонное состояние совместной работы

Так Memory, SQLite и собственные Adapters используют общие политики приложения.

Если бизнес-данным нужна строгая согласованность с данными совместной работы, собственный Database Adapter может прочитать значения из customData и записать оба набора в своей транзакции базы данных.

Если общая транзакция не нужна, Middleware или Events могут записать бизнес-данные в дополнительные таблицы или внешние системы. По умолчанию они не разделяют внутреннюю транзакцию Adapter. Events — локальные для процесса уведомления с доставкой по возможности, испускаемые после успешного коммита. Необходимую идемпотентность, повторы и компенсации реализует приложение.

Хранение расширений

History, Thread Comment и Worktree определяют независимые интерфейсы Database Adapter. Их Adapters могут использовать одну базу или разные хранилища. Одна собственная реализация также может предоставлять несколько интерфейсов Adapter и внедряться в соответствующие Services.

Из-за независимости интерфейсов SDK по умолчанию не предоставляет межмодульных транзакций. Даже при общей базе стратегию межмодульной согласованности определяет приложение.

Выбор Adapter

AdapterПодходящие задачиОсновное ограничение
MemoryТесты, примеры, временная разработкаДанные исчезают при завершении процесса
SQLiteРазработка, тесты, локальные приложения, небольшой масштабОднофайловая база с настройками под контролем приложения
СобственныйСуществующие базы или особая интеграция бизнес-данныхПриложение реализует и проверяет полный контракт

Приложение владеет всеми внедрёнными Adapters. Освободите Service раньше Adapter:

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

Сравнить работающие реализации можно в примере Database Adapter.

Footnotes

  1. Идемпотентность позволяет повторить отправку без повторного применения изменений. Сохраняйте идентификатор отправки: те же данные с новым идентификатором считаются новой отправкой. ↩

  2. CAS обновляет значение, только если оно совпадает с ожидаемым. Проверка текущей ревизии и запись следующего состояния должны быть атомарными, чтобы два параллельных запроса не подтвердили одну ревизию. ↩

Насколько полезно это руководство?

© 2026 DreamNum Co., Ltd.