DDD中的Unitwork与DomainEvent如何相容?(续)
上篇中说到了面临的问题(传送门: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如何相容?(续)的更多相关文章
- DDD设计中的Unitwork与DomainEvent如何相容?
最近在开发过程中,遇到了一个场景,甚是棘手,在这里分享一下.希望大家脑洞大开一起来想一下解决思路.鄙人也想了一个方案拿出来和大家一起探讨一下是否合理. 一.简单介绍一下涉及的对象概念 工作单元:维护变 ...
- 初探领域驱动设计(2)Repository在DDD中的应用
概述 上一篇我们算是粗略的介绍了一下DDD,我们提到了实体.值类型和领域服务,也稍微讲到了DDD中的分层结构.但这只能算是一个很简单的介绍,并且我们在上篇的末尾还留下了一些问题,其中大家讨论比较多的, ...
- DDD~Unity在DDD中的使用
回到目录 上一讲介绍了DDD中的领域层,并提到下次要讲Unity,所以这篇文章当然就要介绍它了,呵呵,Unity是Microsoft.Practices中的一部分,主要实现了依赖注入的功能,或者叫它控 ...
- Repository在DDD中的应用
Repository在DDD中的应用2014-10-09 08:55 by Jesse Liu, 98 阅读, 0 评论, 收藏, 编辑 概述 上一篇我们算是粗略的介绍了一下DDD,我们提到了实体.值 ...
- DDD中的分层架构
DDD中的分层架构很好的应用了关注点分离原则Separation of Concerns(SOC),每一层做好自己的事情,减少交叉 表现层 表现层提供用来完成任务的用户界面,如webform wpf ...
- DDD中的EFCore
EFCore在DDD中的使用 在DDD中,我们对聚合根的操作都会通过仓储去获取聚合实例. 因为聚合根中可能会含有实体属性,值对象属性,并且,在DDD中,我们所设计的领域模型都是充血模型.所以,在对聚合 ...
- 对DDD中领域服务的理解
CZ 能不能清晰具体区分service和实体的区别 网上有人用DCI来解决 不知道对不对 STST 我复习下DDD中的服务的概念了参与讨论啊CZ 这个我也看过 但是太过于笼统 STST STST 复习 ...
- DDD中的聚合和UML中的聚合以及组合的关系
UML:聚合关系:成员对象是整体的一部分,但是成员对象可以脱离整体对象独立存在.如汽车(Car)与引擎(Engine).轮胎(Wheel).车灯(Light)之间的关系为聚合关系,引擎.轮胎.车灯可以 ...
- DDD中的值对象如何用NHibernate进行映射
原文:DDD中的值对象如何用NHibernate进行映射 <component/>是NHibernate中一个有趣的特性,即是用来映射DDD(Data-Display-Debuger)概念 ...
随机推荐
- 前端极易被误导的css选择器权重计算及css内联样式的妙用技巧
记得大学时候,专业课的网页设计书籍里面讲过css选择器权重的计算:id是100,class是10,html标签是5等等,然后全部加起来的和进行比较... 我只想说:真是误人子弟,害人不浅! 最近,在前 ...
- 【探索】无形验证码 —— PoW 算力验证
先来思考一个问题:如何写一个能消耗对方时间的程序? 消耗时间还不简单,休眠一下就可以了: Sleep(1000) 这确实消耗了时间,但并没有消耗 CPU.如果对方开了变速齿轮,这瞬间就能完成. 不过要 ...
- 从Script到Code Blocks、Code Behind到MVC、MVP、MVVM
刚过去的周五(3-14)例行地主持了技术会议,主题正好是<UI层的设计模式——从Script.Code Behind到MVC.MVP.MVVM>,是前一天晚上才定的,中午花了半小时准备了下 ...
- 如何利用ETW(Event Tracing for Windows)记录日志
ETW是Event Tracing for Windows的简称,它是Windows提供的原生的事件跟踪日志系统.由于采用内核(Kernel)层面的缓冲和日志记录机制,所以ETW提供了一种非常高效的事 ...
- 谈谈一些有趣的CSS题目(十)-- 结构性伪类选择器
开本系列,谈谈一些有趣的 CSS 题目,题目类型天马行空,想到什么说什么,不仅为了拓宽一下解决问题的思路,更涉及一些容易忽视的 CSS 细节. 解题不考虑兼容性,题目天马行空,想到什么说什么,如果解题 ...
- node中子进程同步输出
管道 通过"child_process"模块fork出来的子进程都是返回一个ChildProcess对象实例,ChildProcess类比较特殊无法手动创建该对象实例,只能使用fo ...
- CSS——关于z-index及层叠上下文(stacking context)
以下内容根据CSS规范翻译. z-index 'z-index'Value: auto | <integer> | inheritInitial: autoApplies to: posi ...
- RabbitMQ + PHP (二)AMQP拓展安装
上篇说到了 RabbitMQ 的安装. 这次要在讲案例之前,需要安装PHP的AMQP扩展.不然可能会报以下两个错误. 1.Fatal error: Class 'AMQPConnection' not ...
- springmvc SSM 多数据源 shiro redis 后台框架 整合
A集成代码生成器 [正反双向(单表.主表.明细表.树形表,开发利器)+快速构建表单 下载地址 ; freemaker模版技术 ,0个代码不用写,生成完整的一个模块,带页面.建表sql脚本,处理类 ...
- Node.js使用PM2的集群将变得更加容易
介绍 众所周知,Node.js运行在Chrome的JavaScript运行时平台上,我们把该平台优雅地称之为V8引擎.不论是V8引擎,还是之后的Node.js,都是以单线程的方式运行的,因此,在多核心 ...