您认为用MediatR替换我的服务层或服务类是否合理?例如,我的服务类看起来像这样:
public interface IEntityService<TEntityDto> where TEntityDto : class, IDto
{
Task<TEntityDto> CreateAsync(TEntityDto entityDto);
Task<bool> DeleteAsync(int id);
Task<IEnumerable<TEntityDto>> GetAllAsync(SieveModel sieveModel);
Task<TEntityDto> GetByIdAsync(int id);
Task<TEntityDto> UpdateAsync(int id, TEntityDto entityDto);
}
我希望实现某种模块化设计,以便其他动态加载的模块 或者插件可以为我的主要核心应用程序编写自己的通知或命令处理程序。
目前,我的应用程序根本不是事件驱动的,并且我的动态加载的插件没有简单的方法可以进行通信。
我可以将MediatR整合到我的控制器中,完全删除服务层,或者将它与我的服务层一起使用,只需发布通知,这样我的插件就可以处理它们。
目前,我的逻辑主要是CRUD,但在创建,更新,删除之前会有很多自定义逻辑。
可能更换我的服务如下:
public class CommandHandler : IRequestHandler<CreateCommand, Response>, IRequestHandler<UpdateCommand, Response>, IRequestHandler<DeleteCommand, bool>
{
private readonly DbContext _dbContext;
public CommandHandler(DbContext dbContext)
{
_dbContext = dbContext;
}
public Task<Response> Handle(CreateCommand request, CancellationToken cancellationToken)
{
//...
}
public Task<Response> Handle(UpdateCommand request, CancellationToken cancellationToken)
{
//...
}
public Task<bool> Handle(DeleteCommand request, CancellationToken cancellationToken)
{
///...
}
}
这样做有什么不对吗?
基本上,我正在努力为我的逻辑流程选择什么:
似乎与MediatR一样,我没有一个模型可用于创建,更新和删除,因此重用它的一种方法我需要得到如下的请求:
public CreateRequest : MyDto, IRequest<MyDto> {}
public UpdateRequest : MyDto, IRequest<MyDto> {}
或将其嵌入我的命令中,如:
public CreateRequest : IRequest<MyDto>
{
MyDto MyDto { get; set; }
}
MediatR的一个优点是能够轻松插入逻辑并将其插入,这似乎非常适合模块化架构,但我仍然有点困惑如何用它来塑造我的架构。
答案 0 :(得分:10)
如果您有一个类,假设是一个API控制器,它取决于
IRequestHandler<CreateCommand, Response>
更改班级取决于IMediator
有什么好处,
而不是致电
return requestHandler.HandleRequest(request);
它打电话
return mediator.Send(request);
结果是,我们注入了service locator而不是注入我们所需的依赖关系,从而解决了我们所需的依赖关系。
引用Mark Seeman的文章
简而言之,Service Locator的问题在于它隐藏了类的依赖关系,从而导致运行时错误而不是编译时错误,并且使代码更难以维护,因为不清楚何时您将引入重大变化。
与
不完全相同var commandHandler = serviceLocator.Resolve<IRequestHandler<CreateCommand, Response>>();
return commandHandler.Handle(request);
因为介体仅限于解析命令和查询处理程序,但这已经很接近了。它仍然是一个界面,可提供对许多其他界面的访问。
在介绍了IMediator
之后,我们的课程仍然间接依赖于IRequestHandler<CreateCommand, Response>
。所不同的是,现在我们无法通过观察来分辨。我们无法从界面导航到其实现。如果知道要查找的内容,也就是我们知道命令处理程序接口名称的约定,我们可能会认为仍然可以遵循依赖关系。但这远不如实际声明它所依赖的类有用。
当然,我们可以受益于将接口连接到具体的实现而无需编写代码的好处,但是节省的费用微不足道,而且由于导航代码的难度增加(如果很小),我们可能会浪费掉所节省的时间。并且有一些库可以为我们注册这些依赖关系,同时仍然允许我们注入实际依赖的抽象。
建议使用介体有助于实现装饰器模式。但是同样,我们已经通过依赖抽象获得了这种能力。我们可以使用接口的一种实现,也可以使用添加装饰器的另一种实现。依赖抽象的意义在于,我们可以在不更改抽象的情况下更改这样的实现细节。
详细说明:依赖ISomethingSpecific
的意义在于,我们可以更改或替换实现,而无需修改依赖它的类。但是,如果我们说:“我想更改ISomethingSpecific
的实现(通过添加装饰器),那么要实现这一点,我将更改依赖于ISomethingSpecific
的类,它们工作得很好,并且使它们依赖于某些通用的通用接口”,那么出了点问题。还有许多其他方法可以添加装饰器,而无需修改不需要更改的代码部分。
是的,使用IMediator
会促进松散耦合。但是我们已经通过使用定义明确的抽象实现了这一点。一层又一层的间接添加不会带来好处。如果您有足够的抽象以至于编写单元测试很容易,那么您就已经足够了。
假设您有一个用于下订单的类,并且它取决于ICommandHandler<PlaceOrderCommand>
。如果有人试图潜入不属于此的东西(例如更新用户数据的命令)会发生什么?他们必须添加一个新的依赖项ICommandHandler<ChangeUserAddressCommand>
。如果他们想继续在该类中堆放更多东西,而违反了SRP,会发生什么?他们将不得不继续添加更多的依赖项。但这并不能阻止他们这样做,但至少可以照亮正在发生的事情。
另一方面,如果您可以在不添加更多依赖项的情况下将各种随机的东西添加到一个类中呢?该类依赖于可以执行任何操作的抽象。它可以下订单,更新地址,请求销售历史记录等等,而无需添加任何新的依赖项。如果将IoC容器注入不属于该类的类,则会遇到相同的问题。它是单个类或接口,可用于请求各种依赖关系。 这是服务定位器。
IMediator
不会引起违反SRP的行为,并且缺少它不会阻止它们。但是明确,特定的依赖关系使我们远离此类违规行为。
在涉及CQRS时,调解器是中立的(除非我们有两个单独的调解器,例如ICommandMediator
和IQueryMediator
。)将命令处理程序与查询处理程序分开,然后注入a单一界面,实际上将它们重新组合在一起,并将我们所有的命令和查询集中在一处。至少很难说有助于将它们分开。
IMediator
用于调用命令和查询处理程序,但与它们的隔离程度无关。如果在我们添加调解器之前将它们隔离开了,那么它们仍然是。如果我们的查询处理程序执行了不应执行的操作,那么中介器仍然会愉快地调用它。
我希望这听起来不像是调解人在我的狗上奔跑。但这不是在我们的代码上撒上CQRS甚至改善我们的体系结构的灵丹妙药。
我们应该问,有什么好处?它会带来什么不良后果?我需要该工具,还是可以在没有那些后果的情况下获得想要的利益?
我要声明的是,一旦我们已经依赖抽象,“隐藏”类的依赖项的进一步步骤通常不会增加任何价值。它们使阅读和理解变得更加困难,并削弱了我们检测和防止其他代码气味的能力。
答案 1 :(得分:2)
部分在此得到回答:MediatR when and why I should use it? vs 2017 webapi
使用MediaR(或MicroBus或任何其他中介实现)的最大好处是isolating and/or segregating的逻辑(这是使用CQ RS 的流行方法之一)和良好的实现decorator pattern的基础(诸如ASP.NET Core MVC过滤器之类的东西)。从MediatR 3.0开始,它对此提供了内置支持(请参见Behaviours)(而不是使用IoC装饰器)
您也可以将装饰器模式用于服务(诸如FooService
之类的服务)。您也可以将CQRS与服务一起使用(FooReadService
,FooWriteService
)
除此之外,它基于观点,并使用您想要实现的目标。除了代码维护,最终结果应该没有任何区别。
其他阅读:
Baking Round Shaped Apps with MediatR (它将自定义中介程序的实现与MediatR提供的一种和移植过程进行比较)