如何控制业务流程?
上一篇:《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 权限申请的业务流程,我们再详细的画一下:

可以看到,管理员审核通过 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(