识别领域事件

随着微服务架构的兴起,微服务设计与拆分的的最佳实践DDD已然成为大家讨论与实践的热点,整个行业都在探索如何用DDD建模来实现微服务设计。事件风暴作为最接地气的实践,在不同的项目中野蛮生长,不断演进,今天已经渐渐成熟。作为事件风暴的灵魂——领域事件,值得我们投入更多的精力去设计与打磨。

领域事件是用特定方式(已发生的时态)表达发生在问题域中的重要事情,是领域通用语言(UL)的一部分。为了方便理解这个概念,这里举一个宠物的例子:如果做为宠物主人,你的问题域是如何养好一只猫,那么是不是已经打了疫苗,给宠物饲喂食物等将成为你关注的事情,领域事件会有:疫苗已注射,猫粮已饲喂等。如果你是宠物医生,问题域是如何治好宠物的病,关注的事情是宠物的身体构成,准确的诊断宠物病情,对症下药,领域事件会有:病情已确诊,药方已开治。虽说二者关注的都是宠物,在不同的问题域下领域事件是不同的。

DDD的提出者和圈内的大师先后提到领域事件在领域建模中的价值,前沿实践者们已经开始应用领域事件来表达业务全景。在DDD建模过程中,以领域事件为线索逐步得到领域模型已经成为了主流的实践,即:事件风暴。

事件风暴是以更专注的方式发现与提取领域事件,并将以领域事件为中心的概念模型逐渐演化成以聚合为中心的领域模型,以快速可落地的方式实现了DDD建模。

对于高质量的事件风暴,首先要解决识别领域事件的问题,理想的情况下领域专家和研发团队一起参加事件风暴,从业务的视角去分析涉众关心的领域事件,短时间内高度可视化交流,集中思考,快速发散收敛形成所有参与者一致认可的领域事件集合。我在多个项目上实现事件风暴后,总结了一些坑和应对办法,供大家参考:

1. 组织没有领域专家

对问题域有深刻见解的主题专家称为领域专家,在大多数组织中没有这个角色,当DDD建模需要领域专家支持时,组织往往找业务部门的业务人员,BA,产品经理或在这个领域有多年开发经验的DEV来充当。

这些一线业务人员和开发团队都清楚有什么功能,但往往不清楚为什么有这些功能。举个例子:如果我们的问题是打开一瓶红酒,你去调研每天都会打开酒瓶的waiter, 给你的答案是:开瓶器。但换做领域专家的视角来看,会回归问题的本质,如果我们希望打开酒瓶,需要把瓶塞移除,移除瓶塞的方式有多种,包括推,撬与拉拽,对于拉拽可能基于吸力或螺旋拉拽,下面右图的开瓶器只不过是螺旋拉拽的一种解决方案。领域专家应该对问题域及其中的各种可行方案有更深入的理解。

在辅导团队的过程中,为了弥补这部分视角的缺失,往往会在事件风暴之前,组织业务愿景和场景分析,与被指派的业务干系人对齐业务愿景,一起分析业务场景背后的问题域,找到问题域的本质后再展开事件风暴。

2. 面向复杂业务系统的事件风暴

高效事件风暴的规模推荐5-8人,超过8人的事件风暴就会出现讨论时间过长,部分成员参与度不高,业务之间的相关度弱等问题。在一个以支付中台为主题的事件风暴中,对于电商商城的支付与理财产品的支付相关性就很弱,各自关心的是自己的业务,让这两组人在一起讨论,在得到同样产出的情况下,会花费双倍的时间。

在处理复杂问题时,一个有效又好用的方法就是分而治之,对于复杂系统的事件风暴也是同样如此。在业务干系人达到一定规模后,将业务干系人分成多组,组织多轮事件风暴,迭代演进领域模型也是一种不错的选择。

分组的基本原则应以业务线为线索,如果目标系统的业务干系人在同一个业务主线上,每一组人代表业务主线上的一个环节 (如下图),这种情况按照业务结点进行分组即可。对于业务相对简单的结点,可以将其与相临结点合并组织事件风暴。

当目标系统是多条业务线上的某几个公共结点,一般业务中台会出现这种情况,如支付中台要为不同的业务部门(保险,商城,还信用卡等)提供支付服务,如下图中的虚线部分。这类业务往往结点之间的边界并没有那么清楚,系统做什么与不做什么只有在梳理完整条业务线才能确认下来,这种情况按每条业务线分组展开事件风暴,然后针对多组产出结果进行统一业务概念抽象,建立系统边界内的统一事件流。

3. 业务代表或领域专家用自己的语言表达业务

事件风暴的第一个环节是让参与者头脑风暴,各自找出业务干系人关注的领域事件,对于业务干系人来讲,往往不适应把自己理解的业务按领域事件的方式表达出来,他们看到一串领域事件,也不觉得这种表达方式比传统方式直观,在这种情况下,我们就需要考虑如何引导业务共同输出领域事件。留心领域专家在表达需求过程中的一些模式:

1. 当…
2. 如果发生…
3. 当…的时候请通知我
4. 发生…时

通过模式中的关键字转换成领域事件,按时间顺序排序后,基于商业模式与价值定位与领域专家讨论领域事件,以统一的语言与统一的业务视角修正并验证领域事件。高质量的领域事件定义自然是清楚的,是可以找到问题域中的某个actor是关注它的,通过讲述领域事件是可以体现商业价值的。

4. 事件风暴可能识别不出来所有领域事件

通过事件风暴可以快速把整个问题域主线梳理出来,这样的产出是相当的高效和有价值,但对于正在尝试用事件风暴成果代替传统交付物的组织,往往会质疑事件风暴是否可以发现所有领域事件。

试考虑一个投资者,为一座摩天大楼的建造提供资金,投资者未必对建造过程的细节感兴趣,材料的选择及各种工程细节会议对于建造者来说是很重要的活动,对于投资者来讲,感兴趣的是良好的投资回报,保护投资免受风险,较为务实的投资者会设立明确的里程碑,每个里程碑通过后再做下一次注资。例如,在项目开始时,提供适量资金进行建筑设计工作。当建造事宜被批准时,再为项目提供较多的资金以进行设计工作。在设计通过评审通过后,才拔给更大量的资金,以便建造者破土动工。梳理得到事件如下:

系统建模同理,我们不关注所有事件,仅关注对干系人解决特定问题有价值的事件,并且这个特定问题应该已经在项目初期,业务愿景梳理的过程中在组织内达成了共识,就像上述投资者关注的问题一样清楚,在业务场景梳理与事件风暴的过程中,不断还原具体过程,以确保识别出的活动或事件真正可以解决业务问题。所以在事件风暴的过程中,并不需要担心是不是找出所有领域事件,只要真正解决了业务问题就好了。

另外,当开始采用新的方法论时,实践过程与角度都有差别,旧有体系的交付物不适用是常有的情况,重点关注的新的方法会不会以更简洁的方式解决实际问题。在存疑的风险处,活学活用新方法的交付物能够让组织更顺利的落地,当然必要的开发过程与交付物改进也是需要的,即可以更高效的完成设计工作,也能够让团队更专注在问题上。

总结

有人说微服务的设计与拆分是一门艺术,经验性的成份占了很大比重。当我们准备基于经验来做微服务的设计决策时,结合业务愿景,找出问题域内所有业务干系人真正关心的领域事件,展开完整的事件风暴,循序渐进的让场景变得更加具体,让经验与艺术在生动的问题域之中得到最大的发挥。

另一方面,有效地识别领域事件,既统一了语言,又助力在模型中体现出业务价值部分,为设计关注业务价值的领域模型打下了坚实的基础。


更多精彩洞见,请关注微信公众号:思特沃克

Share

[译] 当我们说“事件驱动”时,我们在说什么?

去年年底,我和ThoughtWorks同事一起参加了一个研讨会,讨论“事件驱动”的本质。过去的几年里,我们构建的很多系统都大量使用了事件。对于这些系统,人们常常赞誉有加,但批评的声音也不绝于耳。我们的北美办公室组织了一次峰会,来自世界各地的ThoughtWorks资深开发者出席会议并分享了他们的想法。

这次峰会的最大成果是认识到当人们谈论“事件”时,实际上说的是完全不同的东西,所以我们花了很多时间来梳理一些有用的模式。本文简要总结我们的成果。

事件通知

当领域内有变化发生时,发送事件消息来通知其它系统。事件通知的一个关键点是源系统并不关心外部系统的响应。通常它根本不期待任何结果,即使有也是间接的。 发送事件的逻辑流与响应该事件的逻辑流之间会有显著的隔离。

事件通知非常有用,因为它意味着低耦合,并且结构也非常简单。但是,当逻辑处理流跨越各种事件通知时,它也可能成为问题。因为没有任何代码显式地描述这个流程,所以这个流程是不可见的。通常,唯一的办法是通过监控系统来观察它。这会导致调试和修改流程变得很困难。这里的危险在于,当你使用事件通知来优雅地做系统解耦时,没有意识到更大规模的流程,而这会让你在未来几年中陷入困境。不论如何,此模式仍然非常有用,但你必须小心陷阱。

将事件用作被动操控型命令(Passive-aggressive command),就是这种陷阱的简单示例。它指的是源系统期待接收方执行一个动作,此时本该使用命令消息(Command message)来展现此意图,然而却使用了事件。

事件不需要包含太多数据,通常只有一些id信息和一个指向发送方、可供查询更多信息的链接。 接收方知道它已发生变化并且接收到关于变化的最少信息,随后会向发送方发出请求,以决定下一步该做什么。

事件携带的状态转移(Event-Carried State Transfer)

采用此模式时,可以在不需要访问源系统的情况下,更新客户端的信息。客户管理系统可能在客户修改自己的详细信息(如地址)时抛出事件,事件包含了详细的修改数据。因此,接收方无需与客户管理系统通信,就可以更新自己的客户数据副本,以进行下一步的操作。

这种模式的一个明显缺点是,有很多冗余数据和副本。但在存储很便宜的时代,这不是一个问题。我们获得了更好的弹性,因为即使客户管理系统不可用时,接收方系统仍然可以正常工作。我们减少了延迟,因为访问客户信息不需要远程调用。我们也不必担心所有来自消费端的查询给客户管理系统带来的负载。但它确实给事件接收端带来了更多复杂性,因为它必须维护所有状态,而如果它直接访问事件发送方查询信息,通常会更加容易。

事件源

事件源(Event Sourcing)的核心思想是,每当系统状态发生变化时,都将状态更改记录为事件,这样我们就有信心在任何时间都能够通过重新处理事件来重建系统状态。事件库成为事实的主要来源,系统状态完全来源于它。对于程序员来说,最好的例子就是版本控制系统。所有的提交日志就是事件库,源码树的工作副本是系统状态。

事件源引入了很多问题,我不会在这里讨论,但我想强调一些常见的误解。事件处理不必是异步的,以更新本地Git库为例,这完全是一个同步操作,就像更新Subversion这样的集中式版本控制系统一样。当然拥有所有这些提交允许你做各种有趣的事情,Git就是一个很好的例子,但核心提交从根本上说是一个简单的动作。

另一个常见错误是,假定使用事件源系统的每个人都应该理解并访问事件日志以确定有用的数据。但实际上很可能对事件日志只具备有限的了解。我正在使用编辑器写这篇文章,编辑器不知道我的源代码树中的所有提交,它只是假设磁盘上有一个文件。事件源系统中的大部分处理可以基于有效的工作副本。只有当真正需要事件日志中的信息时才必须处理它。如果需要的话,我们可以有多个不同Schema的工作副本,但通常应该在领域处理和通过事件日志派生工作副本之间做明确区分。

使用事件日志时,构建工作副本的快照通常很有用,这样你就不必在每次需要工作副本时都从头开始处理所有事件。实际上这里存在二元性,我们可以将事件日志视为变更列表或状态列表。 我们可以从一个派生出另一个。版本控制系统通常在事件日志中混合快照和增量变更,以获得最佳性能。[1]

考虑一下版本控制系统带来的价值,就很容易明白事件源有许多有趣的收益。事件日志提供了强大的审计功能(账户交易是帐户余额的事件源)。我们可以重放事件日志到某个点来重新创建历史状态。在重放时注入假设事件可以探索不一样的历史。事件源使得非持久化的工作副本(例如Memory Image)变得合理可行。

事件源也有自己的问题。 当结果依赖于与外部系统的交互时,重放事件就会成为问题。随着时间的推移,我们必须清楚如何处理事件Schema的变化。许多人发现事件处理给系统增加了很多复杂性(尽管我很想知道,工作副本派生组件和领域处理组件之间糟糕的隔离,是不是更主要的原因)。

CQRS

命令查询职责分离(CQRS)是指读取和写入分别拥有单独的数据结构。 严格地说,CQRS跟事件没有关系,因为你完全不需要任何事件就可以使用CQRS。但通常人们会将CQRS与之前的模式结合起来,因此我们在峰会上就此进行了讨论。

使用CQRS的理由是,在复杂领域中,使用单一模型处理读取和写入过于复杂,我们可以通过分离模型来简化。当访问模式有区别时(例如大量读取和非常少的写入),这一点尤其具有吸引力。但是,需要注意平衡CQRS的收益和分离模型所带来的额外复杂度。我发现很多同事对使用CQRS非常警惕,发现它经常被滥用。

理解这些模式

作为一名热衷于收集样本的软件植物学家,我发现这是一个棘手的地带。 核心问题是不同模式的混淆。 在某个项目中,一位能力很强,经验丰富的项目经理告诉我,事件源是一场灾难,任何变化都需要两倍的时间来修改读和写模型。 在他这句话中,我可以发现事件源和CQRS之间可能存在混淆,我们如何找出哪个是罪魁祸首? 该项目的技术主管声称主要问题是大量的异步通信,这当然是一个已知的复杂性助推器,但这不是事件源或CQRS的必要组成部分。 总的来说,我们必须要注意这些模式在对的地方都很好,反之则很糟糕。 但是当我们混淆了这些模式时,很难弄清楚哪里是对的地方。

我想写一篇论文来梳理清楚所有的混乱,提供强有力的指导,告诉你何时使用,以及如何使用好每一种模式。遗憾的是,我没有那么多时间。 我希望这篇文章有用,但它可能缺少你真正需要的信息。

Best Regards

文/Martin Fowler

译/梅雪松


更多精彩商业洞见,请关注微信公众号:思特沃克

Share

当Subdomain遇见Bounded Context

《实现领域驱动设计》的作者Vernon根据过去几年DDD的实战经验又写了一本《领域驱动设计精粹》,日前已经在中国翻译出版。去年底出版社找到我时,读完英文原著最终还是放弃了翻译,推荐给了其他同事,并告诉他们出版后准备接受炮火洗礼。

不得不承认Vernon的新书在构建DDD落地体系方面较之上一本有了很大的进步,全书读起来很连贯,有一定实践基础的团队或个人均可直接上手书中很多的实践。并且通过一个案例完整叙述了从需求分析开始到最后的团队迭代开发。当然迭代运作过程中的工作量估计方式,在我看来过于简单粗暴,虽然强化了架构的最终代码落地,但却可能造成一系列的僵化。

本文主要针对Vernon一直以来对Subdomain和Bounded Context的一对一映射关系进行讨论。目标是让更多同学意识到这个方面的不同声音,从而能够加深对这两个概念存在意义的理解,并建立自己的判断。

区分问题和解决方案是个老大难问题

问题和解决方案总是像一对难以分辨的孪生兄弟,一个人看到的哥哥可能就是另一个人认为的弟弟。好像程序员在开发Story时,Story成了我们要解决的问题,具体的代码实现成了解决方案;但当BA在分析同样一个Story时,问题就成了对应的业务需求,Story只是分析出的解决方案的描述。

当然这个区分有时候可能并没有那么重要,Story到底是一个问题,还是一个解决方案,其实我们在迭代过程中并不是很关心。但有时候不做问题和解决方案的区分确是十分危险的,甚至会造成整个产品的失败。这样的例子当然是一抓一大把的,比如我经常提及的为税务审计人员提供屏幕上多记录的翻页功能,就是我职业生涯中记忆最深刻的一次失误,想当然地采用了“通用”解决方案。

Eric Evan在构建DDD的体系时显然是思考了问题和解决方案这两个维度的,我相信这个过程也是十分痛苦的,以至于最后呈现在书里的实践并没有做非常明确地划分。对于后面的实践者,包括我们自己,都存在着不一样的解读。我们曾经讨论过一个DDD实践的象限划分,但由于这样的划分太过主观,结果是一组很长的邮件讨论。

象限如下图所示,这是一个如同“PHP是世界上最好语言”般的讨论,建议大家慎入,以免上火。

(从问题/解决方案和战略/战术 维度分析DDD元模型的元素)

这样的象限分类确实有点简单粗暴,但Subdomain和Bounded Context却是Eric明确定义的两个核心模型概念。Subdomain是对问题域的分解,而Bounded Context是对解决方案域的分解。这两个核心概念构建起了DDD处理真实世界复杂度的根基。

建模过程中很多同学其实是忽略Subdomain的,反正目标是Bounded Context。当问题相对简单时,Subdomain的划分确实给人感觉是自寻烦恼,划出Bounded Context后反过来推Subdomain视乎更容易上手。读《领域驱动设计精粹》时你会发现相似的逻辑,配合书中敏捷项目管理工具的案例(问题也挺简单)还是挺好用的。

那么为什么我们还要关注Subdomain,还要去区分什么Core Domain、Support Domain和Generic Domain呢?是否和Story一样,留给业务和BA就好,程序员还是应该抓紧搞完Bounded Context,然后开写微服务比较务实呢?

区分Subdomain的必要性

在帮助一个长期合作伙伴构建大规模DDD应用时,我写了一个“xx阶xx步”的体系。也成了很多咱们同事体系性学习DDD的开始。

一年半以后这个团队组织了所有的技术专家和主管让我又讲了一次这个体系。这次我花了一天时间让大家体会问题和解决方案的区别,加入了Subdomain的概念。参加团建时,我问了几个专家和主管他们怎么看之前的设计,得到更多是务实的“赞赏”。其实我并不在意具体落地时的裁剪,但希望白纸黑字时应该明确原委,这也是我为什么拒绝了《领域驱动设计精要》翻译的原因。

我经常用电商的案例让大家快速认识到Subdomain划分的重要性。大浪淘沙之后我们发现淘宝和京东依然是霸主。当年马爸爸嘲笑强哥构建人肉物流网的寓言也并没有发生,反而很多人爱上了京东自有物流的速度。当然站在马爸爸当年对电商问题的认知角度,自建物流是可笑的,毕竟他要解决的核心问题是如何让琳琅满目的中小供应商能够直接对接千千万万的用户,让用户能够更容易的发现适合的商品。

所以从一开始淘宝和京东定义的Core Subdomain就是不一样的,正是问题认知的区别让两家都活了下来,并且活得很好。我们可以看到在线物品展示,吸引消费者方面淘宝一直在引领;而行业里如果你有机会接触电商领域,会发现京东物流系统还是蛮厉害的。

这是我们多年后的今天看到的结果呈现,但其实真正决定命运和格局的确是多年前两家电商对自身核心问题的理解。这个认知驱动出了两家完全不同的成功电商。

很多同学会说这玩意儿是商业模式,也轮不到我们搞研发的参与。我们拿到的都是既定问题了,再识别Subdomain也没啥意义了。这个论断有两方面问题:

  • 作为产品和服务的实现者,如果都不参与和关注问题本身的划分及核心子问题的认知,那么你很可能在浪费自己的时间,开发出未来被边缘化,甚至淘汰的系统。这不是危言耸听,在我的最近咨询过程中已经鉴证了很多次,比如在这个移动优先的时代去强化PC应用的技术架构。
  • 其次在这个软件应用空前发展的时代,始终抱着所有模块都必须是“自研”,所有代码都必须自己写的思想,毫无疑问只能成为“小作坊”。构建现代的复杂系统已经逐步成为一个生态工程,随着数字化服务的普及,识别哪些领域应该直接外购使用也成为了开发团队的重要能力,构建一个典型的移动应用应该没有人再会去重头写一个二维码扫描模块,而是学会从市场上选择适合的软件包。

那么什么地方应该建,什么地方应该买,应该如何决定呢?这时候我们会发现Subdomain的划分就非常有指导意义了。类似二维码扫描这样的Generic领域显然应该是外购的,而当年京东对电商的理解来看物流系统是要自建的。同样道理还有上次DDD China大会来分享的盒马生鲜,半年时间已经重写了三次核心ERP系统。不去思考问题划分的同学们会觉得盒马疯了,ERP在外部看来是多么成熟的软件包啊~ 但事实上盒马生鲜的本质就在如何解决生鲜食品的高效配送上,也可以说是一家特殊的物流公司。

小结一下,即使区分问题和解决方案很抽象,划分子问题很烧脑,我们还是必须认识到分析问题本身的重要性和必要性。借用雷布斯的成名句“不要用战术上的勤奋掩盖战略上的懒惰”!

Subdomain和Bounded Context的对应关系?

探讨了Subdomain的必要性,自然我们需要分析和解决方案这边Bounded Context分解的关系。第一次看Eric构建的DDD模型脑图(如下)时,我一直认为少画了Subdomain和Bounded Context的对应关系。最早采用DDD时,个人认知是一个Subdomain下应该有多个Bounded Context,即当我们分析出了一个子问题后在针对建模的解决方案进行分解,成为多个Bounded Context。所以Subdomain:Bounded Context应该是1:N的关系。

(Eric构建的DDD模型脑图)

然而Vernon一直以来的实践方式隐含着1:1的对应关系。这样的对应关系并非没有道理,如果咱们从一个Bounded Context出发,我们会发现每个Bounded Context必然应该是“解决”部分问题的,而这个部分问题是否就应该是一个Subdomain呢?

当我们拿着这个差异去跟Event Storming的发明者Alberto Brandolini讨论时,发现对方委婉地表达了N:N的理解。简而言之没有直接的对应关系。当然这种理解隐含了一个Bounded Context是可以服务于多个Subdomain子问题的。比如“产品展示”Bounded Context的模型可能服务于产品销售和产品评论两个Subdomain子问题。

这三个对应关系的理解暴露出了大家对问题和解决方案这个老大难问题的纠结~ 当然最简单的是能够建立一对一的映射,作为解决方案高手的程序员们显然是非常喜欢这个模式的。以至于很多用DDD建模的程序员直接就跳过Subdomain搞起了Bounded Context。当然这也是我坚决反对这样简单化映射关系的重要原因。

出于对方法实操性的考虑,我仍然认为一对多的映射是最优的选择。诚然在我们的现实世界里,问题和解决方案是没有必然对应关系的,他山之石可以攻玉也是古来有之的。但软件设计本身就是一个问题抽象的过程,这个抽象一定会选取一个视角,也就会放弃部分信息。在这样的认知下,其实我并不介意在不同子问题的解决方案里存在一定的重复。

所以,如果让我来站队Subdomain和Bounded Context的对应关系,我仍然会选择一对多。在准确性和易用性之间寻求一个平衡,并保证大家能够更多的关注问题本身。

坚持持续认知问题

Subdomain和Bounded Context的讨论随着DDD实践的深入会进一步被大家所讨论,不论大家是否能够共识,这样的讨论都是有好处的。作为软件开发的从业者,在面对这个越来越多不确定性的数字化时代,认知问题本身将越来越重要。

Subdomain和Bounded Context在实际认知过程中一定也是相辅相成,逐步清晰的两个概念。Bounded Context建立一定是针对Subdomain的;而Subdomain的划分又会通过Bounded Context的模型得到持续地验证。


更多精彩洞见,请关注微信公众号:思特沃克

Share