ABP入门系列(19)——使用领域事件
1.引言
最近刚学习了下DDD中领域事件的理论知识,总的来说领域事件主要有两个作用,一是解耦,二是使用领域事件进行事务的拆分,通过引入事件存储,来实现数据的最终一致性。若想了解DDD中领域事件的概念,可参考DDD理论学习系列(9)-- 领域事件。

Abp中使用事件总线来实现领域事件,而关于事件总线的实现,大家可参考我这篇博文——事件总线知多少,本文将不再赘述。
2.用例分析
当用户被成功分配任务后,发送邮件和消息通知给用户。
这个用例比较简单,没有太多的复杂逻辑,按照我们传统的思路,直接在任务编辑方法中添加邮件和消息发送的方法即可,代码如下:
public void UpdateTask(UpdateTaskInput input)
{
//We can use Logger, it's defined in ApplicationService base class.
Logger.Info("Updating a task for input: " + input);
//获取是否有权限
bool canAssignTaskToOther = PermissionChecker.IsGranted(PermissionNames.Pages_Tasks_AssignPerson);
//如果任务已经分配且未分配给自己,且不具有分配任务权限,则抛出异常
if (input.AssignedPersonId.HasValue && input.AssignedPersonId.Value != AbpSession.GetUserId() &&
!canAssignTaskToOther)
{
throw new AbpAuthorizationException("没有分配任务给他人的权限!");
}
var updateTask = Mapper.Map<Task>(input);
var user = _userRepository.Get(input.AssignedPersonId.Value);
//先执行分配任务
_taskManager.AssignTaskToPerson(updateTask, user);
//再更新其他字段
_taskRepository.Update(updateTask);
//发送通知
var message = "You hava been assigned one task into your todo list.";
_smtpEmailSender.Send("ysjshengjie@qq.com", updateTask.AssignedPerson.EmailAddress, "New Todo item", message);
_notificationPublisher.Publish("NewTask", new MessageNotificationData(message), null,
NotificationSeverity.Info, new[] { updateTask.AssignedPerson.ToUserIdentifier() });
}

运行,直接挂掉。原因是很清楚,是由于邮箱配置有误导致。但是我们思考一下。我们进行任务分配时最关注的是任务被成功分配,而至于通知是否成功发送相对来说是次要的。但是现在却由于通知发送失败导致任务无非被成功分配,这是不合理的。
那我们要如何做呢?当然是拆分业务逻辑。而这时领域事件就可以粉墨登场了。
3.使用领域事件
就这个用例而言,“用户被成功分配任务”就是一个领域事件。下面我们就来实际应用一下。
3.1. 定义事件源
一个领域事件是通过事件源来识别的,我们直接定义一个TaskAssignedEventData继承自EventData即可:
public class TaskAssignedEventData : EventData
{
public User User { get; set; }
public Task Task { get; set; }
public TaskAssignedEventData(Task task, User user)
{
this.Task = task;
this.User = user;
}
}
3.2. 实现事件处理
定义TaskAssignedToUser事件处理,实现IEventHandler<TaskAssignedEventData>泛型接口即可:
public class TaskAssignedToUser : IEventHandler<TaskAssignedEventData>, ITransientDependency
{
private readonly ISmtpEmailSender _smtpEmailSender;
private readonly INotificationPublisher _notificationPublisher;
public TaskAssignedToUser(ISmtpEmailSender smtpEmailSender, INotificationPublisher notificationPublisher)
{
_smtpEmailSender = smtpEmailSender;
_notificationPublisher = notificationPublisher;
}
public void HandleEvent(TaskAssignedEventData eventData)
{
var message = "You hava been assigned one task into your todo list.";
//TODO:需要重新配置QQ邮箱密码
_smtpEmailSender.Send("ysjshengjie@qq.com", eventData.Task.AssignedPerson.EmailAddress, "New Todo item", message);
_notificationPublisher.Publish("NewTask", new MessageNotificationData(message), null,
NotificationSeverity.Info, new[] { eventData.User.ToUserIdentifier() });
}
}
3.3. 事件触发
我们可以直接在上一节定义的TaskManager领域服务中触发领域事件。因为这样更符合当前领域事件通用语言的表述。
//TaskManager.cs
public void AssignTaskToPerson(Task task, User user)
{
//已经分配,就不再分配
if (task.AssignedPersonId.HasValue && task.AssignedPersonId.Value == user.Id)
{
return;
}
if (task.State != TaskState.Open)
{
throw new ApplicationException("处于非活动状态的任务不能分配!");
}
task.AssignedPersonId = user.Id;
//使用领域事件触发发送通知操作
_eventBus.Trigger(new TaskAssignedEventData(task, user));
}
再运行,我们发现虽然没有接收到消息通知(发送失败),但任务却可以成功分配。
4. 一些问题
- 领域事件在哪注册(订阅)?
应用程序启动时Abp根据约定俗成的命名规则将事件源和事件处理注册到了依赖容器中和事件总线维护的容器中。我们也可以自行在应用服务或领域服务中手动注册。 - 领域事件在哪触发(发布)?
事件的触发同样也没有限定,根据需要,可以在应用服务、领域服务、聚合、实体中发布。 - 领域事件的命名?
领域事件的名字要反映出过去发生的事情的概念。
4.最后
由于demo比较简单,找不到合适的用例,以上使用的用例比较简单。在复杂的用例中,当需要更新多个聚合时,领域事件的作用就体现出来了,借助领域事件我们可以很好的进行事务拆分,达到最终一致性的目的。
而至于领域事件衍生出来的事件存储和事件溯源,下次再和大家分享。
ABP入门系列(19)——使用领域事件的更多相关文章
- ABP入门系列(3)——领域层创建实体
这一节我们主要和领域层打交道.首先我们要对ABP的体系结构以及从模板创建的解决方案进行一一对应.网上有代码生成器去简化我们这一步的任务,但是不建议初学者去使用. 一.首先来看看ABP体系结构 领域层就 ...
- ABP入门系列(18)—— 使用领域服务
ABP入门系列目录--学习Abp框架之实操演练 源码路径:Github-LearningMpaAbp 1.引言 自上次更新有一个多月了,发现越往下写,越不知如何去写.特别是当遇到DDD中一些概念术语的 ...
- ABP入门系列(2)——领域层创建实体
ABP入门系列目录--学习Abp框架之实操演练 这一节我们主要和领域层打交道.首先我们要对ABP的体系结构以及从模板创建的解决方案进行一一对应.网上有代码生成器去简化我们这一步的任务,但是不建议初学者 ...
- ABP入门系列(4)——领域层定义仓储并实现
一.先来介绍下仓储 仓储(Repository): 仓储用来操作数据库进行数据存取.仓储接口在领域层定义,而仓储的实现类应该写在基础设施层. 在ABP中,仓储类要实现IRepository接口,接口定 ...
- ABP入门系列(3)——领域层定义仓储并实现
ABP入门系列目录--学习Abp框架之实操演练 一.先来介绍下仓储 仓储(Repository): 仓储用来操作数据库进行数据存取.仓储接口在领域层定义,而仓储的实现类应该写在基础设施层. 在ABP中 ...
- ABP入门系列目录——学习Abp框架之实操演练
ABP是"ASP.NET Boilerplate Project (ASP.NET样板项目)"的简称. ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WE ...
- ABP入门系列(1)——学习Abp框架之实操演练
作为.Net工地搬砖长工一名,一直致力于挖坑(Bug)填坑(Debug),但技术却不见长进.也曾热情于新技术的学习,憧憬过成为技术大拿.从前端到后端,从bootstrap到javascript,从py ...
- ABP入门系列(6)——展现层实现增删改查
这一章节将通过完善Controller.View.ViewModel,来实现展现层的增删改查.最终实现效果如下图: 一.定义Controller ABP对ASP.NET MVC Controllers ...
- ABP入门系列(5)——创建应用服务
一.解释下应用服务层 应用服务用于将领域(业务)逻辑暴露给展现层.展现层通过传入DTO(数据传输对象)参数来调用应用服务,而应用服务通过领域对象来执行相应的业务逻辑并且将DTO返回给展现层.因此,展现 ...
随机推荐
- Day2-字符编码转换
1.在python2默认编码是ASCII, python3里默认是unicode 2.unicode 分为 utf-32(占4个字节),utf-16(占两个字节),utf-8(占1-4个字节), so ...
- bootstrapValidator 使用(包含入门demo,常用方法,以及常用的规则)
一 什么是bootstrapValidator? -- 一个基于 jquery,boostrap 的表单验证框架....简单实用上手快,页面美观还过得去,不废话了,直接撸. 二 boots ...
- 读Zepto源码之样式操作
这篇依然是跟 dom 相关的方法,侧重点是操作样式的方法. 读Zepto源码系列文章已经放到了github上,欢迎star: reading-zepto 源码版本 本文阅读的源码为 zepto1.2. ...
- JavaScript中对事件简单的理解(1)
事件(event) 1.什么是JavaScript事件? 事件是文档或浏览器中发生的特定交互瞬间. 2.事件流 事件流描述的是从页面中接受事件的顺序,包含IE提出的事件冒泡流与Netscape提出的事 ...
- Javascript中的数组去重-indexof方法
在Javascript中,有时我们会用到数组去重.我在这里给大家介绍一下本人认为最简单实用的一种方法-indexOf()去重. var arr = [1,1,1,2,2,2,3,3,4,5,6,2,1 ...
- Django中通过filter和simple_tag为前端实现自定义函数
Django的模板引擎提供了一般性的功能函数,通过前端可以实现多数的代码逻辑功能,这里称之为一般性,是因为它仅支持大多数常见情况下的函数功能,例如if判断,ifequal对比返回值等,但是稍微复杂一些 ...
- 为网页生成二维码(jquery.qrcode.min.js)
做网站活动页面的时候,要为每个活动生成一个二维码,虽然简单,但还是习惯记录下来. jquery.qrcode.min.js是js的一个库,主流的浏览器都支持:IE6~10, Chrome, Firef ...
- .NET和JAVA 反射对比
反射是一个程序集发现及运行的过程,通过反射可以得到*.exe或*.dll等程序集内部的信息.使用反射可以看到一个程序集内部的接口.类.方法.字段.属性.特性等等信息.在System.Reflectio ...
- 对jsp的初步了解及生成war包(一)
1.jsp与html的区别 最简单的说:jsp是动态网页,html是静态网页 HTML(Hypertext Markup Language)文本标记语言,它是静态页面,和JavaScript一样解释性 ...
- RMAN备份与恢复(二)--常用操作学习
(1)连接目标数据库 在RMAN中可以建立与目标数据库或恢复目录数据库的连接.与目标数据库连接时,用户须具有sysdba系统权限,以保证可以进行数据库的备份.修复与恢复工作. 可以在操作系统命令提示符 ...