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

В браузерных приложениях, использующих localForage, организация экземпляров напрямую влияет на изоляцию данных, читаемость архитектуры и предсказуемость работы хранилища. Библиотека предоставляет модель, в которой каждый экземпляр может иметь собственные настройки драйвера, собственного пространства ключей и независимый набор данных. Это позволяет рассматривать хранилище не как глобальную свалку значений, а как набор логически разделённых модулей.

localForage по умолчанию использует единый глобальный экземпляр. Он создаётся автоматически и наследует настройки, заданные через config. Однако при росте приложения такой подход быстро становится ограничением: ключи начинают пересекаться, данные разных подсистем смешиваются, а управление жизненным циклом хранения усложняется.

Для решения этой проблемы используется метод createInstance, который позволяет создавать независимые экземпляры:

import localForage from "localforage";

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

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

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

Параметр name как граница домена

Поле name задаёт логическую область хранения. На уровне IndexedDB оно соответствует имени базы данных. В контексте localForage это первый уровень изоляции.

Использование разных name имеет смысл в следующих случаях:

  • разделение крупных подсистем (например, admin и client)
  • полная изоляция окружений (sandbox, production)
  • разделение приложений внутри одного домена

Пример:

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

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

Хотя storeName совпадает, данные физически разделены на уровне базы.

storeName как логический модуль

Если name задаёт уровень базы, то storeName определяет внутреннюю коллекцию. Это наиболее важный инструмент архитектурного разделения внутри localForage.

Типичные подходы к именованию storeName:

  • users
  • auth
  • cart
  • preferences
  • cache
  • session

Важно избегать слишком абстрактных названий вроде data или store, так как они не отражают предметную область и усложняют поддержку.

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

В крупных приложениях распространён подход, при котором для каждой предметной области создаётся отдельный экземпляр:

import localForage from "localforage";

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

export const cartStorage = localForage.createInstance({
  name: "shopApp",
  storeName: "cart"
});

export const productStorage = localForage.createInstance({
  name: "shopApp",
  storeName: "products"
});

Такой подход позволяет:

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

Фабрика экземпляров

При динамическом создании модулей часто используется фабрика экземпляров. Это особенно актуально, когда требуется изоляция по userId или tenantId:

import localForage from "localforage";

function createUserStore(userId) {
  return localForage.createInstance({
    name: `app_user_${userId}`,
    storeName: "data"
  });
}

В контексте localForage это позволяет масштабировать клиентское хранилище до многопользовательских сценариев без конфликтов ключей.

Изоляция данных через соглашения об именовании

Даже при использовании createInstance критически важно соблюдать единый стиль именования ключей внутри storeName. Распространённые стратегии:

Префиксная модель

await userStore.setItem("user:profile", data);
await userStore.setItem("user:settings", settings);

Групповая модель

await cartStore.setItem("items", items);
await cartStore.setItem("totals", totals);

Иерархическая модель через ключи

await storage.setItem("auth/token", token);
await storage.setItem("auth/refreshToken", refreshToken);

localForage не интерпретирует ключи как структуру, но соглашения позволяют имитировать иерархию на уровне логики приложения.

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

В некоторых архитектурах экземпляры создаются на лету, например при переключении workspace:

import localForage from "localforage";

const stores = new Map();

function getWorkspaceStore(workspaceId) {
  if (!stores.has(workspaceId)) {
    stores.set(
      workspaceId,
      localForage.createInstance({
        name: "workspaceApp",
        storeName: `ws_${workspaceId}`
      })
    );
  }
  return stores.get(workspaceId);
}

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

Ошибки организации экземпляров

На практике встречаются типичные архитектурные ошибки при использовании localForage:

Один глобальный store для всего

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

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

Случайные имена storeName

Динамическое или непоследовательное именование вроде:

storeName: "data1"
storeName: "tempStore"
storeName: "cache_new_final"

создаёт хаос в структуре хранения.

Смешивание доменов

Хранение авторизации, UI-состояния и бизнес-данных в одном экземпляре нарушает принципы изоляции и усложняет миграции.

Стратегии именования в масштабируемых приложениях

В архитектурах среднего и крупного масштаба используется строгая схема:

  • name → идентификатор приложения или окружения
  • storeName → доменная область
  • ключи → конкретные сущности или состояния

Пример структуры:

name: "crmApp"
storeName: "contacts"
keys:
  - contact:list
  - contact:selected
  - contact:filters

Такая организация в localForage позволяет легко переносить данные между версиями приложения и упрощает миграции схемы хранения.

Версионирование через экземпляры

Отдельный подход — использование версии приложения в имени базы:

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

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

Это упрощает миграцию данных, позволяя работать с несколькими схемами параллельно.

Разделение по средам выполнения

В некоторых приложениях экземпляры разделяются по средам:

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

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

localForage в этом случае используется как единый API-слой, а изоляция достигается через namespace.

Итоговая модель организации

Структурно корректная архитектура экземпляров строится по принципу трёх уровней:

  • уровень приложения (name)
  • уровень домена (storeName)
  • уровень сущности (ключи)

Такая модель обеспечивает предсказуемость, минимизирует конфликты и делает клиентское хранилище управляемым даже в сложных распределённых интерфейсах.