Использование нескольких экземпляров в одном приложении

Библиотека localForage предоставляет единый асинхронный API поверх различных механизмов хранения браузера (IndexedDB, WebSQL, localStorage). Одной из ключевых возможностей архитектуры является поддержка нескольких независимых экземпляров, позволяющих логически и физически разделять данные внутри одного приложения.

Каждый экземпляр представляет собой изолированную конфигурацию, в которой задаются параметры хранения: имя базы данных, имя хранилища (storeName), предпочтительные драйверы и другие настройки. Это позволяет строить модульные системы хранения без пересечения ключей и без риска коллизий.


Базовый принцип множественных экземпляров

По умолчанию библиотека использует глобальный singleton:

  • один набор настроек
  • одно пространство ключей
  • единая конфигурация драйвера

Однако при использовании метода создания экземпляра появляется возможность выделить отдельные «контексты хранения»:

  • независимые namespaces
  • собственные конфигурации драйверов
  • изолированные ключи
  • отдельные внутренние базы IndexedDB

Каждый экземпляр работает как автономный слой поверх браузерного storage API.


Создание экземпляра через createInstance

Основной механизм изоляции реализован через метод createInstance:

import localForage from "localforage";

const userStore = localForage.createInstance({
  name: "app_db",
  storeName: "users"
});

const settingsStore = localForage.createInstance({
  name: "app_db",
  storeName: "settings"
});

Ключевые параметры

name Определяет имя базы данных (в IndexedDB) или namespace для других драйверов.

storeName Логическое разделение внутри одной базы данных. Фактически формирует отдельный object store.

driver Позволяет задать предпочтительный механизм хранения:

driver: localForage.INDEXEDDB

Архитектурная модель изоляции

При использовании нескольких экземпляров важно понимать внутреннюю структуру:

Уровень 1: База данных (name)

Определяет физическую базу IndexedDB:

  • app_db
  • cache_db
  • auth_db

Каждая база существует независимо.

Уровень 2: Store (storeName)

Внутри одной базы создаются отдельные хранилища:

  • users
  • sessions
  • cache

Уровень 3: Key-value пространство

Каждый store содержит набор ключей:

users:
  1 -> { ... }
  2 -> { ... }

settings:
  theme -> "dark"

Изоляция данных между модулями приложения

Использование нескольких экземпляров особенно эффективно при модульной архитектуре.

Пример разделения ответственности

const authStorage = localForage.createInstance({
  name: "app",
  storeName: "auth"
});

const profileStorage = localForage.createInstance({
  name: "app",
  storeName: "profile"
});

const cacheStorage = localForage.createInstance({
  name: "app",
  storeName: "cache"
});

Такое разделение позволяет:

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

Поведение драйверов в разных экземплярах

Несмотря на логическую изоляцию, драйверы остаются общими для всего приложения. Однако каждый экземпляр может иметь собственный приоритет выбора.

Приоритет драйвера

localForage.createInstance({
  name: "app",
  storeName: "fast_cache",
  driver: [
    localForage.INDEXEDDB,
    localForage.LOCALSTORAGE
  ]
});

Каждый экземпляр:

  • выполняет независимую инициализацию
  • проверяет доступные драйверы отдельно
  • может выбрать fallback независимо от других

Особенности IndexedDB и физическая изоляция

При использовании IndexedDB:

  • name становится именем базы данных
  • storeName — именем object store

Важно учитывать:

  • разные экземпляры с разными storeName внутри одной name используют одну физическую базу
  • разные name создают полностью независимые базы

Это влияет на:

  • производительность открытия соединений
  • миграции структуры
  • конкурентный доступ

Сценарии использования нескольких экземпляров

1. Разделение доменных данных

Типичная архитектура приложения:

  • user data
  • session data
  • UI preferences
  • cache layer

Каждый слой изолирован.


2. Изоляция временного кеша

const tempCache = localForage.createInstance({
  name: "app",
  storeName: "temp_cache"
});

Позволяет:

  • очищать кеш без риска удаления пользовательских данных
  • применять TTL-логику отдельно

3. Multi-tenant архитектура

Для SaaS приложений:

function createTenantStorage(tenantId) {
  return localForage.createInstance({
    name: `tenant_${tenantId}`,
    storeName: "data"
  });
}

Каждый клиент получает:

  • собственную базу
  • полную изоляцию данных
  • независимые миграции

4. Версионирование схемы данных

Разделение экземпляров по версиям:

const v1Store = localForage.createInstance({
  name: "app_v1",
  storeName: "data"
});

const v2Store = localForage.createInstance({
  name: "app_v2",
  storeName: "data"
});

Используется при:

  • постепенных миграциях
  • A/B тестировании структур хранения
  • обратной совместимости

Поведение ключей и коллизии

Ключи изолированы только внутри store, но не между экземплярами с одинаковыми параметрами.

Сценарий коллизии

const a = localForage.createInstance({
  name: "app",
  storeName: "data"
});

const b = localForage.createInstance({
  name: "app",
  storeName: "data"
});

Оба экземпляра:

  • работают с одной и той же областью
  • читают и пишут одни и те же ключи

Очистка данных и управление жизненным циклом

Каждый экземпляр поддерживает собственные операции очистки:

await cacheStorage.clear();

При этом:

  • очистка затрагивает только текущий store
  • другие экземпляры остаются неизменными

Удаление базы требует внешних IndexedDB API.


Производительность при множественных экземплярах

Использование нескольких экземпляров влияет на поведение:

Плюсы

  • разделение данных снижает размер выборок
  • ускоряется доступ к узким доменам
  • уменьшается конкуренция ключей

Минусы

  • увеличивается количество открытых соединений IndexedDB
  • возможны дополнительные инициализации драйверов
  • повышается нагрузка на startup приложения

Типичные ошибки при проектировании

1. Дублирование storeName без необходимости

Создание множества экземпляров с одинаковыми параметрами не даёт изоляции, но усложняет архитектуру.


2. Смешивание логических слоёв

Хранение кеша и критичных данных в одном store приводит к:

  • случайной потере данных
  • сложным очисткам
  • трудностям миграции

3. Отсутствие стратегии именования

Без строгих правил:

  • базы начинают пересекаться
  • усложняется дебаг
  • возникают скрытые конфликты в мульти-модулях

Рекомендованная модель организации экземпляров

Практическая структура:

const storage = {
  auth: localForage.createInstance({
    name: "app",
    storeName: "auth"
  }),

  user: localForage.createInstance({
    name: "app",
    storeName: "user"
  }),

  cache: localForage.createInstance({
    name: "app",
    storeName: "cache"
  }),

  settings: localForage.createInstance({
    name: "app",
    storeName: "settings"
  })
};

Такая модель обеспечивает:

  • предсказуемую архитектуру
  • простое масштабирование
  • удобную миграцию
  • изоляцию ответственности между слоями данных

Поведение в асинхронной среде

Каждый экземпляр:

  • инициализируется асинхронно
  • может быть использован независимо
  • не блокирует другие экземпляры при открытии

При одновременном создании множества экземпляров рекомендуется учитывать:

  • возможные гонки инициализации драйверов
  • задержки открытия IndexedDB
  • необходимость предварительного warm-up в критичных приложениях

Совместное использование экземпляров в приложении

Экземпляры можно передавать между модулями без риска нарушения изоляции:

export function saveUser(storage, user) {
  return storage.setItem(user.id, user);
}

Это позволяет:

  • тестировать модули с разными storage реализациями
  • внедрять dependency injection
  • переиспользовать бизнес-логику

Влияние на тестирование

Множественные экземпляры упрощают тестирование:

  • можно создавать временные storeName
  • легко очищать тестовые данные
  • возможно полное изолирование тест-кейсов
const testStore = localForage.createInstance({
  name: "test_db",
  storeName: "unit_test"
});

Такая схема предотвращает утечки данных между тестами.