9.2领域事件

简介: 领域(近似理解为实现某个功能的多个模型)事件可以切断领域模型之间的强依赖关系,事件发布后,由事件的处理者决定如何响应事件,以便于实现事件发布和事件处理的解耦。

9.2领域事件

基本使用

领域(近似理解为实现某个功能的多个模型)事件可以切断领域模型之间的强依赖关系,事件发布后,由事件的处理者决定如何响应事件,以便于实现事件发布和事件处理的解耦。

MediatR可实现进程内事件的传递,支持一对一和一对多,使用步骤如下:

  1. NuGet安装MediatR.Extensions.Microsoft.DependencyInjection
  2. 在Program.cs中调用AddMediatR方法进行注册,参数为事件处理者所在的程序集

builder.Services.AddMediatR(Assembly.Load("用MediatR实现领域事件"));

  1. 定义在发布者和处理者之间进行数据传递的类,即消息类型。该类需要实现INotification接口

public record TestEvent(string UserName) : INotification;

  1. 事件处理者要实现NotificationHandler<TNotification>接口,泛型参数代表的是要处理的消息类型

publicclassTestEventHandler1 : INotificationHandler<TestEvent>

{

   publicTaskHandle(TestEventnotification, CancellationTokencancellationToken)

   {

       Console.WriteLine($"我收到了{notification.UserName}");

       returnTask.CompletedTask;

   }

}

publicclassTestEventHandler2 : INotificationHandler<TestEvent>

{

   publicasyncTaskHandle(TestEventnotification, CancellationTokencancellationToken)

   {

       awaitFile.WriteAllTextAsync("d:/1.txt", $"来了{notification.UserName}");

   }

}

  1. 在需要发布事件的类中注入IMediator类型的服务,调用Publish方法来发布一对多事件,Send用来发布一对一事件。

[Route("api/[controller]/[action]")]

[ApiController]

publicclassTestController : ControllerBase

{

   privatereadonlyIMediatormediator;

 

   publicTestController(IMediatormediator)

   {

       this.mediator=mediator;

   }

 

   [HttpPost]

   publicasyncTask<IActionResult>Login(LoginRequestreq)

   {

       //不要写成Send

       //如果使用await那么要等所有的Handle方法执行完才能继续执行

       awaitmediator.Publish(newTestEvent(req.UserName));

       returnOk("ok");

   }

}

EF Core中发布领域事件

我们一般在操作EF Core的changeName、构造方法等方法中调用IMediator的publish方法来发布领域事件,但是在这些方法中立即处理发布的事件会有以下问题:

  • 可能存在重复发送领域事件的情况。如分别调用changeName,changeAge方法进行修改,由于每个changeXXX都会发布“实体类被修改”的事件,到导致出多次处理事件,其实只需最后执行一次就可以。
  • 领域事件发布的太早。为了能够发布“新增实体类”的领域事件,我们一般在实体类的构造方法中发布领域事件,但可能存在数据验证没有通过等等的原因最终没有将新增实体类保存在数据库,那就会出现了事件发布过早的错误问题。

解决方法:把领域事件的发布延迟到上下文修改时,即在实体类中仅仅是注册领域事件,而在上下文中的SaveChanges方法中发布事件。

实现步骤:

  1. 为了方便实体类关于领域事件的管理,定义接口

publicinterfaceIDomainEvents

{

   IEnumerable<INotification>GetDomainEvents();//获得注册的领域事件

   voidAddDomainEvent(INotificationeventItem);//注册领域事件

   voidAddDomainEventIfAbsent(INotificationeventItem);//如果领域事件不存在,则注册事件

   voidClearDomainEvents();//清除领域事件

}

  1. 为了简化实体类的编写,定义实体类的抽象类,该抽象类要实现自定义的IDomainEvents接口

publicabstractclassBaseEntity : IDomainEvents

{

   privateList<INotification>DomainEvents=new();

 

   publicvoidAddDomainEvent(INotificationeventItem)

   {

       DomainEvents.Add(eventItem);

   }

 

   publicvoidAddDomainEventIfAbsent(INotificationeventItem)

   {

       if (!DomainEvents.Contains(eventItem))

       {

           DomainEvents.Add(eventItem);

       }

   }

 

   publicvoidClearDomainEvents()

   {

       DomainEvents.Clear();

   }

 

   publicIEnumerable<INotification>GetDomainEvents()

   {

       returnDomainEvents;

   }

}

 

  1. 需要在上下文中保存数据的时候发布注册的领域事件,为了简化上下文代码的编写,声明上下文抽象类

publicabstractclassBaseDbContext : DbContext

{

   privateIMediatormediator;//依赖注入

 

   publicBaseDbContext(DbContextOptionsoptions, IMediatormediator) : base(options)

   {

       this.mediator=mediator;

   }

    //强制不能使用同步方法

   publicoverrideintSaveChanges(boolacceptAllChangesOnSuccess)

   {

       thrownewNotImplementedException("Don not call SaveChanges, please call SaveChangesAsync instead.");

   }

    //重写父类的方法

   publicasyncoverrideTask<int>SaveChangesAsync(boolacceptAllChangesOnSuccess, CancellationTokencancellationToken=default)

   {   //ChangeTracker是上下文用来对实体类变化进行追踪的对象

       //Entries<IDomainEvents>获得所有实现了IDomainEvents接口的实体类

       //只要包含了领域事件的所有实体

       vardomainEntities=this.ChangeTracker.Entries<IDomainEvents>()

                       .Where(x=>x.Entity.GetDomainEvents().Any());

       //获得所有实体的所有领域事件

       vardomainEvents=domainEntities

           .SelectMany(x=>x.Entity.GetDomainEvents()).ToList();

       //清除所有实体类中的所有领域事件,因为执行完了就要在集合中清除

       domainEntities.ToList()

           .ForEach(entity=>entity.Entity.ClearDomainEvents());

       //发布所有的领域事件,要放到代用父类的SaveChangeAsync之前

       //因为这样事件的处理代码会在上下文模型修改保存之前执行

       foreach (vardomainEventindomainEvents)

       {

           awaitmediator.Publish(domainEvent);

       }

       //调用父类方法

       returnawaitbase.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);

   }

}

  1. 编写传递领域事件的类

publicrecordUserUpdatedEvent(GuidId):INotification;

publicrecordUserAddedEvent(UserItem):INotification;

  1. 编写实体类

publicclassUser: BaseEntity

{

   publicGuidId { get; init; }

   publicstringUserName { get; init; }

   publicstringEmail { get; privateset; }

   publicstring?NickName { get; privateset; }

   publicint?Age { get; privateset; }

   publicboolIsDeleted { get; privateset; }

   privateUser()

   {

       //提供无参构造方法。避免EF Core加载数据的时候调用有参的构造方法触发领域事件

   }

   publicUser(stringuserName,stringemail)

   {

       this.Id=Guid.NewGuid();

       this.UserName=userName;

       this.Email=email;

       this.IsDeleted=false;

       //构造方法中注册增加用户事件,放到集合中

       AddDomainEvent(newUserAddedEvent(this));

   }

   publicvoidChangeEmail(stringvalue)

   {

       this.Email=value;

       //避免重复注册

       AddDomainEventIfAbsent(newUserUpdatedEvent(Id));

   }

   publicvoidChangeNickName(string?value)

   {

       this.NickName=value;

       AddDomainEventIfAbsent(newUserUpdatedEvent(Id));

   }

   publicvoidChangeAge(intvalue)

   {

       this.Age=value;

       AddDomainEventIfAbsent(newUserUpdatedEvent(Id));

   }

}

  1. 实体上下文类继承自上面的BaseDbContext
  2. 编写事件处理类

publicclassNewUserSendEmailHandler : INotificationHandler<UserAddedEvent>

{

   privatereadonlyILogger<NewUserSendEmailHandler>logger;

 

   publicNewUserSendEmailHandler(ILogger<NewUserSendEmailHandler>logger)

   {

       this.logger=logger;

   }

 

   publicTaskHandle(UserAddedEventnotification, CancellationTokencancellationToken)

   {

       varuser=notification.Item;

       logger.LogInformation($"向{user.Email}发送欢迎邮件");

       returnTask.CompletedTask;

   }

}

 

publicclassModifyUserLogHandler : INotificationHandler<UserUpdatedEvent>

{

   privatereadonlyUserDbContextcontext;

   privatereadonlyILogger<ModifyUserLogHandler>logger;

 

   publicModifyUserLogHandler(UserDbContextcontext, ILogger<ModifyUserLogHandler>logger)

   {

       this.context=context;

       this.logger=logger;

   }

 

   publicasyncTaskHandle(UserUpdatedEventnotification, CancellationTokencancellationToken)

   {

       //var user = await context.Users.SingleAsync(u=>u.Id== notification.Id);

       varuser=awaitcontext.Users.FindAsync(notification.Id);

       logger.LogInformation($"通知用户{user.Email}的信息被修改");

   }

}

 

  1. 在控制器中使用user的增删改查

[HttpPut]

[Route("{id}")]

publicasyncTask<IActionResult>Update(Guidid,UpdateUserRequestreq)

{

   User?user=context.Users.Find(id);

   if (user==null)

   {

       returnNotFound($"id={id}的User不存在");

   }

   user.ChangeAge(req.Age);

   user.ChangeEmail(req.Email);

   user.ChangeNickName(req.NickName);

   awaitcontext.SaveChangesAsync();

   returnOk();

}


相关文章
|
数据采集 存储 SQL
基于Apache doris的元数据管理系统
什么是元数据?元数据和数据的区别是什么?元数据有什么作用。
1563 0
基于Apache doris的元数据管理系统
28个残疾人,两个月,被阿里客服改变的命运
十年里,阿里巴巴云客服累计免费培训了35万人,为11万人提供了就业岗位,这28个残疾人也正是这11万人中的一份子。
28个残疾人,两个月,被阿里客服改变的命运
|
7月前
|
测试技术 持续交付 开发工具
《鸿蒙开发深度揭秘:应用版本管理与回滚策略》
在鸿蒙开发中,版本管理与回滚是保障应用稳定迭代和用户体验的关键环节。通过语义化版本控制(如“主版本号.次版本号.修订号”)、Git版本控制系统及CI/CD流程,开发者可高效管理代码变更、实现并行开发并确保版本清晰可追溯。当新版本出现问题时,回滚机制通过技术手段(如`git revert`或`git reset`)快速恢复至稳定状态。此外,完善的测试体系与灰度发布策略能降低回滚风险,而持续优化的版本管理方案则应对技术演进与生态变化带来的挑战。掌握这些核心技能,开发者可在鸿蒙生态中实现技术与商业双赢。
296 5
|
12月前
|
消息中间件 SQL 分布式计算
大数据-64 Kafka 高级特性 分区Partition 分区重新分配 实机实测重分配
大数据-64 Kafka 高级特性 分区Partition 分区重新分配 实机实测重分配
352 7
|
9月前
|
数据可视化 数据挖掘 atlas
地图不只是导航:DataV Atlas 揭示地理数据的深层价值
随着互联网场景的快速衍生,打车、外卖、智能驾驶等领域的空间数据爆发式增长,海量数据分析成为日常需求。然而,传统地图服务面临性能、安全和成本挑战。为此,我们推出「DataV Atlas 地理数据服务」,提供高效、安全、易用的地理数据解决方案。通过简单的 SQL 查询即可生成专业地理服务,支持多源数据整合、实时更新与分析,确保数据安全,并深度集成 DataV Board 数据看板,实现一键上屏和交互式分析。适用于大屏展示、城市规划等多种场景,助力企业轻松挖掘空间数据价值。
468 6
地图不只是导航:DataV Atlas 揭示地理数据的深层价值
|
11月前
|
XML Java 数据安全/隐私保护
Spring Aop该如何使用
本文介绍了AOP(面向切面编程)的基本概念和术语,并通过具体业务场景演示了如何在Spring框架中使用Spring AOP。文章详细解释了切面、连接点、通知、切点等关键术语,并提供了完整的示例代码,帮助读者轻松理解和应用Spring AOP。
282 2
Spring Aop该如何使用
|
10月前
DataSourceConfig类
该类包含以下属性,前4个属性是必须由外部指定的,剩下的都是可以默认指定的。(如果pool.properties中没有说明,就是用默认值,约定大于配置的思想)DataSourceConfig类
|
12月前
|
物联网 Linux Android开发
一键掌控未来!用 Uno Platform 打造跨平台 IoT 应用,轻松连接你的智能设备,让生活更智能!
本文通过具体案例介绍了如何使用微软的开源框架 Uno Platform 实现与 IoT 设备的集成。Uno Platform 支持一次编写、多平台部署,适用于 Windows、macOS、Linux、WebAssembly 及 iOS/Android。本例创建了一个控制网络 LED 灯的应用,详细说明了环境搭建、MQTT 客户端配置、主题订阅及控制指令发送等步骤。该案例展示了 Uno Platform 在 IoT 领域的潜力及其跨平台优势,未来可扩展至更多设备类型,构建智能家居系统。
357 0
|
存储 安全 数据安全/隐私保护
配置本地安全策略(一)
配置本地安全策略(一)
397 0
QT TextEdit控件 全面详解
本文详细的介绍了TextEdit控件的各种操作,例如:获取内容、输入控件字符、保持在最后一行添加(自动滚屏)、定时关闭、添加数据换行、向鼠标位置插入一行字符、设置字体颜色属性等操作。 本系列QT全面详解文章目前共有十五篇,本系列文章较为详细的讲述了QT控件的基础操作和使用,也谢谢大家的关注、点赞、收藏。
1937 2
QT TextEdit控件 全面详解