前言
源码地址:
https://github.com/SkylerSkr/Skr3D
CQRS
随着并发量的增大,往往让我们处理读请求和写请求的解决方案完全不同。
例如:在处理读请求的时候用Redis,Es数据仓库等;在处理写请求的时候使用Rabbitmq等消峰。
所以在DDD架构中,通常会将查询和命令操作分开,我们称之为CQRS(命令查询的责任分离Command Query Responsibility Segregation),具体落地时,是否将Command和Query分开成两个项目可以看情况决定,大多数情况下放在一个项目可以提高业务内聚性。
那么就有人问了,如果放在两个项目里,业务就不内聚了。如果放在一个项目里,那又怎么算分离呐。
餐厅故事
在讲解决方式之前,先引入一个故事。
老王开了一个餐馆,经常在隔壁家爬上爬下的他自然是文武双全。服务员,厨师,老公都是自己一人担当。
后来餐馆的名气越来越大了,老王自己也不干了,招了很多厨师,每个厨师都只做自己的特色菜。
又找了一个服务员,写了一份菜品清单,让服务员根据清单找指定的厨师做菜。
整理一下:
客人点菜->服务员查看菜品清单->找厨师做菜->给客人送菜
MediatR,中介者模式
上述这个例子,就是典型的中介者模式,服务员作为中介者,来分配给指定厨师做菜。
我们用现成的轮子MediatR,来做中介者模式。
MediatR学习链接
我们直接上代码:
中介者接口,用MediatR实现
///
/// 中介处理程序接口
/// 可以定义多个处理程序
/// 是异步的
///
public interface IMediatorHandler
{
///
/// 发送命令,将我们的命令模型发布到中介者模块
///
/// 泛型
/// 命令模型,比如RegisterStudentCommand
///
Task SendCommand(T command) where T : Command;
///
/// 引发事件,通过总线,发布事件
///
/// 泛型 继承 Event:INotification
/// 事件模型,比如StudentRegisteredEvent,
/// 请注意一个细节:这个命名方法和Command不一样,一个是RegisterStudentCommand注册学生命令之前,一个是StudentRegisteredEvent学生被注册事件之后
///
Task RaiseEvent(T @event) where T : Event;
}
///
/// 一个密封类,实现我们的中介内存总线
///
public sealed class InMemoryBus : IMediatorHandler
{
//构造函数注入
private readonly IMediator _mediator;
// 事件仓储服务
//private readonly IEventStoreService _eventStoreService;
public InMemoryBus(IMediator mediator)
{
_mediator = mediator;
}
///
/// 实现我们在IMediatorHandler中定义的接口
/// 没有返回值
///
///
///
///
public Task SendCommand(T command) where T : Command
{
//这个是正确的
return _mediator.Send(command);//请注意 入参 的类型
}
///
/// 引发事件的实现方法
///
/// 泛型 继承 Event:INotification
/// 事件模型,比如StudentRegisteredEvent
///
public Task RaiseEvent(T @event) where T : Event
{
// MediatR中介者模式中的第二种方法,发布/订阅模式
return _mediator.Publish(@event);
}
}
然后是处理程序
///
/// 领域命令处理程序
/// 用来作为全部处理程序的基类,提供公共方法和接口数据
///
public class CommandHandler
{
// 注入中介处理接口(目前用不到,在领域事件中用来发布事件)
private readonly IMediatorHandler _bus;
///
/// 构造函数注入
///
///
///
///
public CommandHandler( IMediatorHandler bus)
{
_bus = bus;
}
}
订单处理程序,指定命令的处理程序,IRequestHandler
///
/// Order命令处理程序
/// 用来处理该Order下的所有命令
/// 注意必须要继承接口IRequestHandler<,>,这样才能实现各个命令的Handle方法
///
public class OrderCommandHandler : CommandHandler,
IRequestHandler
{
// 注入仓储接口
private readonly IOrderRepository _OrderRepository;
// 注入总线
private readonly IMediatorHandler Bus;
///
/// 构造函数注入
///
///
///
///
///
public OrderCommandHandler(IOrderRepository OrderRepository,
IMediatorHandler bus
) : base( bus)
{
_OrderRepository = OrderRepository;
Bus = bus;
}
// RegisterOrderCommand命令的处理程序
// 整个命令处理程序的核心都在这里
// 不仅包括命令验证的收集,持久化,还有领域事件和通知的添加
public Task Handle(RegisterOrderCommand message, CancellationToken cancellationToken)
{
// 实例化领域模型,这里才真正的用到了领域模型
// 注意这里是通过构造函数方法实现
var Order = new Order(Guid.NewGuid(), message.Name, message.Address, message.OrderItem);
//返回错误
if (Order.Name.Equals("Err"))
{
Bus.RaiseEvent(new DomainNotification("", "订单名为Err"));
return Task.FromResult(new Unit());
}
// 持久化
_OrderRepository.Add(Order);
if (_OrderRepository.SaveChanges() > 0)
{
Bus.RaiseEvent(new RegisterOrderEvent());
}
Bus.RaiseEvent(new DomainNotification("", "Register成功") );
return Task.FromResult(new Unit());
}
// 手动回收
public void Dispose()
{
_OrderRepository.Dispose();
}
}
然后在StartUp的依赖注入中注入:
public class NativeInjectorBootStrapper
{
public static void RegisterServices(IServiceCollection services)
{
// ASP.NET HttpContext dependency
services.AddSingleton();
// 注入 应用层Application
services.AddScoped();
//命令总线Domain Bus(Mediator)
services.AddScoped();
// 领域层 - 领域命令
// 将命令模型和命令处理程序匹配
services.AddScoped, OrderCommandHandler>();
// 领域事件
services.AddScoped, OrderEventHandler>();
// 领域通知
services.AddScoped, DomainNotificationHandler>();
// 领域层 - Memory
services.AddSingleton(factory =>
{
var cache = new MemoryCache(new MemoryCacheOptions());
return cache;
});
// 注入 基础设施层 - 数据层
services.AddScoped();
services.AddScoped();
}
}
然后我们就可以在应用层,这样调用:
public class OrderAppService : IOrderAppService
{
// 用来进行DTO
private readonly IMapper _mapper;
// 中介者 总线
private readonly IMediatorHandler Bus;
public OrderAppService(
IOrderRepository OrderRepository,
IMapper mapper, IMediatorHandler bus
)
{
_mapper = mapper;
Bus = bus;
}
public void Register(OrderViewModel OrderViewModel)
{
var registerCommand = _mapper.Map(OrderViewModel);
Bus.SendCommand(registerCommand);
}
}
这样子请求最后被转发去哪个处理程序,就可以在依赖注入中配置了。
总结
到这里为止,应用层,服务层,基础设施层是绝对内聚的。在开发过程中,各层只需要考虑自己要实现的功能的,不会受其他层的业务影响。