缘起

哈喽小伙伴周三好,老张又来啦,DDD领域驱动设计的第二个D也快说完了,下一个系列我也在考虑之中,是 Id4 还是 Dockers 还没有想好,甚至昨天我还想,下一步是不是可以写一个简单的Angular 入门教程,本来是想来个前后端分离的教学视频的,简单试了试,发现自己的声音不好听,真心不好听那种,就作罢了,我看博客园有一个大神在 Bilibili 上有一个视频,具体地址忘了,有需要的留言,我找找。不过最近年底了比较累了,目前已经写了15万字了(一百天,平均一天1500字),或者看看是不是给自己放一个假吧,自己也找一些书看一看,给自己充充电,希望大家多提一下建议或者帮助吧。

言归正传,在上一篇文章中《之十 ║领域驱动【实战篇·中】:命令总线Bus分发(一)》,我主要是介绍了,如果通过命令模式来对我们的API层(这里也包括应用层)进行解耦,通过命令分发,可以很好的解决在应用层写大量的业务逻辑,以及多个对象之间混乱的关联的问题。如果对上一篇文章不是很记得了,我这里简单再总结一下,如果你能看懂这些知识点,并心里能大概行程一个轮廓,那可以继续往下看了,如果说看的很陌生,或者想不起来了,那请看上一篇文章吧。上篇文章有以下几个小点:

1、什么是中介者模式?以及中介者模式的原理?(提示:多对象不依赖,但可通讯)

2、MediatR 是如何实现中介者服务的?常用哪两种方法?(提示:请求/响应)

3、工作单元是什么?作用?(提示:事务)

这些知识点都是在上文中提到的,可能说的有点儿凌乱,不知道是否能看懂,上篇遗留了几个问题,所以我就新开了一篇文章,来重点对上一篇文章进行解释说明,大家可以看看是否和自己想的一样,欢迎来交流。

当然还是每篇一问,也是本文的提纲:

1、我们是如何把一个Command命令,一步步走到持久化的?

2、你自己能画一个详细的流程草图么?

零、今天实现左下角浅紫色的下框部分

(昨天的故事中,说到了,咱们已经建立了一个基于 MediatR 的在缓存中的命令总线,我们可以在任何一个地方通过该总线进行命令的分发,然后我们在应用层 StudentAppService.cs 中,对添加StudentCommand进行了分发,那我们到底应该如何分发,中介者又是如何调用的呢, 今天我们就继续接着昨天的故事往下说... )

一、创建命令处理程序 CommandHandlers

咱们先把处理程序做出来,具体是如何执行的,咱们下边会再说明。

1、添加一个命令处理程序基类 CommandHandler.cs

namespace Christ3D.Domain.CommandHandlers
{
/// <summary>
/// 领域命令处理程序
/// 用来作为全部处理程序的基类,提供公共方法和接口数据
/// </summary>
public class CommandHandler
{
// 注入工作单元
private readonly IUnitOfWork _uow;
// 注入中介处理接口(目前用不到,在领域事件中用来发布事件)
private readonly IMediatorHandler _bus;
// 注入缓存,用来存储错误信息(目前是错误方法,以后用领域通知替换)
private IMemoryCache _cache; /// <summary>
/// 构造函数注入
/// </summary>
/// <param name="uow"></param>
/// <param name="bus"></param>
/// <param name="cache"></param>
public CommandHandler(IUnitOfWork uow, IMediatorHandler bus, IMemoryCache cache)
{
_uow = uow;
_bus = bus;
_cache = cache;
} //工作单元提交
//如果有错误,下一步会在这里添加领域通知
public bool Commit()
{
if (_uow.Commit()) return true; return false;
}
}
}

这个还是很简单的,只是提供了一个工作单元的提交,下边会增加对领域通知的伪处理。

2、定义学生命令处理程序 StudentCommandHandler.cs

namespace Christ3D.Domain.CommandHandlers
{
/// <summary>
/// Student命令处理程序
/// 用来处理该Student下的所有命令
/// 注意必须要继承接口IRequestHandler<,>,这样才能实现各个命令的Handle方法
/// </summary>
public class StudentCommandHandler : CommandHandler,
IRequestHandler<RegisterStudentCommand, Unit>,
IRequestHandler<UpdateStudentCommand, Unit>,
IRequestHandler<RemoveStudentCommand, Unit>
{
// 注入仓储接口
private readonly IStudentRepository _studentRepository;
// 注入总线
private readonly IMediatorHandler Bus;
private IMemoryCache Cache; /// <summary>
/// 构造函数注入
/// </summary>
/// <param name="studentRepository"></param>
/// <param name="uow"></param>
/// <param name="bus"></param>
/// <param name="cache"></param>
public StudentCommandHandler(IStudentRepository studentRepository,
IUnitOfWork uow,
IMediatorHandler bus,
IMemoryCache cache
) : base(uow, bus, cache)
{
_studentRepository = studentRepository;
Bus = bus;
Cache = cache;
} // RegisterStudentCommand命令的处理程序
// 整个命令处理程序的核心都在这里
// 不仅包括命令验证的收集,持久化,还有领域事件和通知的添加
public Task<Unit> Handle(RegisterStudentCommand message, CancellationToken cancellationToken)
{
// 命令验证
if (!message.IsValid())
{
// 错误信息收集
NotifyValidationErrors(message);
return Task.FromResult(new Unit());
} // 实例化领域模型,这里才真正的用到了领域模型
// 注意这里是通过构造函数方法实现
var customer = new Student(Guid.NewGuid(), message.Name, message.Email, message.Phone, message.BirthDate); // 判断邮箱是否存在
// 这些业务逻辑,当然要在领域层中(领域命令处理程序中)进行处理
if (_studentRepository.GetByEmail(customer.Email) != null)
{
//这里对错误信息进行发布,目前采用缓存形式
List<string> errorInfo = new List<string>() { "The customer e-mail has already been taken." };
Cache.Set("ErrorData", errorInfo);
return Task.FromResult(new Unit());
} // 持久化
_studentRepository.Add(customer); // 统一提交
if (Commit())
{
// 提交成功后,这里需要发布领域事件
// 比如欢迎用户注册邮件呀,短信呀等 // waiting....
} return Task.FromResult(new Unit()); } // 同上,UpdateStudentCommand 的处理方法
public Task<Unit> Handle(UpdateStudentCommand message, CancellationToken cancellationToken)
{
// 省略...
} // 同上,RemoveStudentCommand 的处理方法
public Task<Unit> Handle(RemoveStudentCommand message, CancellationToken cancellationToken)
{
// 省略...
} // 手动回收
public void Dispose()
{
_studentRepository.Dispose();
}
}
}

3、注入我们的处理程序

在我们的IoC项目中,注入我们的命令处理程序,这个时候,你可能有疑问,为啥是这样的,下边我讲原理的时候会说明。

// Domain - Commands
services.AddScoped<IRequestHandler<RegisterStudentCommand, Unit>, StudentCommandHandler>();
services.AddScoped<IRequestHandler<UpdateStudentCommand, Unit>, StudentCommandHandler>();
services.AddScoped<IRequestHandler<RemoveStudentCommand, Unit>, StudentCommandHandler>();

好啦!这个时候我们已经成功的,顺利的,把由中介总线发出的命令,借助中介者 MediatR ,通过一个个处理程序,把我们的所有命令模型,领域模型,验证模型,当然还有以后的领域事件,和领域通知联系在一起了,只有上边两个类,甚至说只需要一个 StudentCommandHandler.cs 就能搞定,因为另一个 CommandHandler 仅仅是一个基类,完全可以合并在 StudentCommandHandler 类里,是不是感觉很神奇,如果这个时候你没有感觉到他的好处,请先停下往下看的眼睛,仔细思考一下,如果我们不采用这个方法,我们会是怎么的工作:

在 API 层的controller中,进行参数验证,然后if else 判断,

接下来在服务器中写持久化,然后也要对持久化中的错误信息,返回到 API 层;

不仅如此,我们还需要提交成功后,进行发邮件,或者发短信等子业务逻辑(当然这一块,咱们还没实现,不过已经挖好了坑,下一节会说到。);

最后,我们可能以后会说,添加成功和删除成功发的邮件方法不一样,甚至还有其他;

现在想想,如果这样的工作,我们的业务逻辑需要写在哪里?毫无疑问的,当然是在API层和应用层,我们领域层都干了什么?只有简单的一个领域模型和仓储接口!那这可真的不是DDD领域驱动设计的第二个D —— 驱动。

但是现在我们采用中介者模式,用命令驱动的方法,情况就不是这样了,我们在API 层的controller中,只有一行代码,在应用服务层也只有两行;

 var registerCommand = _mapper.Map<RegisterStudentCommand>(StudentViewModel);
Bus.SendCommand(registerCommand);

到这个时候,我们已经从根本上,第二次了解了DDD领域驱动设计所带来的不一样的快感(第一次是领域、聚合、值对象等相关概念)。当然可能还不是很透彻,至少我们已经通过第一条总线——命令总线,来实现了复杂多模型直接的通讯了,下一篇我们说领域事件的时候,你会更清晰。那聪明的你一定就会问了:

好吧,你说的这些我懂了,也大概知道了怎么用,那它们是如何运行的呢?不知道过程,反而无法理解其作用!没错,那接下来,我们就具体说一说这个命令是如何分发的,请耐心往下看。

二、基于源码分析命令处理过程

这里说的基于源码,不是一字一句的讲解,那要是我能说出来,我就是作者了

从壹开始微服务 [ DDD ] 之十一 ║ 基于源码分析,命令分发的过程(二)的更多相关文章

  1. SpringCloud微服务如何优雅停机及源码分析

    目录 方式一:kill -9 java进程id[不建议] 方式二:kill -15 java进程id 或 直接使用/shutdown 端点[不建议] kill 与/shutdown 的含义 Sprin ...

  2. 从壹开始微服务 [ DDD ] 之十二 ║ 核心篇【下】:事件驱动EDA 详解

    缘起 哈喽大家好,又是周二了,时间很快,我的第二个系列DDD领域驱动设计讲解已经接近尾声了,除了今天的时间驱动EDA(也有可能是两篇),然后就是下一篇的事件回溯,就剩下最后的权限验证了,然后就完结了, ...

  3. 从壹开始微服务 [ DDD ] 之八 ║剪不断理还乱的 值对象和Dto

    缘起 哈喽大家周四好,时间是过的真快,这几天一直忙着在公司的项目,然后带带新人,眼看这周要过去了,还是要抽出时间学习学习,这些天看到群里的小伙伴也都在忙着新学习,还是很开心的,至少当时的初衷已经达到了 ...

  4. 微服务架构 | *3.5 Nacos 服务注册与发现的源码分析

    目录 前言 1. 客户端注册进 Nacos 注册中心(客户端视角) 1.1 Spring Cloud 提供的规范标准 1.2 Nacos 的自动配置类 1.3 监听服务初始化事件 AbstractAu ...

  5. Kafka服务端之网络连接源码分析

    #### 简介 上次我们通过分析KafkaProducer的源码了解了生产端的主要流程,今天学习下服务端的网络层主要做了什么,先看下 KafkaServer的整体架构图 ![file](https:/ ...

  6. 从壹开始微服务 [ DDD ] 之终篇 ║当事件溯源 遇上 粉丝活动

    回首 哈喽~大家好,时间过的真快,关于DDD领域驱动设计的讲解基本就差不多了,本来想着周四再开一篇,感觉没有太多的内容了,剩下的一个就是验证的问题,就和之前的JWT很类似,就不打开一个章节了,而且这个 ...

  7. 从壹开始微服务 [ DDD ] 之六 ║聚合 与 聚合根 (下)

    前言 哈喽大家周二好,上次咱们说到了实体与值对象的简单知识,相信大家也是稍微有些了解,其实实体咱们平时用的很多了,基本可以和数据库表进行联系,只不过值对象可能不是很熟悉,值对象简单来说就是在DDD领域 ...

  8. 从壹开始微服务 [ DDD ] 之七 ║项目第一次实现 & CQRS初探

    前言 哈喽大家周五好,我们又见面了,感谢大家在这个周五读我的文章,经过了三周的时间,当然每周两篇的速度的情况下,咱们简单说了下DDD领域驱动设计的第一部分,主要包括了,<项目入门DDD架构浅析& ...

  9. 书籍《深入理解Spring Cloud 与微服务构建》勘误、源码下载

    转载请标明出处: https://blog.csdn.net/forezp/article/details/79638403 本文出自方志朋的博客 文章勘误 错误在所难免,欢迎大家批评指正,在文章下方 ...

随机推荐

  1. Java 8 基础教程 - Predicate

    在Java 8中,Predicate是一个函数式接口,可以被应用于lambda表达式和方法引用.其抽象方法非常简单: /** * Evaluates this predicate on the giv ...

  2. flex 分页打印表格功能

    private function printHandler():void{ var printJob:FlexPrintJob = new FlexPrintJob(); printJob.print ...

  3. 深入理解css3中的线性渐变

    css3中的线性渐变 线性渐变公式: background-image: linear-gradient( [ <angle> | <side-or-corner> ]?, & ...

  4. CSS学习笔记2:伪类

    w3c对伪类的定义是:CSS伪类是用来添加一些选择器的特殊效果. 在我目前看来就是动态的对元素的修饰   它的基本语法是 选择器:伪类{} 伪类有以下几种   常用的伪类:     :link,:vi ...

  5. MySQL中的外键约束

  6. 在高分屏正确显示CHM文件

    今天下了白色相簿2推,发现里面的chm格式的帮助文档显示不正确,又没法在应用程序直接设置系统分辨率托管,google了一下找到了这个方法: 新建 HKEY_LOCAL_MACHINE\ SOFTWAR ...

  7. margin-right没有效果的问题

    margin-right其实有效果的,只是在默认即标准流的情况的下显示不出来效果.如果脱离标准流呢?想到这个,就立马在css文件中加了一个:float:right;然后在测试的时候就能看到margin ...

  8. 洛谷 P1053 解题报告

    P1053 篝火晚会 题目描述 佳佳刚进高中,在军训的时候,由于佳佳吃苦耐劳,很快得到了教官的赏识,成为了"小教官".在军训结束的那天晚上,佳佳被命令组织同学们进行篝火晚会.一共有 ...

  9. 夜神模拟器链接Android studoid

    在cmd 窗口输入:adb.exe connect 127.0.0.1:62001然后as就自动匹配了夜神经常忘记,特此提醒

  10. Deep Learning Enables You to Hide Screen when Your Boss is Approaching

    https://github.com/Hironsan/BossSensor/ 背景介绍 学生时代,老师站在窗外的阴影挥之不去.大家在玩手机,看漫画,看小说的时候,总是会找同桌帮忙看着班主任有没有来. ...