当我发现自己处于多个处理程序具有许多共同依赖项的情况时,我会考虑两件事:
- 我的管理人员是否做得太多;和
- 如果是这样,我是否可以在单独的类中重构一些行为
例如,在您发布的处理程序代码中,有一个缓存客户端,这可能意味着您的处理程序做了两件事:
- 执行业务逻辑以检索硬币;和
- 执行一些逻辑确实返回一个已经缓存的硬币,或者缓存您刚刚检索到的硬币
MediatR 的概念是行为 https://github.com/jbogard/MediatR/wiki/Behaviors它允许您在一个地方处理跨领域的问题;这可能适用于缓存、日志记录和异常处理。如果您熟悉 ASP.NET Core 中间件,它们遵循相同的概念,因为给出了每种行为:
- 当前请求(或 MediatR 术语中的查询);和
- 管道中的下一项,可以是另一个行为或查询处理程序
让我们看看如何提取行为中的缓存逻辑。现在,您不需要完全遵循此示例,它实际上只是一种可能的实现。
首先,我们将定义一个应用于需要缓存的查询的接口:
public interface IProvideCacheKey
{
string CacheKey { get; }
}
那么我们可以改变GetCoinByIdQuery
实现该新接口:
public class GetCoinByIdQuery : IRequest<CoinModel>, IProvideCacheKey
{
public int Id { get; set; }
public string CacheKey => $"{GetType().Name}:{Id}";
}
接下来,我们需要创建负责缓存的 MediatR 行为。这使用IMemoryCache https://learn.microsoft.com/en-us/aspnet/core/performance/caching/memory?view=aspnetcore-2.2ASP.NET Core 中提供的仅仅是因为我不知道你的定义ICacheClient
界面:
public class CacheBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IProvideCacheKey, IRequest<TResponse>
{
private readonly IMemoryCache _cache;
public CacheBehavior(IMemoryCache cache)
{
_cache = cache;
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
// Check in cache if we already have what we're looking for
var cacheKey = request.CacheKey;
if (_cache.TryGetValue<TResponse>(cacheKey, out var cachedResponse))
{
return cachedResponse;
}
// If we don't, execute the rest of the pipeline, and add the result to the cache
var response = await next();
_cache.Set(cacheKey, response);
return response;
}
}
最后,我们需要向 Autofac 注册该行为:
builder
.RegisterGeneric(typeof(CacheBehavior<,>))
.As(typeof(IPipelineBehavior<,>))
.InstancePerDependency();
现在我们已经看到,缓存现在是一个跨领域的关注点,它的实现位于单个类中,使其易于更改和测试。
我们可以对不同的事物应用相同的模式,并使处理程序只负责业务逻辑。