.NET Core开发实战(第30课:领域事件:提升业务内聚,实现模块解耦)--学习笔记...

30 | 领域事件:提升业务内聚,实现模块解耦

我们在领域的抽象层定义了领域事件和领域事件处理的接口

IDomainEvent

namespace GeekTime.Domain
{public interface IDomainEvent : INotification{}
}

这是一个空接口,它只是标记出来某一个对象是否是领域事件,INotification 也是一个空接口,它是 MediatR 框架的一个接口,是用来实现事件传递用的

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 接口,它有一个泛型参数是 TDomainEvent,这个 TDomainEvent 约束必须为 IDomainEvent,也就是说处理程序只处理 IDomainEvent 作为入参

实际上该方法已经在 INotificationHandler 中定义好了,所以这里不需要重新定义,只是告诉大家它的定义是什么样子的

在 Entity 中对领域事件代码的处理

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();
}

将领域事件做一个实体的属性存储进来,它应该是一个列表,因为在一个实体操作过程中间可能会发生多件事情,领域事件应该是可以被实体模型之外的代码读到,所以暴露一个 ReadOnly 的 Collection

这里还提供几个方法:添加领域事件,移除领域事件,清除领域事件

这些方法都是在领域模型内部进行调用的

可以看一下之前定义的 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));
}

当我们构造一个全新的 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 : 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,这个接口是上面讲到的领域事件处理器的接口,它的泛型入参就是要处理的事件的类型 OrderCreatedDomainEvent

为了简单演示起见,这里的逻辑是当我们创建一个新的订单时,我们向 EventBus 发布一条事件,叫做 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 里面创建了一个 Order,然后保存进仓储,调用了 UnitOfWork 的 SaveEntitiesAsync

启动程序,直接执行,调用我们的方法,可以看到我们先进入到了创建订单的处理系统(CreateOrderCommandHandler),接着进入到了领域事件发布的 Publish 的代码(MediatorExtension),当仓储存储完毕之后,进入到了 OrderCreatedDomainEventHandler,也就是说我们在创建完我们的领域模型并将其保存之后,我们的领域事件的处理程序才触发

在之前讲解实现 UnitOfWork 的时候(EFContext),我们的 SaveEntitiesAsync 里面只有一行代码是 SaveChangesAsync,这里添加了一行代码,是发送领域事件的代码 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);}}
}

大家可以看到我们发送领域事件实际上是这么一个过程:我们从当前要保存的 EntityContext 里面去跟踪我们的实体,然后从跟踪到的实体的对象中获取到我们当前的 Event,如果 Event 是存在的,就把它取出来,然后将实体内的 Event 进行清除,再然后将这些 Event 逐条地通过中间件发送出去,并且找到对应的 Handler 处理

定义领域事件实际上也非常简单,只需要在领域模型创建一个 Events 的目录,然后将领域事件都定义在这里,领域事件需要继承 IDomainEvent,领域事件的处理器都定义在 DomainEventHandler,在应用层这个目录下面,我们可以为每一个事件都定义我们的处理程序

总结一下

领域模型内创建事件:我们不要在领域模型的外面去构造事件,然后传递给领域模型,因为整个领域事件是由领域的业务逻辑触发的,而不是说外面的对模型的操作触发的

另外就是针对领域事件应该定义专有的领域事件处理类,就像我们刚才演示的,在一个特定的目录,对每一个事件进行定义处理类

还有一个就是在同一个事务里面去处理我们的领域事件,实际上我们也可以选择在不同的事务里面处理,如果需要在不同的事务里面去处理领域事件的时候,我们就需要考虑一致性的问题,考虑中间出错,消息丢失的问题

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/310931.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《C++ Primer》8.1.2节练习

练习8.1: #include <iostream> #include <stdexcept> using namespace std;istream &f(istream &in) {int v;while (in >> v, !in.eof()) {if (in.bad())throw runtime_error("IO流错误");if (in.fail()) {cerr << "数据错误&…

.NET 5 Preview 1的深度解读和跟进

这几天微软.NET 团队发布了.NET 5 Preview 1, 如约而至。很兴奋&#xff0c;因为.NET Core和.NET Framework终于实现了大一统&#xff0c;同时也很期待&#xff0c;期待.NET 5能给我们带来哪些好的新特性。让我们先把时间拨回到2019年...一、2019年.NET 5的提前剧透去年2019年 …

程序员羽化之路--假如需要一百万个对象

点击上方蓝字关注我们菜菜哥&#xff0c;救命呀又被产品经理砍了&#xff1f;这次搞不好真要被砍了&#xff0c;线上一个用户系统内存溢出了&#xff0c;占用内存太高了用户基数大&#xff0c;内存占用高正常高的不太正常了&#xff0c;我觉得可能和我的设计有关那说说你的用户…

WTM 3.5发布,VUE来了!

千呼万唤中&#xff0c;WTM的Vue前后端分离版本终于和大家见面了&#xff0c;我曾经跟群里1000多位用户保证过Vue版本会在春天到来&#xff0c;吹过的牛逼总算是圆上了。卧槽&#xff0c;NB啊!我等到花都谢了风太大&#xff0c;吹瞎了朕的双眼我是谁&#xff0c;我在哪儿&#…

java 第三方序列化,11.既然有第三方的序列化方式,说明java官方提供的序列化方式应该有一些很明显或者很致命的缺点……...

序列化是什么&#xff1a;把一个java对象转化为二进制对象&#xff0c;并保存到硬盘&#xff0c;或在网络上传输。反序列化就是把序列化的二进制对象读到内存中。 作用&#xff1a;1、减少内存占用或网络传输。比如web容器中的session&#xff0c;当session数量过大比如10W连接…

2020年,我来盘点下微服务架构技术栈

2020年了&#xff0c;很多小伙伴儿对微服务还比较陌生&#xff0c;说起来很多人可能不敢相信&#xff0c;其实微服务这个概念早在2012年就提出来了&#xff0c;经过了这些年的发展&#xff0c;现在已经成为企业非常主流的架构选项了。今天&#xff0c;我就来带大家一起探讨下微…

2.5w字长文爆肝 C++动态内存与智能指针一篇搞懂!太顶了!!!

动态内存与智能指针1.动态内存与智能指针2.shared_ptr类2.1.make_shared函数2.2.shared_ptr的拷贝和赋值2.3.shared_ptr自动销毁所管理的对象2.4. shared_ptr会自动释放相关联的内存2.5.使用了动态生存期的资源的类2.6.定义StrBlob类2.7. StrBlob构造函数2.8.元素访问成员函数2…

ASP.NET Core应用的7种依赖注入方式

ASP.NET Core框架中的很多核心对象都是通过依赖注入方式提供的&#xff0c;如用来对应用进行初始化的Startup对象、中间件对象&#xff0c;以及ASP.NET Core MVC应用中的Controller对象和View对象等&#xff0c;所以我们可以在定义它们的时候采用注入的形式来消费已经注册的服务…

ASP.NET Core 3.x - 为什么采用新的 Endpoint Routing 路由系统

Endpoint Routing 路由系统ASP.NET Core 3.x 使用了一套叫做 Endpoint Routing 的路由系统。这套路由系统在ASP.NET Core 2.2 的时候就开始露面了。这套Endpoint Routing路由系统提供了更强大的功能和灵活性&#xff0c;以便能更好的处理请求。早期ASP.NET Core的路由系统我们先…

《C++ Primer》10.1节练习

练习10.1: #include <iostream> #include <vector> #include <algorithm> using namespace std;int main() {vector<int>vi;int val;vi.push_back(45);vi.push_back(45);vi.push_back(45);vi.push_back(45);for (int i 1; i < 45; i) {vi.push_ba…

Asp.Net Core AuthorizeAttribute 和AuthorizeFilter 跟进及源码解读

一、前言IdentityServer4已经分享了一些应用实战的文章&#xff0c;从架构到授权中心的落地应用&#xff0c;也伴随着对IdentityServer4掌握了一些使用规则&#xff0c;但是很多原理性东西还是一知半解&#xff0c;故我这里持续性来带大家一起来解读它的相关源代码&#xff0c;…

1张手稿图讲明白 Kubernetes 是怎么运行的

注意&#xff1a;如果您已经知道Kubernetes的工作原理&#xff0c;那么您可能会对我之前的博文感兴趣&#xff0c;请停止使用具有管理员权限的kubeconfigKubernetes是最初由Google设计的开源工具&#xff0c;现在由 Cloud Native Computing Foundation&#xff08;CNCF&#xf…

就喜欢用vSphere部署K8s集群,不全是因为自动化!

通过努力&#xff0c;我们有了一个完整配置的工作负载域&#xff0c;其中包括一个NSX-T Edge部署。现在&#xff0c;我们准备继续使用Kubernetes 部署vSphere。通过VMware Cloud Foundation 4.0中的SDDC Manager&#xff0c;我们确保NSX-T Edge可用&#xff0c;并且还确保Workl…

同源策略_如何支持跨域

欢迎大家阅读《朝夕Net社区技术专刊》我们致力于.NetCore的推广和落地&#xff0c;为更好的帮助大家学习&#xff0c;方便分享干货&#xff0c;特创此刊&#xff01;很高兴你能成为忠实读者&#xff0c;文末福利不要错过哦&#xff01;01PARTCoreWebApi的调用1.在Core MVC下建立…

企业数字化转型解决方案

2020年国务院常务会议明确指出&#xff0c;依托工业互联网促进传统产业加快上线上云。此前&#xff0c;工信部也在全国工业和信息化工作会议上表示&#xff0c;2020年要实施“5G工业互联网”512工程。和5G、人工智能等同列的“新基建”&#xff0c;工业互联网成为数字时代的基础…

网站 asp和php的用途,asp和php都有什么功能?

ASP是什么&#xff1f;有什么功能&#xff1f;ASP.NET是微软开发&#xff0c;建立动态的&#xff0c;强大的&#xff0c;智能的、可扩展的网站和网际网络应用的全功能的程序语言如C或VB.NET #。它包括一个强大的安全评估的亮点&#xff0c;以及一个组织的小工具&#xff0c;可以…

ASP.NET Core 3.x - Endpoint Routing 路由体系的内部机制

Endpoint是什么&#xff1f;Endpoint简单的可以理解为这样的一些类&#xff0c;它们包含一个请求的委托&#xff08;Request Delegate&#xff09;和其它的一些元数据&#xff0c;使用这些东西&#xff0c;Endpoint类可以生成一个响应。而在MVC的上下文中&#xff0c;这个请求委…

java获取主机信息大全,网络编程:Java获取网络主机信息

java.net.InetAddress类表示互联网协议 (IP) 地址。有两个子类&#xff1a;Inet4Address&#xff0c; Inet6Address通过 InetAddress可以方便获取一个域名下的IP&#xff0c;也可以获取一个IP的主机名。下面是例子&#xff0c;通过程序查看51cto主机的IP信息&#xff0c;51cto是…

【项目升级】单库、多库、读写分离 · 任你选

本期配套视频&#xff1a;https://www.bilibili.com/video/BV1BJ411B7mn?p6&#xff08;点击阅读原文&#xff0c;可看&#xff0c;如果没有&#xff0c;最晚下午可看&#xff09;继上周增加【任务调度】以后&#xff0c;继续对项目进行2.0版本升级&#xff0c;其实改动的地方…

.Net微服务实战之技术选型篇

王者荣耀 去年我有幸被老领导邀请以系统架构师的岗位带技术团队&#xff0c;并对公司项目以微服务进行了实施。无论是技术团队还是技术架构都是由我亲自的从0到1的选型与招聘成型的&#xff0c;此过程让我受益良多&#xff0c;因此也希望在接下来的系列博文尽可能的与大家分享…