知名的摄影网站有哪些,企业官网网站设计,产品故事软文案例,网页解析技术流程30 | 领域事件#xff1a;提升业务内聚#xff0c;实现模块解耦我们在领域的抽象层定义了领域事件和领域事件处理的接口IDomainEventnamespace GeekTime.Domain
{public interface IDomainEvent : INotification{}
}这是一个空接口#xff0c;它只是标记出来某一个对象是否是… 30 | 领域事件提升业务内聚实现模块解耦我们在领域的抽象层定义了领域事件和领域事件处理的接口IDomainEventnamespace GeekTime.Domain
{public interface IDomainEvent : INotification{}
}
这是一个空接口它只是标记出来某一个对象是否是领域事件INotification 也是一个空接口它是 MediatR 框架的一个接口是用来实现事件传递用的namespace MediatR
{public interface INotification{}
}
接着是 IDomainEventHandlernamespace GeekTime.Domain
{public interface IDomainEventHandlerTDomainEvent : INotificationHandlerTDomainEventwhere TDomainEvent : IDomainEvent{//这里我们使用了INotificationHandler的Handle方法来作为处理方法的定义//Task Handle(TDomainEvent domainEvent, CancellationToken cancellationToken);}
}
同样这个接口也是继承了 IDomainEventHandler 接口它有一个泛型参数是 TDomainEvent这个 TDomainEvent 约束必须为 IDomainEvent也就是说处理程序只处理 IDomainEvent 作为入参实际上该方法已经在 INotificationHandler 中定义好了所以这里不需要重新定义只是告诉大家它的定义是什么样子的在 Entity 中对领域事件代码的处理private ListIDomainEvent _domainEvents;
public IReadOnlyCollectionIDomainEvent DomainEvents _domainEvents?.AsReadOnly();public void AddDomainEvent(IDomainEvent eventItem)
{_domainEvents _domainEvents ?? new ListIDomainEvent();_domainEvents.Add(eventItem);
}public void RemoveDomainEvent(IDomainEvent eventItem)
{_domainEvents?.Remove(eventItem);
}public void ClearDomainEvents()
{_domainEvents?.Clear();
}
将领域事件做一个实体的属性存储进来它应该是一个列表因为在一个实体操作过程中间可能会发生多件事情领域事件应该是可以被实体模型之外的代码读到所以暴露一个 ReadOnly 的 Collection这里还提供几个方法添加领域事件移除领域事件清除领域事件这些方法都是在领域模型内部进行调用的可以看一下之前定义的 Orderpublic 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));
}
当我们构造一个全新的 Order 的时候实际上这里可以定义一个事件叫做 OrderCreatedDomainEvent这个领域事件它的构造函数的入参就是一个 Order当我们调用 Order 的构造函数时实际上我们的行为就是在创建一个全新的 Order所以在这里添加一个事件 AddDomainEvent同理的比如说 ChangeAddress 被调用了我们在这里实际上可以定义一个 OrderAddressChangedDomainEvent 类似这样子的领域事件出来大家可以看到领域事件的构造和添加都应该是在领域模型的方法内完成的而不应该是被外界的代码去调用创建因为这些事件都是领域模型内部发生的事件接着看看 OrderCreatedDomainEvent 的定义namespace GeekTime.Domain.Events
{public class OrderCreatedDomainEvent : IDomainEvent{public Order Order { get; private set; }public OrderCreatedDomainEvent(Order order){this.Order order;}}
}
那我们如何处理我们的领域事件接收领域事件的处理应该定义在应用层namespace GeekTime.API.Application.DomainEventHandlers
{public class OrderCreatedDomainEventHandler : IDomainEventHandlerOrderCreatedDomainEvent{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这个接口是上面讲到的领域事件处理器的接口它的泛型入参就是要处理的事件的类型 OrderCreatedDomainEvent为了简单演示起见这里的逻辑是当我们创建一个新的订单时我们向 EventBus 发布一条事件叫做 OrderCreated 这个事件我们在 OrderController 的 CreateOrder 定义了一个 CreateOrderCommand[HttpPost]
public async Tasklong CreateOrder([FromBody]CreateOrderCommand cmd)
{return await _mediator.Send(cmd, HttpContext.RequestAborted);
}
CreateOrderCommandnamespace GeekTime.API.Application.Commands
{public class CreateOrderCommand : IRequestlong{//ublic CreateOrderCommand() { }public CreateOrderCommand(int itemCount){ItemCount itemCount;}public long ItemCount { get; private set; }}
}
CreateOrderCommandHandlerpublic async Tasklong 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 里面创建了一个 Order然后保存进仓储调用了 UnitOfWork 的 SaveEntitiesAsync启动程序直接执行调用我们的方法可以看到我们先进入到了创建订单的处理系统CreateOrderCommandHandler接着进入到了领域事件发布的 Publish 的代码MediatorExtension当仓储存储完毕之后进入到了 OrderCreatedDomainEventHandler也就是说我们在创建完我们的领域模型并将其保存之后我们的领域事件的处理程序才触发在之前讲解实现 UnitOfWork 的时候EFContext我们的 SaveEntitiesAsync 里面只有一行代码是 SaveChangesAsync这里添加了一行代码是发送领域事件的代码 DispatchDomainEventsAsyncpublic async Taskbool SaveEntitiesAsync(CancellationToken cancellationToken default)
{var result await base.SaveChangesAsync(cancellationToken);//await _mediator.DispatchDomainEventsAsync(this);return true;
}
这就是 MediatorExtension 中看到的 DispatchDomainEventsAsyncnamespace GeekTime.Infrastructure.Core.Extensions
{static class MediatorExtension{public static async Task DispatchDomainEventsAsync(this IMediator mediator, DbContext ctx){var domainEntities ctx.ChangeTracker.EntriesEntity().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);}}
}
大家可以看到我们发送领域事件实际上是这么一个过程我们从当前要保存的 EntityContext 里面去跟踪我们的实体然后从跟踪到的实体的对象中获取到我们当前的 Event如果 Event 是存在的就把它取出来然后将实体内的 Event 进行清除再然后将这些 Event 逐条地通过中间件发送出去并且找到对应的 Handler 处理定义领域事件实际上也非常简单只需要在领域模型创建一个 Events 的目录然后将领域事件都定义在这里领域事件需要继承 IDomainEvent领域事件的处理器都定义在 DomainEventHandler在应用层这个目录下面我们可以为每一个事件都定义我们的处理程序总结一下领域模型内创建事件我们不要在领域模型的外面去构造事件然后传递给领域模型因为整个领域事件是由领域的业务逻辑触发的而不是说外面的对模型的操作触发的另外就是针对领域事件应该定义专有的领域事件处理类就像我们刚才演示的在一个特定的目录对每一个事件进行定义处理类还有一个就是在同一个事务里面去处理我们的领域事件实际上我们也可以选择在不同的事务里面处理如果需要在不同的事务里面去处理领域事件的时候我们就需要考虑一致性的问题考虑中间出错消息丢失的问题