上篇中说到了面临的问题(传送门:DDD设计中的Unitwork与DomainEvent如何相容?),和当时实现的一个解决方案。在实际使用了几天后,有了新的思路,和@trunks 兄提出的观点类似。下面且听我娓娓道来。

一、回顾

先回顾一下,代码中的核心类。

DomainEventConsistentQueue : 用于把多个领域事件放到一个集合中,批量进行实际的发布操作。
SqlServerUnitOfWork : 基于SQL SERVER的工作单元实现。

上篇最终的编码效果。

            var aggregateA = new AggregateRootA(); //从仓储中获取
var aggregateB = new AggregateRootB(); //从仓储中获取 using (var queue = DomainEventConsistentQueue.Current())
{
using (var unitwork = new SqlServerUnitOfWork(GlobalConfig.DBConnectString))
{
aggregateA.Event();
unitwork.RegisterModfied(aggregateA);
aggregateB.Event();
unitwork.RegisterModfied(aggregateA); var isSuccess = unitwork.Commit();
if (isSuccess)
queue.PublishEvents();
}
} public class AggregateRootA : AggregateRoot
{
public void Event()
{
DomainEventConsistentQueue.Current().RegisterEvent(new DomainEventA());
}
} public class AggregateRootB : AggregateRoot
{
public void Event()
{
DomainEventConsistentQueue.Current().RegisterEvent(new DomainEventB());
}
} public class DomainEventA : IDomainEvent
{ } public class DomainEventB : IDomainEvent
{ }

二、问题

1.其中红色标识出来的代码显得与整个上下文格格不入,此处是应用层中的一个跨多个聚合根的业务处理操作。对于编码业务逻辑的人来说,其实没有必要去管理整个领域事件如何发布,因为领域事件本身表达的就是已经发生的事情,所以概念上是在数据已经完成修改后给我成功发布出去就行。那么此处标记出的代码显得有点多余,因为这里需要编码人员去管理领域事件的发布。

2.其中橙色标识出来的代码的副作用很大,导致所有调用此方法发布的领域事件都得通过一致性队列进行批量发布。哪怕是单个聚合根的操作,也都得在外层加个 using (var queue = DomainEventConsistentQueue.Current())。这样的方式与常规的DomainEventBus.Instance().Publish方式产生了差异,让编码业务代码的人多了一份职责,去决定此处加不加using (var queue = DomainEventConsistentQueue.Current())。

三、解决方案

  此时我想到的方案是,把工作单元的生命周期提炼出来作为执行上下文中的一个概念。这样可以使用类似Thread.CurrentThread这样的方式来在任何地方获取到当前的工作单元。有了这个可以做2件事:

  ①根据当前是否处于工作单元的环境中来处理领域事件的发布方式。这样可以隐藏起直接发布还是通过DomainEventConsistentQueue来发布的逻辑。

  ②在工作单元中抛出必要的事件,如(提交事件、回滚事件),通过注册其事件来关联DomainEventConsistentQueue的发布操作。

四、进行改造

1.先定义一个执行上下文。

    public class ExcutingContext
{
private static readonly ThreadLocal<IUnitOfWork> _unitWork = new ThreadLocal<IUnitOfWork>(); public static UnitOfWork UseSqlServerUnitOfWork(string dbConnectString)
{
if (_unitWork.Value != null)
throw new ApplicationException("当前线程已经启动了一个工作单元"); var unitWork = new SqlServerUnitOfWork(dbConnectString);
_unitWork.Value = unitWork; unitWork.CommittedEvent += CommittedEventHandle;
unitWork.RollBackEvent += RollBackEventHandle; return unitWork;
} public static UnitOfWork GetCurrentUnitOfWork()
{
return _unitWork.Value as UnitOfWork;
} private static void CommittedEventHandle(bool isSuccess)
{
_unitWork.Value = null;
} private static void RollBackEventHandle()
{
_unitWork.Value = null;
}
}

2.改造DomainEventBus的发布方法

        public void Publish<T>(T aDomainEvent) where T : IDomainEvent
{
if (aDomainEvent.IsRead)
return; var unitOfWork = ExcutingContext.GetCurrentUnitOfWork();
if (unitOfWork != null) //工作单元环境
{
var domainEventConsistentQueue = DomainEventConsistentQueue.Current();
if (domainEventConsistentQueue.IsEmpty())
{
unitOfWork.CommittedEvent += AutoPublishDomainEventConsistentQueue;
unitOfWork.RollBackEvent += domainEventConsistentQueue.Dispose;
} domainEventConsistentQueue.RegisterEvent(aDomainEvent);
return;
} var registeredSubscribers = _subscribers;
if (registeredSubscribers != null)
{
var domainEventType = aDomainEvent.GetType();
List<IDomainEventSubscriber> subscribers;
if (!registeredSubscribers.TryGetValue(domainEventType, out subscribers))
{
aDomainEvent.Read(); //未找到订阅者,但是消息还是消费掉。
return;
} foreach (var domainEventSubscriber in subscribers)
{
var subscribedTo = domainEventSubscriber.SubscribedToEventType();
if (subscribedTo == domainEventType || subscribedTo is IDomainEvent)
{
Distribute(domainEventSubscriber, aDomainEvent);
}
} aDomainEvent.Read();
}
} private void AutoPublishDomainEventConsistentQueue(bool isSuccess)
{
if (isSuccess)
DomainEventConsistentQueue.Current().PublishEvents();
}

这里有一点要说明一下,因为这里的2个注册CommittedEvent的事件,AutoPublishDomainEventConsistentQueue的注册在CommittedEventHandle之后,所以当DomainEventConsistentQueue中调用Publish方法时ExcutingContext.GetCurrentUnitOfWork()已经获取到null了,就会进入到实际的发布操作。

五、使用方式

            var aggregateA = new AggregateRootA(); //从仓储中获取
var aggregateB = new AggregateRootB(); //从仓储中获取 using (var unitwork = new SqlServerUnitOfWork(GlobalConfig.DBConnectString))
{
aggregateA.Event();
unitwork.RegisterModfied(aggregateA);
aggregateB.Event();
unitwork.RegisterModfied(aggregateA); var isSuccess = unitwork.Commit();
} public class AggregateRootA : AggregateRoot
{
public void Event()
{
DomainEventBus.Instance().Publish(new DomainEventA());
}
} public class AggregateRootB : AggregateRoot
{
public void Event()
{
DomainEventBus.Instance().Publish(new DomainEventB());
}
} public class DomainEventA : IDomainEvent
{ } public class DomainEventB : IDomainEvent
{ }

这样代码又精简了些,并且隐藏了领域事件的实际发布过程,业务编码时无需关注领域事件是如何发布的。

欢迎大家继续探讨~

  

作者:  Zachary
出处:https://zacharyfan.com/archives/81.html

▶关于作者:张帆(Zachary,个人微信号:Zachary-ZF)。坚持用心打磨每一篇高质量原创。欢迎扫描右侧的二维码~。

定期发表原创内容:架构设计丨分布式系统丨产品丨运营丨一些思考。

如果你是初级程序员,想提升但不知道如何下手。又或者做程序员多年,陷入了一些瓶颈想拓宽一下视野。欢迎关注我的公众号「跨界架构师」,回复「技术」,送你一份我长期收集和整理的思维导图。

如果你是运营,面对不断变化的市场束手无策。又或者想了解主流的运营策略,以丰富自己的“仓库”。欢迎关注我的公众号「跨界架构师」,回复「运营」,送你一份我长期收集和整理的思维导图。

DDD中的Unitwork与DomainEvent如何相容?(续)的更多相关文章

  1. DDD设计中的Unitwork与DomainEvent如何相容?

    最近在开发过程中,遇到了一个场景,甚是棘手,在这里分享一下.希望大家脑洞大开一起来想一下解决思路.鄙人也想了一个方案拿出来和大家一起探讨一下是否合理. 一.简单介绍一下涉及的对象概念 工作单元:维护变 ...

  2. 初探领域驱动设计(2)Repository在DDD中的应用

    概述 上一篇我们算是粗略的介绍了一下DDD,我们提到了实体.值类型和领域服务,也稍微讲到了DDD中的分层结构.但这只能算是一个很简单的介绍,并且我们在上篇的末尾还留下了一些问题,其中大家讨论比较多的, ...

  3. DDD~Unity在DDD中的使用

    回到目录 上一讲介绍了DDD中的领域层,并提到下次要讲Unity,所以这篇文章当然就要介绍它了,呵呵,Unity是Microsoft.Practices中的一部分,主要实现了依赖注入的功能,或者叫它控 ...

  4. Repository在DDD中的应用

    Repository在DDD中的应用2014-10-09 08:55 by Jesse Liu, 98 阅读, 0 评论, 收藏, 编辑 概述 上一篇我们算是粗略的介绍了一下DDD,我们提到了实体.值 ...

  5. DDD中的分层架构

    DDD中的分层架构很好的应用了关注点分离原则Separation of Concerns(SOC),每一层做好自己的事情,减少交叉 表现层 表现层提供用来完成任务的用户界面,如webform wpf ...

  6. DDD中的EFCore

    EFCore在DDD中的使用 在DDD中,我们对聚合根的操作都会通过仓储去获取聚合实例. 因为聚合根中可能会含有实体属性,值对象属性,并且,在DDD中,我们所设计的领域模型都是充血模型.所以,在对聚合 ...

  7. 对DDD中领域服务的理解

    CZ 能不能清晰具体区分service和实体的区别 网上有人用DCI来解决 不知道对不对 STST 我复习下DDD中的服务的概念了参与讨论啊CZ 这个我也看过 但是太过于笼统 STST STST 复习 ...

  8. DDD中的聚合和UML中的聚合以及组合的关系

    UML:聚合关系:成员对象是整体的一部分,但是成员对象可以脱离整体对象独立存在.如汽车(Car)与引擎(Engine).轮胎(Wheel).车灯(Light)之间的关系为聚合关系,引擎.轮胎.车灯可以 ...

  9. DDD中的值对象如何用NHibernate进行映射

    原文:DDD中的值对象如何用NHibernate进行映射 <component/>是NHibernate中一个有趣的特性,即是用来映射DDD(Data-Display-Debuger)概念 ...

随机推荐

  1. react-router 组件式配置与对象式配置小区别

    1. react-router 对象式配置 和 组件式配置    组件式配置(Redirect) ----对应---- 对象式配置(onEnter钩子) IndexRedirect -----对应-- ...

  2. 【调侃】IOC前世今生

    前些天,参与了公司内部小组的一次技术交流,主要是针对<IOC与AOP>,本着学而时习之的态度及积极分享的精神,我就结合一个小故事来初浅地剖析一下我眼中的“IOC前世今生”,以方便初学者能更 ...

  3. ABP文档 - 目录

    ABP框架 概览 介绍 多层结构 模块系统 启动配置 多租户 集成OWIN 共同结构 依赖注入 会话 缓存 日志 设置管理 时间 领域层 实体 值对象(新) 仓储 领域服务 工作单元 领域事件(Eve ...

  4. .Net多线程编程—预备知识

    1 基本概念 共享内存的多核架构:一个单独的封装包内封装了多个互相连接的未处理器,且所有内核都可以访问主内存.共享内存的多核系统的一些微架构,例如内核暂停功能,超频. 内核暂停功能:当使用内核不多的时 ...

  5. QT5利用chromium内核与HTML页面交互

    在QT5.4之前,做QT开发浏览器只能选择QWebkit,但是有过使用的都会发现,这个webkit不是出奇的慢,简直是慢的令人发指,Release模式下还行,debug下你就无语了,但是webkit毕 ...

  6. nginx源码分析之网络初始化

    nginx作为一个高性能的HTTP服务器,网络的处理是其核心,了解网络的初始化有助于加深对nginx网络处理的了解,本文主要通过nginx的源代码来分析其网络初始化. 从配置文件中读取初始化信息 与网 ...

  7. Html.DropDownLis绑定数据库

    效果: 方法一: View: <div class="col-md-md-4"> <div class="input-group"> & ...

  8. MSYS2环境下编译X265

    HEVC(High Efficiency Video Coding),是一种新的视频压缩标准.可以替代H.264/ AVC编码,使得保持相同质量的情况下,体积减少40%左右.目前有多种实现版本,x26 ...

  9. CSS3新特性应用之结构与布局

    一.自适应内部元素 利用width的新特性min-content实现 width新特性值介绍: fill-available,自动填充盒子模型中剩余的宽度,包含margin.padding.borde ...

  10. 【干货分享】流程DEMO-补打卡

    流程名: 补打卡申请 业务描述: 当员工在该出勤的工作日出勤但漏打卡时,于一周内填写补打卡申请. 流程相关文件: 流程包.xml 流程说明: 直接导入流程包文件,即可使用本流程 表单:  流程: 图片 ...