Skip to content

Instantly share code, notes, and snippets.

@matthewrosse
Created December 6, 2023 08:46
Show Gist options
  • Save matthewrosse/d9c052ed1b3631ba57a9589fb0b01e64 to your computer and use it in GitHub Desktop.
Save matthewrosse/d9c052ed1b3631ba57a9589fb0b01e64 to your computer and use it in GitHub Desktop.
using DomainDrivenDesignExample.Api.Features.Common.Caching;
using DomainDrivenDesignExample.Api.Features.Common.Messaging;
using MediatR;
namespace DomainDrivenDesignExample.Api.Features.Common.Behaviors;
internal sealed class CacheQueryBehavior<TQuery, TQueryResponse>
(
ICacheService cacheService
) : IPipelineBehavior<TQuery, TQueryResponse>
where TQuery : ICachableQuery<TQueryResponse>
where TQueryResponse : class
{
public async Task<TQueryResponse> Handle(
TQuery request,
RequestHandlerDelegate<TQueryResponse> next,
CancellationToken cancellationToken
) => await cacheService.GetAsync(
request.QueryKey,
async () => await next(),
cancellationToken);
}
using System.Collections.Concurrent;
using System.Text.Json;
using Microsoft.Extensions.Caching.Distributed;
namespace DomainDrivenDesignExample.Api.Features.Common.Caching;
internal sealed class CacheService
(IDistributedCache distributedCache) : ICacheService
{
private static readonly ConcurrentDictionary<string, bool> CacheKeys = new();
public async Task<T?> GetAsync<T>(string key, CancellationToken cancellationToken = default)
where T : class
{
var cachedValue = await distributedCache.GetStringAsync(key, cancellationToken);
if (cachedValue is null)
{
return null;
}
var value = JsonSerializer.Deserialize<T>(cachedValue);
return value;
}
public async Task<T> GetAsync<T>(string key, Func<Task<T>> valueFactory,
CancellationToken cancellationToken = default) where T : class
{
var cachedValue = await GetAsync<T>(key, cancellationToken);
if (cachedValue is not null)
{
return cachedValue;
}
var newCachedValue = await valueFactory();
await SetAsync(key, newCachedValue, cancellationToken);
return newCachedValue;
}
public async Task SetAsync<T>(string key, T value, CancellationToken cancellationToken = default) where T : class
{
var cachedValue = JsonSerializer.Serialize(value);
await distributedCache.SetStringAsync(key, cachedValue, cancellationToken);
CacheKeys.TryAdd(key, true);
}
public async Task RemoveAsync(string key, CancellationToken cancellationToken = default)
{
await distributedCache.RemoveAsync(key, cancellationToken);
CacheKeys.TryRemove(key, out _);
}
public async Task RemoveByPrefixAsync(string prefixKey, CancellationToken cancellationToken = default)
{
var tasks = CacheKeys
.Keys
.Where(k => k.StartsWith(prefixKey))
.Select(k => RemoveAsync(k, cancellationToken));
await Task.WhenAll(tasks);
}
}
using MediatR;
namespace DomainDrivenDesignExample.Api.Features.Common.Messaging;
public interface ICachableQuery<out TResponse> : IRequest<TResponse>
{
public string QueryKey { get; }
}
using MediatR;
namespace DomainDrivenDesignExample.Api.Features.Common.Messaging;
public interface ICachableQueryHandler<in TQuery, TResponse> : IRequestHandler<TQuery, TResponse>
where TQuery : ICachableQuery<TResponse>;
namespace DomainDrivenDesignExample.Api.Features.Common.Caching;
internal interface ICacheService
{
Task<T?> GetAsync<T>(string key, CancellationToken cancellationToken = default)
where T : class;
Task<T> GetAsync<T>(string key, Func<Task<T>> valueFactory, CancellationToken cancellationToken = default)
where T : class;
Task SetAsync<T>(string key, T value, CancellationToken cancellationToken = default)
where T : class;
Task RemoveAsync(string key, CancellationToken cancellationToken = default);
Task RemoveByPrefixAsync(string prefixKey, CancellationToken cancellationToken = default);
}
using MediatR;
namespace DomainDrivenDesignExample.Api.Features.Common.Messaging;
public interface IQuery<out TResponse> : IRequest<TResponse>;
using MediatR;
namespace DomainDrivenDesignExample.Api.Features.Common.Messaging;
public interface IQueryHandler<in TQuery, TResponse> : IRequestHandler<TQuery, TResponse>
where TQuery : IQuery<TResponse>;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment