上篇中说到了面临的问题(传送门: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. CSS3 background-image背景图片相关介绍

    这里将会介绍如何通过background-image设置背景图片,以及背景图片的平铺.拉伸.偏移.设置大小等操作. 1. 背景图片样式分类 CSS中设置元素背景图片及其背景图片样式的属性主要以下几个: ...

  2. ImageView缩放选项

    ImageView.ScaleType 将图片边界缩放到所在view边界时的缩放选项. Options for scaling the bounds of an image to the bounds ...

  3. Visual Studio Code 代理设置

    Visual Studio Code (简称 VS Code)是由微软研发的一款免费.开源的跨平台文本(代码)编辑器,在十多年的编程经历中,我使用过非常多的的代码编辑器(包括 IDE),例如 Fron ...

  4. Sublime Text 3中文乱码解决方法以及安装包管理器方法

    一般出现乱码是因为文本采用了GBK编码格式,Sublime Text默认不支持GBK编码. 安装包管理器 简单安装 使用Ctrl+`快捷键或者通过View->Show Console菜单打开命令 ...

  5. 【Win 10 应用开发】启动远程设备上的应用

    这个功能必须在“红石-1”(build 14393)以上的系统版中才能使用,运行在一台设备上的应用,可以通过URI来启动另一台设备上的应用.激活远程应用需要以下前提: 系统必须是build 14393 ...

  6. Python标准库--typing

    作者:zhbzz2007 出处:http://www.cnblogs.com/zhbzz2007 欢迎转载,也请保留这段声明.谢谢! 1 模块简介 Python 3.5 增加了一个有意思的库--typ ...

  7. Android性能优化之利用LeakCanary检测内存泄漏及解决办法

    前言: 最近公司C轮融资成功了,移动团队准备扩大一下,需要招聘Android开发工程师,陆陆续续面试了几位Android应聘者,面试过程中聊到性能优化中如何避免内存泄漏问题时,很少有人全面的回答上来. ...

  8. WebAPi之SelfHost自创建证书启动Https疑难解惑及无法正确返回结果

    前言 话说又来需求了,之前对于在SelfHost中需要嵌套页面并操作为非正常需求,这回来正常需求了,客户端现在加了https,老大过来说WebAPi访问不了了,这是什么情况,我去试了试,还真是这个情况 ...

  9. iOS逆向工程之App脱壳

    本篇博客以微信为例,给微信脱壳."砸壳"在iOS逆向工程中是经常做的一件事情,,因为从AppStore直接下载安装的App是加壳的,其实就是经过加密的,这个“砸壳”的过程就是一个解 ...

  10. 高仿it之家新闻客户端源码

    仿it之家新闻客户端界面,数据为本地假数据.仅实现了新闻模块的功能. 源码下载:http://code.662p.com/list/11_1.html 详细说明:http://android.662p ...