Хранилища данных и кэш

Сложный

Назначение хранилищ — логическое разделение информации, которая содержится в хранилище, и упрощение ее дальнейшего использования в исходном коде.

Действия, которые позволяют выполнять хранилища Creatio:

  • Получить доступ к данным по ключу для чтения или записи.
  • Удалить данные из хранилища по ключу.

Виды хранилищ 

Виды хранилищ, которые поддерживает Creatio:

  • Хранилище данных.
  • Кэш.

Действия, которыми позволяет управлять разделение хранилищ:

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

Физически хранилища данных и кэша могут располагаться на абстрактном сервере хранения данных. Исключением являются данные уровня Request, которые хранятся в памяти.

Сервером хранилища данных является Redis. В качестве сервера хранилищ может использоваться произвольное хранилище, доступ к которому осуществляется через унифицированные интерфейсы. Необходимо учитывать, что обращение к хранилищу является ресурсоемкой операцией, поскольку связана с сериализацией/десериализацией данных и сетевым обменом.

Хранилище данных 

Назначение хранилища данных — промежуточное хранение редко изменяемых (т. е., долгосрочных) данных. Хранилище данных состоит из уровней, которые представлены в таблице ниже. Уровни хранилища представлены перечислением Terrasoft.Core.Store.DataLevel. Перечисление DataLevel описано в Библиотеке .NET классов.

Уровни хранилища данных
Уровень
Описание
Ограничения времени жизни объектов
Request
Уровень запроса. Данные доступны во время обработки текущего запроса.
Объекты сохраняются во время выполнения запроса
Session
Уровень сессии. Данные доступны в сессии текущего пользователя.
Объекты сохраняются во время существования сессии
Application
Уровень приложения. Данные доступны для всего приложения.
Объекты сохраняются на протяжении всего периода существования приложения. Удалить объекты из хранилища можно путем очистки внешнего хранилища.

Данные, которые добавляются в хранилище, хранятся в нем до момента явного удаления.

Хранилище кэша 

Назначение хранилища кэша — хранение оперативной информации. Хранилище кэша состоит из уровней, которые представлены в таблице ниже. Уровни хранилища представлены перечислением Terrasoft.Core.Store.CacheLevel. Перечисление CacheLevel описано в Библиотеке .NET классов.

Уровни хранилища кэша
Уровень
Описание
Ограничения времени жизни объектов
Session
Уровень сессии. Данные доступны в сессии текущего пользователя.
Объекты удаляются при завершении сессии
Workspace
Уровень рабочего пространства. Данные доступны для всех пользователей рабочего пространства.
Объекты удаляются при удалении рабочего пространства
Application
Уровень приложения. Данные доступны всем пользователям приложения вне зависимости от рабочего пространства.
Объекты сохраняются на протяжении всего периода существования приложения. Удалить объекты из хранилища можно путем очистки внешнего хранилища.

Данные, которые хранятся в кэше, характеризуются временем устаревания. Время устаревания — граничный срок актуальности элемента кэша. Независимо от времени устаревания, все элементы удаляются из кэша при завершении времени их жизни.

Данные можно удалить из кэша в произвольный момент времени. Поэтому могут возникать ситуации, когда исходный код пытается получить закэшированные данные, которые на момент обращения уже удалены. В этом случае необходимо получить данные из постоянного хранилища и поместить их в кэш.

Класс Terrasoft.Core.Entities.EntitySchemaQuery предоставляет механизм работы с хранилищем (кэшем Creatio или произвольным хранилищем, которое определено пользователем). Работа с кэшем позволяет оптимизировать эффективность выполнения операций путем обращения к закэшированным результатам запроса без дополнительного обращения к базе данных. При выполнении EntitySchemaQuery-запроса данные, которые получены из базы данных, добавляются в кэш (значение свойства Cache) с ключом (значение свойства CacheItemName). По умолчанию в качестве кэша EntitySchemaQuery-запроса выступает кэш Creatio уровня сессии с локальным хранением данных. В качестве кэша запроса можно использовать произвольное хранилище, которое реализует интерфейс ICacheStore.

Пример работы с кэшем приложения при выполнении запроса EntitySchemaQuery приведен ниже.

Пример работы с кэшем приложения при выполнении запроса EntitySchemaQuery
/* Создает экземпляр EntitySchemaQuery-запроса с корневой схемой [City]. */
var esqResult = new EntitySchemaQuery(UserConnection.EntitySchemaManager, "City");

/* Добавляет в запрос колонку с названием города. */
esqResult.AddColumn("Name");

/* Определяет ключ, под которым в кэше хранятся результаты выполнения запроса.
В качестве кэша выступает кэш уровня сессии с локальным кэшированием данных, поскольку свойство Cache объекта не переопределяется. */
esqResult.CacheItemName = "EsqResultItem";

/* Выполняет запрос к базе данных для получения результирующей коллекции объектов.
После выполнения операции результаты запроса добавляются в кэш. При дальнейшем обращении к esqResult для получения коллекции объектов запроса (при условии, что запрос не был изменен) эти объекты считываются из кэша уровня сессии. */
esqResult.GetEntityCollection(UserConnection);

Объектная модель хранилищ 

Логика работы с хранилищем данных и кэшем реализована в классах и интерфейсах пространства имен Terrasoft.Core.Store. Пространство имен описано в Библиотеке .NET классов.

Интерфейс IBaseStore 

Назначение интерфейса Terrasoft.Core.Store.IBaseStore — определяет базовые возможности всех видов хранилищ.

Действия, которые позволяет реализовать интерфейс IBaseStore:

  • Получить доступ к данным по ключу для чтения или записи (индексатор this[string key]).
  • Удалить данные из хранилища по заданному ключу (метод Remove(string key)).
  • Инициализировать хранилище заданным перечнем параметров (метод Initialize(IDictionary parameters)). Параметры для инициализации хранилищ Creatio считываются из конфигурационного файла. Перечень параметров задается в секциях storeDataAdapter (для хранилища данных) и storeCacheAdapter (для кэша). Можно задать параметры произвольным образом.

Интерфейс IDataStore 

Назначение интерфейса Terrasoft.Core.Store.IDataStore — определяет специфику работы с хранилищами данных. Является наследником базового интерфейса хранилищ IBaseStore. Позволяет получить перечень всех ключей хранилища (свойство Keys).

Важно. При работе с хранилищами данных рекомендуется использовать свойство Keys, если задачу невозможно решить альтернативными способами.

Интерфейс ICacheStore 

Назначение интерфейса Terrasoft.Core.Store.ICacheStore — определяет специфику работы с кэшем. Является наследником базового интерфейса хранилищ IBaseStore. Позволяет оптимизировать работу с хранилищем при одновременном получении набора данных. Реализует метод GetValues(IEnumerable keys), который возвращает словарь объектов кэша с заданными ключами.

Класс Store 

Назначение статического класса Terrasoft.Core.Store.Store — получает доступ к кэшу и хранилищам данных разных уровней.

Статические свойства класса Store:

  • Data — возвращает экземпляр провайдера хранилища данных.
  • Cache — возвращает экземпляр провайдера кэша.

Важно. Для корректной работы хранилищ в приложении Creatio на платформе .NET Core необходимо заменить статические свойства на подключение через UserConnection.

Получить доступ к хранилищам данных и кэша 

Способы получения доступа к хранилищам данных и кэша:

  • Через пользовательское подключение UserConnection.
  • Через прокси-классы.

Получить доступ к хранилищам данных и кэша через UserConnection 

Получить доступ к хранилищам данных и кэша приложения из исходного кода позволяют свойства статического класса Store. Альтернативным способом доступа к хранилищу данных и кэшу является доступ через экземпляр класса UserConnection. Этот способ позволяет избежать использования длинных имен свойств и подключения дополнительных сборок. Для обеспечения миграции от фреймворка .NET Framework к .NET Core необходимо заменить статические свойства на подключение через UserConnection.

Вспомогательные свойства класса UserConnection, которые позволяют получить быстрый доступ к хранилищам данных и кэшу разных уровней:

  • ApplicationCache — возвращает ссылку на кэш уровня Application.
  • WorkspaceCache — возвращает ссылку на кэш уровня Workspace.
  • SessionCache — возвращает ссылку на кэш уровня Session.
  • RequestData — возвращает ссылку на хранилище данных уровня Request.
  • SessionData — возвращает ссылку на хранилище данных уровня Session.
  • ApplicationData — возвращает ссылку на хранилище данных уровня Application.

Пример работы с кэшем через класс UserConnection приведен ниже.

Пример работы с кэшем через класс UserConnection
/* Ключ, с которым значение добавляется в кэш. */
string cacheKey = "SomeKey";

/* Добавляет значение в кэш уровня сессии через свойство UserConnection. */
UserConnection.SessionCache[cacheKey] = "SomeValue";

/* Получает значение из кэша через свойство класса Store. В результате переменная valueFromCache содержит значение SomeValue. */
string valueFromCache = UserConnection.SessionCache[cacheKey] as String;

Получить доступ к хранилищам данных и кэша через прокси-классы 

Прокси-классы — это промежуточное звено между хранилищами и кодом, который получает доступ к хранилищам. Назначение прокси-классов — выполнение промежуточных действий над данными перед их чтением из хранилища или записью в хранилище. Каждый прокси-класс является хранилищем.

Действия, которые позволяют выполнять прокси-классов:

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

Чтобы настроить использование прокси-классов для хранилищ данных и кэшей:

  1. Добавьте секцию proxies в секции storeDataAdapters и storeCacheAdapters конфигурационного файла Web.config, который находится в корневом каталоге приложения.
  2. В секции proxies перечислите все прокси-классы хранилища.

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

Особенности настройки цепочки прокси-классов:

  • Конечной точкой применения цепочки прокси-классов является кэш или хранилище данных, для которого эта цепочка определяется.
  • Каждый прокси-класс работает или с хранилищем данных, или с хранилищем кэша. Вид хранилища определяется с помощью свойств ICacheStoreProxy.CacheStore или IDataStoreProxy.DataStore. Свойство может ссылаться на другой прокси-класс, на конечное хранилище или кэш, но это неизвестно для прокси-класса. Прокси-класс может выступать в качестве хранилища, с которым работают другие прокси-классы.

Пример настройки прокси-классов приведен ниже.

Пример настройки прокси-классов
<storeDataAdapters>
    <storeAdapter levelName="Request" type="RequestDataAdapterClassName">
        <proxies>
            <proxy name="RequestDataProxyName1" type="RequestDataProxyClassName1" />
            <proxy name="RequestDataProxyName2" type="RequestDataProxyClassName2" />
            <proxy name="RequestDataProxyName3" type="RequestDataProxyClassName3" />
        </proxies>
    </storeAdapter>
</storeDataAdapters>

<storeCacheAdapters>
    <storeAdapter levelName="Session" type="SessionCacheAdapterClassName">
        <proxies>
            <proxy name="SessionCacheProxyName1" type="SessionCacheProxyClassName1" />
            <proxy name="SessionCacheProxyName2" type="SessionCacheProxyClassName2" />
        </proxies>
    </storeAdapter>
</storeCacheAdapters>

Цепочка вызова прокси-классов хранилища данных RequestDataProxyName3 —> RequestDataProxyName2 —> RequestDataProxyName1 —> RequestDataAdapterClassName (конечное хранилище данных уровня запроса).

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

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

Базовые интерфейсы прокси-классов 
  • Terrasoft.Core.Store.IDataStoreProxy — интерфейс прокси-классов хранилища данных.
  • Terrasoft.Core.Store.ICacheStoreProxy — интерфейс прокси-классов кэша.

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

Каждый из этих интерфейсов имеет одно свойство — ссылку на хранилище или кэш, с которым работает текущий прокси-класс. Для интерфейса IDataStoreProxy это свойство DataStore, а для интерфейса ICacheStoreProxy — свойство CacheStore.

Прокси-классы трансформации ключей 

Прокси-классы, которые реализуют логику трансформации ключей значений хранилища:

  • Terrasoft.Core.Store.KeyTransformerProxy — базовый абстрактный класс всех прокси-классов, которые трансформируют ключи кэша. Реализует методы и свойства интерфейса ICacheStoreProxy. Чтобы избежать дублирования логики, при создании пользовательских прокси-классов в качестве родительского используйте класс KeyTransformerProxy.
  • Класс Terrasoft.Core.Store.PrefixKeyTransformerProxy — прокси-класс, который трансформирует ключи кэша путем добавления к ним заданного префикса.

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

    Пример работы с кэшем уровня сессии через прокси-класс PrefixKeyTransformerProxy
    /* Ключ, с которым значение помещается в кэш через прокси-класс. */
    string key = "Key";
    
    /* Префикс, который добавляется прокси-классом к значению ключа. */
    string prefix = "customPrefix";
    
    /* Создает прокси-класс, который используется для записи значений в кэш уровня сессии. */
    ICacheStore proxyCache = new PrefixKeyTransformerProxy(prefix, Store.Cache[CacheLevel.Session]);
    
    /* Через прокси-класс записывает в кэш значения с ключом key. Фактически значение записывается в глобальный кэш уровня сессии с ключом prefix + key. */
    proxyCache[key] = "CachedValue";
    
    /* Через прокси-класс получает значения по ключу key. */
    var valueFromProxyCache = (string)proxyCache[key];
    
    /* Получает значения по ключу prefix + key непосредственно из кэша уровня сессии. */
    var valueFromGlobalCache = (string)UserConnection.SessionCache[prefix + key];
    

    В результате переменные valueFromProxyCache и valueFromGlobalCache содержат одинаковое значение CachedValue.

  • Класс Terrasoft.Core.Store.DataStoreKeyTransformerProxy — прокси-класс, который трансформирует ключи хранилища данных путем добавления к ним заданного префикса.

    Пример работы с хранилищем данных через прокси-класс DataStoreKeyTransformerProxy приведен ниже.

    Пример работы с хранилищем данных через прокси-класс DataStoreKeyTransformerProxy
    /* Ключ, с которым значение помещается в хранилище через прокси-класс. */
    string key = "Key";
    
    /* Префикс, который добавляется прокси-классом к значению ключа. */
    string prefix = "customPrefix";
    
    /* Создает прокси-класс, который используется для записи значений в хранилище уровня сессии. */
    IDataStore proxyStorage = new DataStoreKeyTransformerProxy(prefix) { DataStore = Store.Data[DataLevel.Session] };
    
    /* Через прокси-класс записывает в хранилище значения с ключом key. Фактически значение записывается в глобальное хранилище уровня сессии с ключом prefix + key. */
    proxyStorage[key] = "StoredValue";
    
    /* Через прокси-класс получает значения по ключу key. */
    var valueFromProxyStorage = (string)proxyStorage[key];
    
    /* Получает значения по ключу prefix + key непосредственно из хранилища уровня сессии. */
    var valueFromGlobalStorage = (string)UserConnection.SessionData[prefix + key]; 
    

    В результате переменные valueFromProxyStorage и valueFromGlobalStorage бсодержат одинаковое значение StoredValue.

Локальное кэширование данных 

На базе прокси-классов в Creatio реализован механизм локального кэширования данных. Назначение кэширования данных — снижение нагрузки на сервер хранения данных и времени выполнения запросов при работе с редко изменяемыми данными.

Логику механизма локального кэширования реализует внутренний прокси-класс LocalCachingProxy, который выполняет кэширование данных на текущем узле веб-фермы. Класс проверяет время жизни кэшированных объектов и получает данные из глобального кэша, если кэшированные данные не актуальны.

Методы расширения интерфейса ICacheStore статического класса CacheStoreUtilities, которые используются для локального кэширования:

  • WithLocalCaching() — переопределенный метод, который возвращает экземпляр класса LocalCachingProxy.
  • WithLocalCachingOnly(string) — выполняет локальное кэширование данных заданной группы элементов с проверкой их актуальности.
  • ExpireGroup(string) — устанавливает признак устаревания для заданной группы элементов. При вызове этого метода все элементы заданной группы становятся неактуальными и не возвращаются при запросе данных.

Пример работы с кэшем рабочего пространства с использованием локального кэширования приведен ниже.

Пример работы с кэшем рабочего пространства с использованием локального кэширования
/* Создает прокси-класс, который выполняет локальное кэширование данных. Все элементы, которые записываются в кэш через текущий прокси-класс, принадлежат к группе контроля актуальности Group1. */
ICacheStore cacheStore1 = Store.Cache[CacheLevel.Workspace].WithLocalCaching("Group1");

/* Добавляет элемент в кэш через прокси-класс. */
cacheStore1["Key1"] = "Value1";

/* Создает прокси-класс, который выполняет локальное кэширование данных. Все элементы, которые записываются в кэш через текущий прокси-класс, принадлежат к группе контроля актуальности Group1. */
ICacheStore cacheStore2 = Store.Cache[CacheLevel.Workspace].WithLocalCaching("Group1");
cacheStore2["Key2"] = "Value2";

/* Для всех элементов группы Group1 устанавливается признак устаревания. Устаревшими считаются элементы с ключами Key1 и Key2, поскольку они принадлежат к одной группе контроля актуальности Group1, несмотря на то, что элементы добавлены в кэш через разные прокси-классы. */
cacheStore2.ExpireGroup("Group1");

/* Попытка получения значений из кэша по ключам Key1 и Key2 после того, как элементы были помечены как устаревшие. В результате переменные cachedValue1 и cachedValue2 содержат значение null. */
var cachedValue1 = cacheStore1["Key1"]; 
var cachedValue2 = cacheStore1["Key2"];

Особенности использования хранилищ данных и кэша 

Особенности, которые необходимо учитывать для повышения эффективности работы с хранилищами данных и кэша:

  • В хранилища добавляются только сериализуемые объекты. Это обусловлено спецификой работы с хранилищами ядра приложения. При сохранении данных в хранилища (за исключением хранилища данных уровня Request) объект предварительно сериализуется, а при получении — десериализуется.
  • Обращение к хранилищу является ресурсоемкой операцией, поэтому необходимо избегать лишних обращений к хранилищу в коде.

    Примеры работы с хранилищами приведены ниже.

    /* Записывает объект из хранилища в промежуточную переменную. */
    object value = UserConnection.SessionData["SomeKey"];
    
    /* Проверяет значение промежуточной переменной. */
    if (value != null)
    {
        /* Возвращает значение. */
        return (string)value;
    }
    
    /* Использует расширяющий метод GetValue(). */
    return UserConnection.SessionData.GetValue<string>("SomeKey");
    
    /* Выполняет сетевое обращение и десериализацию данных. */
    if (UserConnection.SessionData["SomeKey"] != null)
    {
        /* Повторно выполняет сетевое обращение и десериализацию данных. */
        return (string)UserConnection.SessionData["SomeKey"];
    }
    
    /* Удаляет без предварительной проверки. */
    UserConnection.SessionData.Remove("SomeKey");
    
    /* Выполняет сетевое обращение и десериализаци. данных. */
    if (UserConnection.SessionData["SomeKey"] != null)
    {
        /* Повторно выполняет сетевое обращение и десериализацию данных. */
        UserConnection.SessionData.Remove("SomeKey");
    }
    
  • Любые изменения состояния объекта, который считан из хранилища данных или кэша, выполняются в памяти локально и автоматически не фиксируются в хранилище. Чтобы изменения отобразились в хранилище, явно запишите в хранилище измененный объект.

    Пример записи данных в хранилище приведен ниже.

    Пример записи данных в хранилище
    /* Получает словарь значений из хранилища данных сессии по ключу "SomeDictionary". */
    Dictionary<string, string> dic = (Dictionary<string, string>)UserConnection.SessionData["SomeDictionary"];
    
    /* Изменяет значения элемента словаря. Изменения в хранилище не фиксируются. */
    dic["Key"] = "ChangedValue";
    
    /* Добавляет новый элемент в словарь. Изменения в хранилище не фиксируются. */
    dic.Add("NewKey", "NewValue");
    
    /* Фиксирует словарь в хранилище данных по ключу "SomeDictionary". Все изменения фиксируются в хранилище. */
    UserConnection.SessionData["SomeDictionary"] = dic;