.NET Core开发实战(第30课:领域事件:提升业务内聚,实现模块解耦)--学习笔记...
30 | 領(lǐng)域事件:提升業(yè)務(wù)內(nèi)聚,實現(xiàn)模塊解耦
我們在領(lǐng)域的抽象層定義了領(lǐng)域事件和領(lǐng)域事件處理的接口
IDomainEvent
namespace GeekTime.Domain {public interface IDomainEvent : INotification{} }這是一個空接口,它只是標記出來某一個對象是否是領(lǐng)域事件,INotification 也是一個空接口,它是 MediatR 框架的一個接口,是用來實現(xiàn)事件傳遞用的
namespace MediatR {public interface INotification{} }接著是 IDomainEventHandler
namespace GeekTime.Domain {public interface IDomainEventHandler<TDomainEvent> : INotificationHandler<TDomainEvent>where TDomainEvent : IDomainEvent{//這里我們使用了INotificationHandler的Handle方法來作為處理方法的定義//Task Handle(TDomainEvent domainEvent, CancellationToken cancellationToken);} }同樣這個接口也是繼承了 IDomainEventHandler 接口,它有一個泛型參數(shù)是 TDomainEvent,這個 TDomainEvent 約束必須為 IDomainEvent,也就是說處理程序只處理 IDomainEvent 作為入?yún)?/p>
實際上該方法已經(jīng)在 INotificationHandler 中定義好了,所以這里不需要重新定義,只是告訴大家它的定義是什么樣子的
在 Entity 中對領(lǐng)域事件代碼的處理
private List<IDomainEvent> _domainEvents; public IReadOnlyCollection<IDomainEvent> DomainEvents => _domainEvents?.AsReadOnly();public void AddDomainEvent(IDomainEvent eventItem) {_domainEvents = _domainEvents ?? new List<IDomainEvent>();_domainEvents.Add(eventItem); }public void RemoveDomainEvent(IDomainEvent eventItem) {_domainEvents?.Remove(eventItem); }public void ClearDomainEvents() {_domainEvents?.Clear(); }將領(lǐng)域事件做一個實體的屬性存儲進來,它應(yīng)該是一個列表,因為在一個實體操作過程中間可能會發(fā)生多件事情,領(lǐng)域事件應(yīng)該是可以被實體模型之外的代碼讀到,所以暴露一個 ReadOnly 的 Collection
這里還提供幾個方法:添加領(lǐng)域事件,移除領(lǐng)域事件,清除領(lǐng)域事件
這些方法都是在領(lǐng)域模型內(nèi)部進行調(diào)用的
可以看一下之前定義的 Order
public Order(string userId, string userName, int itemCount, Address address) {this.UserId = userId;this.UserName = userName;this.Address = address;this.ItemCount = itemCount;this.AddDomainEvent(new OrderCreatedDomainEvent(this)); }public void ChangeAddress(Address address) {this.Address = address;//this.AddDomainEvent(new OrderAddressChangedDomainEvent(this)); }當我們構(gòu)造一個全新的 Order 的時候,實際上這里可以定義一個事件叫做 OrderCreatedDomainEvent,這個領(lǐng)域事件它的構(gòu)造函數(shù)的入?yún)⒕褪且粋€ Order,當我們調(diào)用 Order 的構(gòu)造函數(shù)時,實際上我們的行為就是在創(chuàng)建一個全新的 Order,所以在這里添加一個事件 AddDomainEvent
同理的比如說 ChangeAddress 被調(diào)用了,我們在這里實際上可以定義一個 OrderAddressChangedDomainEvent 類似這樣子的領(lǐng)域事件出來
大家可以看到領(lǐng)域事件的構(gòu)造和添加都應(yīng)該是在領(lǐng)域模型的方法內(nèi)完成的,而不應(yīng)該是被外界的代碼去調(diào)用創(chuàng)建,因為這些事件都是領(lǐng)域模型內(nèi)部發(fā)生的事件
接著看看 OrderCreatedDomainEvent 的定義
namespace GeekTime.Domain.Events {public class OrderCreatedDomainEvent : IDomainEvent{public Order Order { get; private set; }public OrderCreatedDomainEvent(Order order){this.Order = order;}} }那我們?nèi)绾翁幚砦覀兊念I(lǐng)域事件,接收領(lǐng)域事件的處理應(yīng)該定義在應(yīng)用層
namespace GeekTime.API.Application.DomainEventHandlers {public class OrderCreatedDomainEventHandler : IDomainEventHandler<OrderCreatedDomainEvent>{ICapPublisher _capPublisher;public OrderCreatedDomainEventHandler(ICapPublisher capPublisher){_capPublisher = capPublisher;}public async Task Handle(OrderCreatedDomainEvent notification, CancellationToken cancellationToken){await _capPublisher.PublishAsync("OrderCreated", new OrderCreatedIntegrationEvent(notification.Order.Id));}} }它繼承了 IDomainEventHandler,這個接口是上面講到的領(lǐng)域事件處理器的接口,它的泛型入?yún)⒕褪且幚淼氖录念愋?OrderCreatedDomainEvent
為了簡單演示起見,這里的邏輯是當我們創(chuàng)建一個新的訂單時,我們向 EventBus 發(fā)布一條事件,叫做 OrderCreated 這個事件
我們在 OrderController 的 CreateOrder 定義了一個 CreateOrderCommand
[HttpPost] public async Task<long> CreateOrder([FromBody]CreateOrderCommand cmd) {return await _mediator.Send(cmd, HttpContext.RequestAborted); }CreateOrderCommand
namespace GeekTime.API.Application.Commands {public class CreateOrderCommand : IRequest<long>{//ublic CreateOrderCommand() { }public CreateOrderCommand(int itemCount){ItemCount = itemCount;}public long ItemCount { get; private set; }} }CreateOrderCommandHandler
public async Task<long> Handle(CreateOrderCommand request, CancellationToken cancellationToken) {var address = new Address("wen san lu", "hangzhou", "310000");var order = new Order("xiaohong1999", "xiaohong", 25, address);_orderRepository.Add(order);await _orderRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken);return order.Id; }我們在 CreateOrderCommandHandler 里面創(chuàng)建了一個 Order,然后保存進倉儲,調(diào)用了 UnitOfWork 的 SaveEntitiesAsync
啟動程序,直接執(zhí)行,調(diào)用我們的方法,可以看到我們先進入到了創(chuàng)建訂單的處理系統(tǒng)(CreateOrderCommandHandler),接著進入到了領(lǐng)域事件發(fā)布的 Publish 的代碼(MediatorExtension),當倉儲存儲完畢之后,進入到了 OrderCreatedDomainEventHandler,也就是說我們在創(chuàng)建完我們的領(lǐng)域模型并將其保存之后,我們的領(lǐng)域事件的處理程序才觸發(fā)
在之前講解實現(xiàn) UnitOfWork 的時候(EFContext),我們的 SaveEntitiesAsync 里面只有一行代碼是 SaveChangesAsync,這里添加了一行代碼,是發(fā)送領(lǐng)域事件的代碼 DispatchDomainEventsAsync
public async Task<bool> SaveEntitiesAsync(CancellationToken cancellationToken = default) {var result = await base.SaveChangesAsync(cancellationToken);//await _mediator.DispatchDomainEventsAsync(this);return true; }這就是 MediatorExtension 中看到的 DispatchDomainEventsAsync
namespace GeekTime.Infrastructure.Core.Extensions {static class MediatorExtension{public static async Task DispatchDomainEventsAsync(this IMediator mediator, DbContext ctx){var domainEntities = ctx.ChangeTracker.Entries<Entity>().Where(x => x.Entity.DomainEvents != null && x.Entity.DomainEvents.Any());var domainEvents = domainEntities.SelectMany(x => x.Entity.DomainEvents).ToList();domainEntities.ToList().ForEach(entity => entity.Entity.ClearDomainEvents());foreach (var domainEvent in domainEvents)await mediator.Publish(domainEvent);}} }大家可以看到我們發(fā)送領(lǐng)域事件實際上是這么一個過程:我們從當前要保存的 EntityContext 里面去跟蹤我們的實體,然后從跟蹤到的實體的對象中獲取到我們當前的 Event,如果 Event 是存在的,就把它取出來,然后將實體內(nèi)的 Event 進行清除,再然后將這些 Event 逐條地通過中間件發(fā)送出去,并且找到對應(yīng)的 Handler 處理
定義領(lǐng)域事件實際上也非常簡單,只需要在領(lǐng)域模型創(chuàng)建一個 Events 的目錄,然后將領(lǐng)域事件都定義在這里,領(lǐng)域事件需要繼承 IDomainEvent,領(lǐng)域事件的處理器都定義在 DomainEventHandler,在應(yīng)用層這個目錄下面,我們可以為每一個事件都定義我們的處理程序
總結(jié)一下
領(lǐng)域模型內(nèi)創(chuàng)建事件:我們不要在領(lǐng)域模型的外面去構(gòu)造事件,然后傳遞給領(lǐng)域模型,因為整個領(lǐng)域事件是由領(lǐng)域的業(yè)務(wù)邏輯觸發(fā)的,而不是說外面的對模型的操作觸發(fā)的
另外就是針對領(lǐng)域事件應(yīng)該定義專有的領(lǐng)域事件處理類,就像我們剛才演示的,在一個特定的目錄,對每一個事件進行定義處理類
還有一個就是在同一個事務(wù)里面去處理我們的領(lǐng)域事件,實際上我們也可以選擇在不同的事務(wù)里面處理,如果需要在不同的事務(wù)里面去處理領(lǐng)域事件的時候,我們就需要考慮一致性的問題,考慮中間出錯,消息丟失的問題
總結(jié)
以上是生活随笔為你收集整理的.NET Core开发实战(第30课:领域事件:提升业务内聚,实现模块解耦)--学习笔记...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【复杂系统迁移 .NET Core平台系
- 下一篇: asp.net ajax控件工具集 Au