Skip to content

Посты

Реализация кеширования запросов в ASP.Net Core

8 июля 2019 г. • 4 мин чтения

Реализация кеширования запросов в ASP.Net Core

На определенном этапе разработки приложения, обычно довольно рано, вы понимаете, что приложение работает медленно. После некоторых исследований выясняется, что причина в ненужном повторном получении одних и тех же данных, и вам приходит в голову мысль: «Мне нужно кеширование».

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

Что такое кеширование запросов? Кеширование запросов — это механизм кеширования данных на время жизни веб-запроса. В .NET у нас была эта возможность в некоторой степени с коллекцией HttpContext.Items, однако HttpContext не известен своей инъектируемостью.

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

Из коробки ASP.Net Core не имеет инъектируемого кеширования. Как упоминалось ранее, HttpContext.Items — это вариант, но это не элегантное решение.

К счастью для нас, ASP.Net Core предоставляет нам инструменты для создания инъектируемой реализации кеширования запросов, используя встроенную платформу внедрения зависимостей (DI).

Встроенная платформа DI имеет три времени жизни для зависимостей: Singleton, Scoped и Transient. Singleton — это время жизни приложения, Scoped — это время жизни запроса, а Transient — это новый экземпляр с каждым запросом.

Я создал интерфейс, смоделированный по образцу интерфейса IMemoryCache, чтобы сохранить согласованность.

Интерфейс

public interface IRequestCache
{
    /// <summary>
    /// Add the value into request cache. If the key already exists, the value is overwritten.
    /// </summary>
    /// <param name="key"></param>
    /// <param name="value"></param>
    /// <typeparam name="TValue"></typeparam>
    void Add<TValue>(string key, TValue value);

    /// <summary>
    /// Remove the key from the request cache
    /// </summary>
    /// <param name="key"></param>
    void Remove(string key);

    /// <summary>
    /// Retrieve the value by key, if the key is not in the cache then the add func is called
    /// adding the value to cache and returning the added value.
    /// </summary>
    /// <param name="key"></param>
    /// <param name="add"></param>
    /// <typeparam name="TValue"></typeparam>
    /// <returns></returns>
    TValue RetrieveOrAdd<TValue>(string key, Func<TValue> add);

    /// <summary>
    /// Retrieves the value by key. When the key does not exist the default value for the type is returned.
    /// </summary>
    /// <param name="key"></param>
    /// <typeparam name="TValue"></typeparam>
    /// <returns></returns>
    TValue Retrieve<TValue>(string key);
}

Реализация

public class RequestCache : IRequestCache
{
    IDictionary<string, object> _cache = new Dictionary<string, object>();

    /// <summary>
    /// Add the value into request cache. If the key already exists, the value is overwritten.
    /// </summary>
    /// <param name="key"></param>
    /// <param name="value"></param>
    /// <typeparam name="TValue"></typeparam>
    public void Add<TValue>(string key, TValue value)
    {
        _cache[key] = value;
    }

    /// <summary>
    /// Remove the key from the request cache
    /// </summary>
    /// <param name="key"></param>
    public void Remove(string key)
    {
        if (_cache.ContainsKey(key))
        {
            _cache.Remove(key);
        }
    }

    /// <summary>
    /// Retrieve the value by key, if the key is not in the cache then the add func is called
    /// adding the value to cache and returning the added value.
    /// </summary>
    /// <param name="key"></param>
    /// <param name="add"></param>
    /// <typeparam name="TValue"></typeparam>
    /// <returns></returns>
    public TValue RetrieveOrAdd<TValue>(string key, Func<TValue> add)
    {
        if (_cache.ContainsKey(key))
        {
            return (TValue)_cache[key];
        }

        var value = add();

        _cache[key] = value;

        return value;
    }

    /// <summary>
    /// Retrieves the value by key. When the key does not exist the default value for the type is returned.
    /// </summary>
    /// <param name="key"></param>
    /// <typeparam name="TValue"></typeparam>
    /// <returns></returns>
    public TValue Retrieve<TValue>(string key)
    {
        if (_cache.ContainsKey(key))
        {
            return (TValue)_cache[key];
        }

        return default(TValue);
    }
}

Используя платформу DI ASP.Net Core, мы подключим её как Scoped.

services.AddScoped<IRequestCache, RequestCache>();

Использование

public class UserService
{
    private readonly IRequestCache _cache;
    private readonly IUserRepository _userRepository;

    public UserService(IRequestCache cache, IUserRepository userRepository)
    {
        _cache = cache;
        _userRepository = userRepository;
    }

    public User RetrieveUserById(int userId)
    {
        var buildCacheKey = UserService.BuildCacheKey(userId);

        return _cache.RetrieveOrAdd(BuildCacheKey, () => { return _userRepository.RetrieveUserBy(userId); });
    }

    public void Delete(int userId)
    {
        var buildCacheKey = UserService.BuildCacheKey(userId);

        _userRepository.Delete(userId);
        _cache.Remove(BuildCacheKey(userId));
    }

    private static string BuildCacheKey(int userId)
    {
        return $"user_{userId}";
    }
}

Вот и всё! Кеширование запросов теперь можно инъектировать в любое место, где оно вам нужно.

Посетите репозиторий Git и не стесняйтесь протестировать код.

Автор: Chuck Conway — инженер AI с почти 30-летним опытом разработки программного обеспечения. Он создает практические системы AI — конвейеры контента, агенты инфраструктуры и инструменты, которые решают реальные проблемы — и делится тем, что он узнает на этом пути. Свяжитесь с ним в социальных сетях: X (@chuckconway) или посетите его на YouTube и на SubStack.

↑ Вернуться в начало

Вам также может понравиться