Разделение схемы базы данных в Dexie.js на модули опирается на идею изоляции доменных областей, при которой каждая функциональная часть приложения описывает собственные таблицы, индексы и миграции независимо от остальных. Такой подход особенно важен при росте приложения, когда монолитная схема становится источником конфликтов версий, дублирования индексов и сложностей сопровождения.
Монолитное описание схемы в одном месте приводит к нескольким типичным ограничениям:
При использовании Dexie.js эти проблемы проявляются особенно заметно, поскольку схема определяется через версионирование базы и полностью пересоздаётся на этапе upgrade.
Модульный подход предполагает разбиение схемы на независимые фрагменты, каждый из которых отвечает за собственную предметную область:
Каждый модуль экспортирует описание таблиц и индексных полей, а также при необходимости — собственные миграции. Далее эти части агрегируются в единую конфигурацию базы.
Ключевая идея заключается в том, что схема перестаёт быть централизованной сущностью и становится композируемой.
Типовая структура проекта с модульной схемой выглядит следующим образом:
db/
core/
baseDb.ts
modules/
users/
schema.ts
migrations.ts
orders/
schema.ts
migrations.ts
logs/
schema.ts
migrations.ts
composeSchema.ts
Каждый модуль содержит минимум два слоя ответственности:
Каждый модуль определяет только свою часть хранилища:
// db/modules/users/schema.ts
export const usersSchema = {
users: "++id, email, createdAt, role"
};
// db/modules/orders/schema.ts
export const ordersSchema = {
orders: "++id, userId, status, createdAt",
orderItems: "++id, orderId, productId"
};
Такое разделение позволяет локализовать изменения структуры без необходимости редактирования единого файла схемы.
Объединение модулей выполняется через простое слияние объектов:
// db/composeSchema.ts
import { usersSchema } from "./modules/users/schema";
import { ordersSchema } from "./modules/orders/schema";
import { logsSchema } from "./modules/logs/schema";
export const fullSchema = {
...usersSchema,
...ordersSchema,
...logsSchema
};
На уровне Dexie.js итоговая схема передаётся в
version().stores().
import Dexie from "dexie";
import { fullSchema } from "./composeSchema";
export class AppDatabase extends Dexie {
constructor() {
super("app_db");
this.version(1).stores(fullSchema);
}
}
Такой подход сохраняет единый источник правды для runtime, при этом оставляя разработку модульной.
Миграции являются наиболее чувствительной частью схемы. При модульном подходе они также разделяются:
// db/modules/users/migrations.ts
export async function migrateUsersV1ToV2(tx) {
const users = tx.table("users");
for await (const user of users) {
if (!user.role) {
user.role = "user";
await users.put(user);
}
}
}
// db/modules/orders/migrations.ts
export async function migrateOrdersV1ToV2(tx) {
const orders = tx.table("orders");
for await (const order of orders) {
if (!order.status) {
order.status = "pending";
await orders.put(order);
}
}
}
Каждый модуль определяет только собственные изменения данных, не затрагивая остальные таблицы.
Проблема модульного подхода заключается в необходимости синхронизации версий. Dexie.js использует инкрементальные версии базы, поэтому композиция миграций должна учитывать порядок изменений.
Один из распространённых подходов — централизованный реестр версий:
const migrationsV2 = [
migrateUsersV1ToV2,
migrateOrdersV1ToV2
];
И выполнение внутри upgrade:
this.version(2).upgrade(async tx => {
for (const migration of migrationsV2) {
await migration(tx);
}
});
При этом каждый модуль остаётся автономным, но подключается к общей цепочке обновлений.
В более сложных системах допускается динамическая регистрация модулей:
const modules = [
usersModule,
ordersModule,
logsModule
];
const schema = modules.reduce((acc, module) => {
return { ...acc, ...module.schema };
}, {});
И аналогично для миграций:
const migrations = modules.flatMap(m => m.migrations || []);
Такой подход позволяет подключать функциональные блоки без изменения ядра базы данных.
Каждый модуль может содержать дополнительные слои:
В контексте Dexie.js это позволяет обернуть таблицы в доменные репозитории, не смешивая их с глобальной схемой.
Каждый модуль может иметь собственную внутреннюю версию, независимую от версии базы:
export const USERS_MODULE_VERSION = 2;
Сопоставление с версией базы выполняется через таблицу соответствий:
const versionMap = {
1: { users: 1, orders: 1 },
2: { users: 2, orders: 1 }
};
Такой слой абстракции позволяет масштабировать схему без полной переработки истории миграций.
При неправильной организации возникают следующие проблемы:
Dexie.js не накладывает ограничений на архитектуру схемы, поэтому дисциплина модульности полностью определяется структурой кода.
Модульная схема становится промежуточным слоем между бизнес-доменом и физическим хранилищем. В такой архитектуре:
Это позволяет масштабировать структуру данных без централизованных изменений, сохраняя предсказуемость миграций и изоляцию доменов.