Skip to content

投稿

ASP.Net Coreでのリクエストキャッシュの実装

2019年7月8日 • 6分で読める

ASP.Net Coreでのリクエストキャッシュの実装

アプリケーション開発のある時点で、通常はかなり早い段階で、アプリケーションが遅いことに気づきます。調査の結果、原因は同じデータを不必要に取得していることであり、ひらめきが生まれ、「キャッシュが必要だ」と考えるようになります。

キャッシュは、データベースやサードパーティAPIへの冗長な呼び出しを排除するための非常に価値のあるパターンです。Microsoftは時間ベースのキャッシュのためにIMemoryCacheを提供していますが、時間ベースのキャッシュが必要でない場合もあります。この記事では、リクエストスコープキャッシュとそれがどのように私たちに利益をもたらすかを見ていきます。

リクエストキャッシュとは何でしょうか?リクエストキャッシュは、Webリクエストの生存期間中にデータをキャッシュするメカニズムです。.NETでは、HttpContext.Itemsコレクションを使用してこの機能をある程度持っていましたが、HttpContextは注入可能性で知られているわけではありません。

リクエストスコープキャッシュにはいくつかの利点があります:まず、古いデータの心配を排除します。ほとんどのシナリオでは、リクエストは1秒未満で実行され、これは通常データが古くなるには十分な時間ではありません。そして次に、リクエストが終了するとデータが消滅するため、有効期限を心配する必要がありません。

標準では、ASP.Net Coreには注入可能なキャッシュがありません。前述のように、HttpContext.Itemsは選択肢の一つですが、エレガントなソリューションではありません。

幸いなことに、ASP.Net Coreは組み込みの依存性注入(DI)フレームワークを使用して、注入可能なリクエストキャッシュ実装を作成するためのツールを提供してくれます。

組み込みのDIフレームワークには、依存関係の3つのライフタイムがあります:SingletonScoped、およびTransientSingletonはアプリケーションの生存期間、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);
    }
}

ASP.Net CoreのDIフレームワークを使用して、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を専門としています。ソーシャルメディアで彼とつながりましょう:X (@chuckconway) または YouTube をご覧ください。

↑ トップに戻る

こちらもおすすめ