はじめに
前々回の記事で紹介したリポジトリパターンライブラリにキャッシュ機能を追加しました。ConcurrentDictionary などをキャッシュとして用いて、リポジトリに読み取りキャッシュ機能を付加します。
公開先
サンプル
// create repositories and add caching functionality.
using var writeRepo = new SampleWriteRepository().WithCache(x => x.ID);
using var readRepo = new SampleReadRepository().WithCache(x => x.ID);
// insert a entity with ID 1.
var entity1 = new SampleEntity() { ID = 1, Code = "001", Name = "entity1" };
writeRepo.Insert(entity1);
// get the entity with ID 1.
// The created instance is cached.
var found1 = readRepo.Get(1);
// get the entity with ID 1 again.
// The cached instance will be returned.
var found2 = readRepo.Get(1);
シーケンス図
キャッシュされていない場合
キーに対応するエンティティがリポジトリから取得されます。
キャッシュされている場合
キーに対応するエンティティがキャッシュされている場合、そのエンティティが返されます。リポジトリにはアクセスしません。
エンティティが更新された場合
更新されたエンティティがキャッシュに存在する場合、そのキーとエンティティをキャッシュから削除します。更新されたことの通知には MessagePipe を利用しています。
利用方法
以下のリポジトリインターフェースに対する拡張メソッドとして実装しています。
- IReadDataRepository<TEntity, TKey>
- IReadDataRepositoryWithContext<TEntity, TKey, TContext>
- IReadDataRepositoryWithUniqueKey<TEntity, TPrimaryKey, TUniqueKey>
- IReadDataRepositoryWithUniqueKeyWithContext<TEntity, TPrimaryKey, TUniqueKey, TContext>
- IAsyncReadDataRepository<TEntity, TKey>
- IAsyncReadDataRepositoryWithContext<TEntity, TKey, TContext>
- IAsyncReadDataRepositoryWithUniqueKey<TEntity, TPrimaryKey, TUniqueKey>
- IAsyncReadDataRepositoryWithUniqueKeyWithContext<TEntity, TPrimaryKey, TUniqueKey, TContext>
次のコードは IReadDataRepository<TEntity, TKey> インターフェースに対する拡張メソッドの一覧です。他のインターフェースも同じ考え方です。
-
IEntityCache<TKey, TEntity> インターフェースまたは IDictionary<TKey, TEntity> インターフェースを実装している型のインスタンスをキャッシュとして指定できます。リポジトリが dispose されるときにキャッシュを dispose するかどうかを指定します。
-
エンティティの型が IHasKey<TKey> インターフェースを実装していない場合、エンティティからキーを取得するメソッドを指定します。
// A ConcurrentDictionary<TKey, TEntity> is generated internally.
IReadDataRepository<TEntity, TKey> repo = CreateRepository();
repo = repo.WithCache();
// specify a cache that implements the IEntityCache<TKey, TEntity> interface.
IReadDataRepository<TEntity, TKey> repo = CreateRepository();
IEntityCache<TKey, TEntity> cache = CreateCache();
bool disposableCache = true;
repo = repo.WithCache(cache, disposableCache);
// specify a cache that implements the IDictionary<TKey, TEntity> interface.
IReadDataRepository<TEntity, TKey> repo = CreateRepository();
IDictionary<TKey, TEntity> cache = CreateCache();
bool disposableCache = true;
repo = repo.WithCache(cache, disposableCache);
// If TEntity does not implement the IHasKey<TKey> interface, specify a method to get the key.
// A ConcurrentDictionary<TKey, TEntity> is generated internally.
IReadDataRepository<TEntity, TKey> repo = CreateRepository();
Func<TEntity, TKey> keyGetter = x => x.ID;
repo = repo.WithCache(keyGetter);
// specify a cache that implements the IEntityCache<TKey, TEntity> interface.
IReadDataRepository<TEntity, TKey> repo = CreateRepository();
Func<TEntity, TKey> keyGetter = x => x.ID;
IEntityCache<TKey, TEntity> cache = CreateCache();
bool disposableCache = true;
repo = repo.WithCache(keyGetter, cache, disposableCache);
// specify a cache that implements the IDictionary<TKey, TEntity> interface.
IReadDataRepository<TEntity, TKey> repo = CreateRepository();
Func<TEntity, TKey> keyGetter = x => x.ID;
IDictionary<TKey, TEntity> cache = CreateCache();
bool disposableCache = true;
repo = repo.WithCache(keyGetter, cache, disposableCache);
更新系リポジトリに対しても拡張メソッドを実装しています。エンティティが更新されたことをキャッシュに通知する機能を付加します。書き込みキャッシュ機能を付加するわけではありません。
- IWriteDataRepository<TEntity>
- IWriteDataRepositoryWithContext<TEntity, TContext>
- IAsyncWriteDataRepository<TEntity>
- IAsyncWriteDataRepositoryWithContext<TEntity, TContext>
次のコードは IWriteDataRepository<TEntity> インターフェースに対する拡張メソッドの一覧です。他のインターフェースも同じ考え方です。
IWriteDataRepository<TEntity> repo = CreateRepository();
Func<TEntity, TKey> keyGetter = x => x.ID;
repo = repo.WithCache(keyGetter);
おわりに
複数の利用者によって頻繁に更新されるエンティティに対してはこのキャッシュ機能は適していませんが、これで十分なユースケースもあると思います。
インターフェースを用いるとこのような後付け機能が追加しやすくなります。