Как реализовать неизменяемый кеш.

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

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

Обновление: если вы используете .NET 5 и выше, вы можете обратиться к другой моей статье под названием IMemoryCache: неизменяемые коллекции и модульные тесты.

Установить и удалить кеш

Предположим, что у нас есть набор парковочных мест, которые часто запрашиваются LotCodes для получения LotNames. Список парковок будет меняться нечасто, поэтому есть смысл хранить список данных в памяти.

В Startup.cs нам нужно зарегистрировать MemoryCache и желаемые службы. Ниже приведен пример случая, когда мы определяем LotNamesCache для извлечения данных из памяти и управления ими.

services.AddMemoryCache();
services.AddScoped<ILotNamesCache, LotNamesCache>();

Фактическая реализация LotNamesCache показана ниже. Мы храним пары "ключ-значение" <LotCode, LotName> как неизменяемый словарь. Ссылка using System.Collections.Immutable; необходима для использования типа ImmutableDictionary и метода ToImmutableDictionary().

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

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

- .Net Docs

В Пространстве имен System.Collections.Immutable есть и другие типы коллекций, такие как ImmutableArray, ImmutableList, ImmutableHashSet и так далее. Мы должны использовать их как можно чаще при работе с неизменяемыми объектами.

Единичные тесты

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

Обратите внимание на строки 26–27: даже несмотря на то, что cachedLots добавляет новую пару "ключ-значение", исходный cachedLots не сохраняет новую пару "ключ-значение". Этот эффект - именно то, что мы хотим, чтобы кешированные значения были согласованными и могли быть изменены только тогда, когда в базовый источник данных внесены изменения.

Вот и все для этого небольшого сообщения в блоге. Надеюсь, это поможет. Спасибо за прочтение.