如何控制业务流程?
2016-04-27 09:44
281 查看
如何控制业务流程?
上一篇:《DDD 领域驱动设计-如何完善 Domain Model(领域模型)?》开源地址:https://github.com/yuezhongxin/CNBlogs.Apply.Sample(代码已更新)
需要注意的是,业务流程并不是工作流程,在领域模型中,业务流程的控制很重要,在上篇的领域模型中我们就忽略了这一点,所以在后面的实现中,出现了一些严重的问题,主要是管理员审核 JS 权限申请的业务流程。
先看一下 JsPermissionApply 实体中的 Pass 操作代码:
public async Task Pass() { this.Status = Status.Pass; this.ApprovedTime = DateTime.Now; this.ReplyContent = "恭喜您!您的JS权限申请已通过审批。"; eventBus = IocContainer.Default.Resolve<IEventBus>(); await eventBus.Publish(new JsPermissionOpenedEvent() { UserId = this.UserId }); await eventBus.Publish(new MessageSentEvent() { Title = "您的JS权限申请已批准", Content = this.ReplyContent, RecipientId = this.UserId }); }
对应的单元测试代码:
[Fact] public async Task ProcessApply_WithPassTest() { var userId = 1; var jsPermissionApply = await _jsPermissionApplyRepository.GetWaiting(userId).FirstOrDefaultAsync(); Assert.NotNull(jsPermissionApply); await jsPermissionApply.Pass(); _unitOfWork.RegisterDirty(jsPermissionApply); Assert.True(await _unitOfWork.CommitAsync()); }
有没有发现一些问题?开通 JS 权限和消息通知,发生在 JsPermissionApply 实体对象持久化之前,这本身的设计就有问题,另外,如果 JsPermissionApply 实体对象持久化失败的话,开通 JS 权限和消息通知会正常执行,相反,开通 JS 权限和消息通知如果出现问题,JsPermissionApply 实体对象持久化也会不受影响,还有就是开通 JS 权限和消息通知放在一起也会有问题。
造成上面这些问题的原因,就是我们之前画的业务流程图太敷衍了,没有具体的进行细化设计,针对管理员审核 JS 权限申请的业务流程,我们再详细的画一下:
![](https://images2015.cnblogs.com/blog/435188/201604/435188-20160422101858335-552193933.png)
可以看到,管理员审核通过 JS 权限申请,JS 权限申请的状态改为“通过”,再开通 JS 权限,然后持久化 JS 权限申请,最后再消息通知用户,整个 JS 权限申请通过的业务流程顺序应该是这样的,对照上面这张图,再看之前的实现,确实牛头不对马尾。
简单总结下审核通过 JS 权限申请的业务流程顺序:
JS 权限申请状态改为“通过”。
开通 JS 权限。
消息通知用户。
好,来看一下改进后的 JsPermissionApply 实体代码:
namespace CNBlogs.Apply.Domain { public class JsPermissionApply : IAggregateRoot { private IEventBus eventBus; public JsPermissionApply() { } public JsPermissionApply(string reason, int userId, string ip) { if (string.IsNullOrEmpty(reason)) { throw new ArgumentException("申请内容不能为空"); } if (reason.Length > 3000) { throw new ArgumentException("申请内容超出最大长度"); } if (userId == 0) { throw new ArgumentException("用户Id为0"); } this.Reason = reason; this.UserId = userId; this.Ip = ip; this.Status = Status.Wait; } public int Id { get; private set; } public string Reason { get; private set; } public int UserId { get; private set; } public Status Status { get; private set; } = Status.Wait; public string Ip { get; private set; } public DateTime ApplyTime { get; private set; } = DateTime.Now; public string ReplyContent { get; private set; } public DateTime? ApprovedTime { get; private set; } public bool IsActive { get; private set; } = true; public async Task<bool> Pass() { if (this.Status != Status.Wait) { return false; } this.Status = Status.Pass; this.ApprovedTime = DateTime.Now; this.ReplyContent = "恭喜您!您的JS权限申请已通过审批。"; eventBus = IocContainer.Default.Resolve<IEventBus>(); await eventBus.Publish(new JsPermissionOpenedEvent() { UserId = this.UserId }); return true; } public bool Deny(string replyContent) { if (this.Status != Status.Wait) { return false; } this.Status = Status.Deny; this.ApprovedTime = DateTime.Now; this.ReplyContent = $"抱歉!您的JS权限申请没有被批准,{(string.IsNullOrEmpty(replyContent) ? "" : $"具体原因:{replyContent}<br/>")}麻烦您重新填写申请理由。"; return true; } public bool Lock() { if (this.Status != Status.Wait) { return false; } this.Status = Status.Lock; this.ApprovedTime = DateTime.Now; this.ReplyContent = "抱歉!您的JS权限申请没有被批准,并且申请已被锁定,具体请联系contact@cnblogs.com。"; return true; } public async Task Passed() { if (this.Status != Status.Pass) { return; } eventBus = IocContainer.Default.Resolve<IEventBus>(); await eventBus.Publish(new MessageSentEvent() { Title = "您的JS权限申请已批准", Content = this.ReplyContent, RecipientId = this.UserId }); } public async Task Denied() { if (this.Status != Status.Deny) { return; } eventBus = IocContainer.Default.Resolve<IEventBus>(); await eventBus.Publish(new MessageSentEvent() { Title = "您的JS权限申请未通过审批", Content = this.ReplyContent, RecipientId = this.UserId }); } public async Task Locked() { if (this.Status != Status.Lock) { return; } eventBus = IocContainer.Default.Resolve<IEventBus>(); await eventBus.Publish(new MessageSentEvent() { Title = "您的JS权限申请未通过审批", Content = this.ReplyContent, RecipientId = this.UserId }); } } }
Passed, Denied, Locked都是过去式,表示
Pass, Deny, Lock操作完成之后的行为,可以看到,在这些操作的内容都有 Status 状态的判断,验证的是什么状态下的 JsPermissionApply 才能执行此行为,任何不符合状态的执行都是不合法的,比如执行 Pass 的前提条件是 Status 状态为 Wait,表示只有 Status 状态为 Wait 的时候,才能执行 Pass 并修改其状态,执行 Passed 的前提前提条件是 Status 状态为 Passed,意思就像其命名 Passed 一样,无需多说。
上面最重要的是开通 JS 权限的执行,因为这是 JS 权限申请最终的执行结果,所以我们后面的操作,都必须建立在其成功的基础之上,那有人会有疑问:为什么上面的业务流程顺序不是这样的呢?当申请状态改为“通过”之后,我们才能去开通 JS 权限,这是开通 JS 权限的前提条件,这时候 JS 权限申请状态是没有被持久化的,所以,如果开通 JS 权限失败,JS 权限申请状态是不会被保存的,另外,开通 JS 权限的领域事件并没有返回值,领域事件一般没有返回值的设计,它只是去通知事件订阅者执行,并不一定需要事件订阅者返回结果给它,那我们如果判断开通 JS 权限是否执行正确呢?就是通过异常判断,如果开通 JS 权限的领域事件发生异常,后面的操作也将不会正常执行。
改进后的 JsPermissionApplyTest 单元测试代码:
namespace CNBlogs.Apply.Domain.Tests { public class JsPermissionApplyTest { private IApplyAuthenticationService _applyAuthenticationService; private IJsPermissionApplyRepository _jsPermissionApplyRepository; private IUnitOfWork _unitOfWork; public JsPermissionApplyTest() { CNBlogs.Apply.BootStrapper.Startup.Configure(); _applyAuthenticationService = IocContainer.Default.Resolve<IApplyAuthenticationService>(); _jsPermissionApplyRepository = IocContainer.Default.Resolve<IJsPermissionApplyRepository>(); _unitOfWork = IocContainer.Default.Resolve<IUnitOfWork>(); } [Fact] public async Task ApplyTest() { var userId = 1; var verfiyResult = await _applyAuthenticationService.Verfiy(userId); Console.WriteLine(verfiyResult); Assert.Empty(verfiyResult); var jsPermissionApply = new JsPermissionApply("我要申请JS权限", userId, ""); _unitOfWork.RegisterNew(jsPermissionApply); Assert.True(await _unitOfWork.CommitAsync()); } [Fact] public async Task ProcessApply_WithPassTest() { var userId = 1; var jsPermissionApply = await _jsPermissionApplyRepository.GetWaiting(userId).FirstOrDefaultAsync(); Assert.NotNull(jsPermissionApply); Assert.True(await jsPermissionApply.Pass()); _unitOfWork.RegisterDirty(jsPermissionApply); Assert.True(await _unitOfWork.CommitAsync()); await jsPermissionApply.Passed(); } [Fact] public async Task ProcessApply_WithDenyTest() { var userId = 1; var jsPermissionApply = await _jsPermissionApplyRepository.GetWaiting(userId).FirstOrDefaultAsync(); Assert.NotNull(jsPermissionApply); Assert.True(jsPermissionApply.Deny("理由太简单了。")); _unitOfWork.RegisterDirty(jsPermissionApply); Assert.True(await _unitOfWork.CommitAsync()); await jsPermissionApply.Denied(); } [Fact] public async Task ProcessApply_WithLockTest() { var userId = 1; var jsPermissionApply = await _jsPermissionApplyRepository.GetWaiting(userId).FirstOrDefaultAsync(); Assert.NotNull(jsPermissionApply); Assert.True(jsPermissionApply.Lock()); _unitOfWork.RegisterDirty(jsPermissionApply); Assert.True(await _unitOfWork.CommitAsync()); await jsPermissionApply.Locked(); } } }
从上面代码,我们可以清晰看到业务流程的执行顺序,
Assert.NotNull和
Assert.True就相当于应用层中的
if判断,如果正确,则继续向下执行。
JsPermissionApply 领域模型经过三篇博文的完善,基本上符合要求了。
在解决方案中,我们可以看到只有领域层、基础设施层和领域层单元测试的项目,并没有应用层和表现层的实现,但到目前为止,我们似乎把整个系统都完成了一样,这种感觉是很美妙的,JsPermissionApply 领域模型在我手心中,任你是 Web 实现或者 WebApi 实现,又或者是其他技术框架,我都不怕,一切都是自然而然的工作,所以,关于后面的实现,你也可以交给其他人去完成,地基由我奠基,盖楼你来完成。
尽管这个系统很简单,但 DDD 确实是一种很美妙的艺术。😏
相关文章推荐
- 创建一个Windows的NTP Server
- 数据结构 - 栈的顺序实现
- redis错误:BeginForkOperation: system error caught. error code=0x000005af
- 语音信号的短时平均能量matlab程序
- so文件的局部函数加密
- 命名空间别名 Namespace aliases
- 【剑指offer-Java版】42翻转单词顺序VS左旋转字符串
- getResource()和getResourceAsStream()以及路径问题
- 团队项目
- 基于JS实现Android,iOS一个手势动画效果
- requireJS使用指南
- 学会用各种姿势备份MySQL数据库
- c# JSON序列化与反序列化
- 360浏览器兼容性问题
- 72、java中如何取得当天是本年的第多少周?
- Matlab函数备忘3
- iOS开发总结之UIScrollView主要属性
- 代码注释中的5要与3不要
- 登录功能测试点
- mvc3 RenderAction传参问题