前言
源碼地址:
https://github.com/SkylerSkr/Skr3D
CQRS
隨著并發(fā)量的增大,往往讓我們處理讀請求和寫請求的解決方案完全不同麸粮。
例如:在處理讀請求的時(shí)候用Redis甘桑,Es數(shù)據(jù)倉庫等;在處理寫請求的時(shí)候使用Rabbitmq等消峰隧膏。
所以在DDD架構(gòu)中,通常會(huì)將查詢和命令操作分開迈窟,我們稱之為CQRS(命令查詢的責(zé)任分離Command Query Responsibility Segregation)私植,具體落地時(shí)忌栅,是否將Command和Query分開成兩個(gè)項(xiàng)目可以看情況決定车酣,大多數(shù)情況下放在一個(gè)項(xiàng)目可以提高業(yè)務(wù)內(nèi)聚性。
那么就有人問了索绪,如果放在兩個(gè)項(xiàng)目里湖员,業(yè)務(wù)就不內(nèi)聚了。如果放在一個(gè)項(xiàng)目里瑞驱,那又怎么算分離吶娘摔。
餐廳故事
在講解決方式之前,先引入一個(gè)故事唤反。
老王開了一個(gè)餐館凳寺,經(jīng)常在隔壁家爬上爬下的他自然是文武雙全。服務(wù)員彤侍,廚師肠缨,老公都是自己一人擔(dān)當(dāng)。
后來餐館的名氣越來越大了盏阶,老王自己也不干了晒奕,招了很多廚師,每個(gè)廚師都只做自己的特色菜。
又找了一個(gè)服務(wù)員脑慧,寫了一份菜品清單魄眉,讓服務(wù)員根據(jù)清單找指定的廚師做菜。
整理一下:
客人點(diǎn)菜->服務(wù)員查看菜品清單->找廚師做菜->給客人送菜
MediatR闷袒,中介者模式
上述這個(gè)例子坑律,就是典型的中介者模式,服務(wù)員作為中介者囊骤,來分配給指定廚師做菜脾歇。
我們用現(xiàn)成的輪子MediatR,來做中介者模式淘捡。
MediatR學(xué)習(xí)鏈接
我們直接上代碼:
中介者接口藕各,用MediatR實(shí)現(xiàn)
/// <summary>
/// 中介處理程序接口
/// 可以定義多個(gè)處理程序
/// 是異步的
/// </summary>
public interface IMediatorHandler
{
/// <summary>
/// 發(fā)送命令,將我們的命令模型發(fā)布到中介者模塊
/// </summary>
/// <typeparam name="T"> 泛型 </typeparam>
/// <param name="command"> 命令模型焦除,比如RegisterStudentCommand </param>
/// <returns></returns>
Task SendCommand<T>(T command) where T : Command;
/// <summary>
/// 引發(fā)事件激况,通過總線,發(fā)布事件
/// </summary>
/// <typeparam name="T"> 泛型 繼承 Event:INotification</typeparam>
/// <param name="event"> 事件模型膘魄,比如StudentRegisteredEvent乌逐,</param>
/// 請注意一個(gè)細(xì)節(jié):這個(gè)命名方法和Command不一樣,一個(gè)是RegisterStudentCommand注冊學(xué)生命令之前,一個(gè)是StudentRegisteredEvent學(xué)生被注冊事件之后
/// <returns></returns>
Task RaiseEvent<T>(T @event) where T : Event;
}
/// <summary>
/// 一個(gè)密封類创葡,實(shí)現(xiàn)我們的中介內(nèi)存總線
/// </summary>
public sealed class InMemoryBus : IMediatorHandler
{
//構(gòu)造函數(shù)注入
private readonly IMediator _mediator;
// 事件倉儲(chǔ)服務(wù)
//private readonly IEventStoreService _eventStoreService;
public InMemoryBus(IMediator mediator)
{
_mediator = mediator;
}
/// <summary>
/// 實(shí)現(xiàn)我們在IMediatorHandler中定義的接口
/// 沒有返回值
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="command"></param>
/// <returns></returns>
public Task SendCommand<T>(T command) where T : Command
{
//這個(gè)是正確的
return _mediator.Send(command);//請注意 入?yún)?的類型
}
/// <summary>
/// 引發(fā)事件的實(shí)現(xiàn)方法
/// </summary>
/// <typeparam name="T">泛型 繼承 Event:INotification</typeparam>
/// <param name="event">事件模型浙踢,比如StudentRegisteredEvent</param>
/// <returns></returns>
public Task RaiseEvent<T>(T @event) where T : Event
{
// MediatR中介者模式中的第二種方法,發(fā)布/訂閱模式
return _mediator.Publish(@event);
}
}
然后是處理程序
/// <summary>
/// 領(lǐng)域命令處理程序
/// 用來作為全部處理程序的基類灿渴,提供公共方法和接口數(shù)據(jù)
/// </summary>
public class CommandHandler
{
// 注入中介處理接口(目前用不到洛波,在領(lǐng)域事件中用來發(fā)布事件)
private readonly IMediatorHandler _bus;
/// <summary>
/// 構(gòu)造函數(shù)注入
/// </summary>
/// <param name="uow"></param>
/// <param name="bus"></param>
/// <param name="cache"></param>
public CommandHandler( IMediatorHandler bus)
{
_bus = bus;
}
}
訂單處理程序,指定命令的處理程序骚露,IRequestHandler<RegisterOrderCommand, Unit>
/// <summary>
/// Order命令處理程序
/// 用來處理該Order下的所有命令
/// 注意必須要繼承接口IRequestHandler<,>蹬挤,這樣才能實(shí)現(xiàn)各個(gè)命令的Handle方法
/// </summary>
public class OrderCommandHandler : CommandHandler,
IRequestHandler<RegisterOrderCommand, Unit>
{
// 注入倉儲(chǔ)接口
private readonly IOrderRepository _OrderRepository;
// 注入總線
private readonly IMediatorHandler Bus;
/// <summary>
/// 構(gòu)造函數(shù)注入
/// </summary>
/// <param name="OrderRepository"></param>
/// <param name="uow"></param>
/// <param name="bus"></param>
/// <param name="cache"></param>
public OrderCommandHandler(IOrderRepository OrderRepository,
IMediatorHandler bus
) : base( bus)
{
_OrderRepository = OrderRepository;
Bus = bus;
}
// RegisterOrderCommand命令的處理程序
// 整個(gè)命令處理程序的核心都在這里
// 不僅包括命令驗(yàn)證的收集,持久化棘幸,還有領(lǐng)域事件和通知的添加
public Task<Unit> Handle(RegisterOrderCommand message, CancellationToken cancellationToken)
{
// 實(shí)例化領(lǐng)域模型焰扳,這里才真正的用到了領(lǐng)域模型
// 注意這里是通過構(gòu)造函數(shù)方法實(shí)現(xiàn)
var Order = new Order(Guid.NewGuid(), message.Name, message.Address, message.OrderItem);
//返回錯(cuò)誤
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());
}
// 手動(dòng)回收
public void Dispose()
{
_OrderRepository.Dispose();
}
}
然后在StartUp的依賴注入中注入:
public class NativeInjectorBootStrapper
{
public static void RegisterServices(IServiceCollection services)
{
// ASP.NET HttpContext dependency
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
// 注入 應(yīng)用層Application
services.AddScoped<IOrderAppService, OrderAppService>();
//命令總線Domain Bus(Mediator)
services.AddScoped<IMediatorHandler, InMemoryBus>();
// 領(lǐng)域?qū)?- 領(lǐng)域命令
// 將命令模型和命令處理程序匹配
services.AddScoped<IRequestHandler<RegisterOrderCommand, Unit>, OrderCommandHandler>();
// 領(lǐng)域事件
services.AddScoped<INotificationHandler<RegisterOrderEvent>, OrderEventHandler>();
// 領(lǐng)域通知
services.AddScoped<INotificationHandler<DomainNotification>, DomainNotificationHandler>();
// 領(lǐng)域?qū)?- Memory
services.AddSingleton<IMemoryCache>(factory =>
{
var cache = new MemoryCache(new MemoryCacheOptions());
return cache;
});
// 注入 基礎(chǔ)設(shè)施層 - 數(shù)據(jù)層
services.AddScoped<IOrderRepository, OrderRepository>();
services.AddScoped<OrderContext>();
}
}
然后我們就可以在應(yīng)用層,這樣調(diào)用:
public class OrderAppService : IOrderAppService
{
// 用來進(jìn)行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<RegisterOrderCommand>(OrderViewModel);
Bus.SendCommand(registerCommand);
}
}
這樣子請求最后被轉(zhuǎn)發(fā)去哪個(gè)處理程序误续,就可以在依賴注入中配置了吨悍。
總結(jié)
到這里為止,應(yīng)用層蹋嵌,服務(wù)層育瓜,基礎(chǔ)設(shè)施層是絕對內(nèi)聚的。在開發(fā)過程中欣尼,各層只需要考慮自己要實(shí)現(xiàn)的功能的爆雹,不會(huì)受其他層的業(yè)務(wù)影響停蕉。