Mediatr:减少 DI 对象的数量
Mediatr: reducing number of DI'ed objects
我有很多命令和查询,其中大部分都需要相同的接口 DI 来做不同的事情。是否有可能减少我的每个处理程序需要的这种混乱,并且一遍又一遍地重复?
public class GetCoinByIdQueryHandler : IRequestHandler<GetCoinByIdQuery, CoinModel>
{
private readonly EventsContext context;
private readonly ICacheClient cache;
private readonly ILogger logger;
private readonly IMapper mapper;
private readonly Settings settings;
public GetCoinByIdQueryHandler(
EventsContext context, ICacheClient cache, ILogger logger,
IMapper mapper, IOptions<Settings> settings)
{
this.context = context;
this.cache = cache;
this.logger = logger;
this.mapper = mapper;
this.settings = settings.Value;
}
}
这可能与 Mediatr 没有直接关系,但我正在寻找一种更优雅的方法,将所有常见参数减少到可能是一个 DI 参数。
如果有任何不同,我正在使用 Autofac 作为我的 DI 容器。
编辑:可能有基 class,所有处理程序都从基 class 继承并在基 class 中访问所有接口并将它们设置为基 class 上的属性,但我不知道如何实现。
编辑 2:Autofac 有 属性 注入,但这似乎不是正确的方法,所以使用 Mediatr 的人,您如何处理一遍又一遍地重复自己。我见过的每个使用 Mediatr 的开源项目似乎都没有解决重复自己的问题。
当我发现自己处于多个处理程序有许多共同依赖关系的情况时,我会看两件事:
- 我的管理员是否做得太多;和
- 如果是这样,我是否可以在单独的 class
中重构某些行为
例如,在您发布的处理程序代码中,有一个缓存客户端,这可能意味着您的处理程序会做两件事:
- 执行取币业务逻辑;和
- 做一些逻辑做 return 一个已经缓存的硬币,或者缓存你刚检索到的那个
MediatR 具有 behaviors 的概念,它允许您在一个地方处理横切关注点;这可能适用于缓存、日志记录和异常处理。如果您熟悉 ASP.NET 核心中间件,它们遵循相同的概念,因为每个行为都是给定的:
- 当前请求(或 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 行为。这使用 ASP.NET Core 中提供的 IMemoryCache
完全是因为我不知道你的 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();
我们知道了,缓存现在是一个横切关注点,它的实现存在于单个 class 中,使其易于更改和测试。
我们可以对不同的事物应用相同的模式,让处理程序只负责业务逻辑。
我有很多命令和查询,其中大部分都需要相同的接口 DI 来做不同的事情。是否有可能减少我的每个处理程序需要的这种混乱,并且一遍又一遍地重复?
public class GetCoinByIdQueryHandler : IRequestHandler<GetCoinByIdQuery, CoinModel>
{
private readonly EventsContext context;
private readonly ICacheClient cache;
private readonly ILogger logger;
private readonly IMapper mapper;
private readonly Settings settings;
public GetCoinByIdQueryHandler(
EventsContext context, ICacheClient cache, ILogger logger,
IMapper mapper, IOptions<Settings> settings)
{
this.context = context;
this.cache = cache;
this.logger = logger;
this.mapper = mapper;
this.settings = settings.Value;
}
}
这可能与 Mediatr 没有直接关系,但我正在寻找一种更优雅的方法,将所有常见参数减少到可能是一个 DI 参数。
如果有任何不同,我正在使用 Autofac 作为我的 DI 容器。
编辑:可能有基 class,所有处理程序都从基 class 继承并在基 class 中访问所有接口并将它们设置为基 class 上的属性,但我不知道如何实现。
编辑 2:Autofac 有 属性 注入,但这似乎不是正确的方法,所以使用 Mediatr 的人,您如何处理一遍又一遍地重复自己。我见过的每个使用 Mediatr 的开源项目似乎都没有解决重复自己的问题。
当我发现自己处于多个处理程序有许多共同依赖关系的情况时,我会看两件事:
- 我的管理员是否做得太多;和
- 如果是这样,我是否可以在单独的 class 中重构某些行为
例如,在您发布的处理程序代码中,有一个缓存客户端,这可能意味着您的处理程序会做两件事:
- 执行取币业务逻辑;和
- 做一些逻辑做 return 一个已经缓存的硬币,或者缓存你刚检索到的那个
MediatR 具有 behaviors 的概念,它允许您在一个地方处理横切关注点;这可能适用于缓存、日志记录和异常处理。如果您熟悉 ASP.NET 核心中间件,它们遵循相同的概念,因为每个行为都是给定的:
- 当前请求(或 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 行为。这使用 ASP.NET Core 中提供的 IMemoryCache
完全是因为我不知道你的 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();
我们知道了,缓存现在是一个横切关注点,它的实现存在于单个 class 中,使其易于更改和测试。
我们可以对不同的事物应用相同的模式,让处理程序只负责业务逻辑。