领域驱动设计共有两个部分:战略设计和战术设计。战略设计也可理解为策略设计,是从宏观角度着眼于领域的分析设计,属于系统分析阶段,注重如何从有界上下文中寻找领域模型,战略模式由有界上下文、无所不在的语言和上下文映射组成;而战术设计属于设计代码阶段,使用聚合、实体、值对象等对象类型概念表达领域模型。
在深入探讨有界上下文之前,我们先来了解一下它在现实世界中的意义。我们知道人类是最聪明的物种,并创造了不同的国家来统治。人类文明出现之前,地球上是只有陆地,没有国家概念的,那问题来了,人类为什么要创建不同的国家,有逻辑的边界?如果存在,两国之间的边界是怎样划定的呢?
一种很好的回答是,为了将行政、文化、法律和经济分开,每个国家都将自己的人民从其他国家中抽象出来,并创造一种统一的文化和语言,就像在远古,不同的部落有自己的语言和文化一样。
在同一个国家,每个公民都遵循一些预定义的规则、语言和风格。他们懂得共同的语言、法律、货币和风格,所以我可以说,在一个国家里,公民之间是同步的,那个国家有一个统一的文化,每个公民都遵循和理解。
在领域驱动的设计中,我们引入了有界上下文,他在空间或时间上有边界的一段环境背景,它确定了每个模型的适用范围,模型体现了这个范围内的逻辑一致性,就像某个语言和货币具有特定含义的国家。在另一个国家,这种货币和语言是无法理解的,因为它们没有意义,也没有不同的含义。
这里有一个难点是:有界上下文是无形的,它不能直接反映出来,其逻辑一致性只能通过模型显现出来。模型的内部定义体现了其所在的上下文。而这个时候,我们对于模型内部的定义就非常重要,这个定义可以称为统一语言,不过这个统一语言不是在整个领域统一的语言,而是在有界上下文边界内统一的语言。
语言是DDD的核心,DDD使用语言来表达想法、探索问题并定义解决方案。如果领域是复杂的,那么这种语言将是丰富而复杂的。统一语言是在整个团队的协商下发展起来的,这种无所不在的、通用语言必须在团队成员之间的任何场合都可以表达,而且必须在领域模型中表达出来,主要体现在领域模型中的名称上。
有界上下文之间关系的处理基本原则是以松耦合、解耦合为主,因为不同的有界上下文有不同的团队、代码库、技术体系,如果两两之间过于耦合,就会发生两个团队经常在一起开会沟通、影响效率的情况,也可能是两个上下文的边界划分得不清晰,需要重新审视。当然,一个大型系统还是需要集成不同的上下文,特别是一个复杂的大型流程可能涉及不同上下文系统的集成,这时可以从集成这个角度去理解上下文关系。
上下文之间的关系有很多类型,这里只讨论常用的几种。
共享内核:指两个有界上下文共同使用一份代码内核(例如一个库)。这种方式已经很少使用,因为共享一份代码,如同共享一个数据库一样,单点风险大。
开放主机服务也是一种上下文关系的映射,也称为上下游关系映射或API调用,一个上下文通过RPC等同步方式调用另外一个上下文的API,调用者是被调用者的客户端。
发布/订阅模式:一个有界上下文是发布者,另外一个有界上下文订阅这个发布者,当发布者有事件发生时,及时将事件发布给所有订阅者(这非常类似微信,当你订阅公众号以后,它们有更新时会及时通知你),这样两个上下文之间不再互相依赖,而是只依赖事件或消息,使得两者之间实现最大化的松耦合,这也是集成模式的主要实现方式。
我们定义好我们的有界上下文时,通常会发现,我们的他是个很复杂的事情,可能会存在一个很长的流程,会存在着很多的依赖,这里需要引入子域的概念。
什么是子域?为了实现公司最终的大目标,必须在不同的小目标范围中工作,它们被称为子域,因为它们自身并不足以使公司取得成功,合起来以后共同构成了公司的业务领域。
子域分为核心子域、支持子域和通用子域。
1)核心子域:这是必须尽最大努力的地方,正是它使公司发挥作用,为公司带来价值,使公司在竞争中脱颖而出。它是最重点的地方,业务策略和规则的重点实施地。
2)支持子域:是核心子域的辅助支持,介于核心与通用之间,如果没有它,核心子域无法成功,因此,它也是非常重要的;需要内部开发或外包,因为没有现成的解决方案来实现。注意它是需要介入代码开发或业务设计的,如果只是有人协助进行配置和运维的则不属于这类。支持子域不能提供任何竞争优势,因此不应该很复杂。它的业务逻辑应该足够简单,可以通过一些快速的应用程序开发框架推出。
3)通用子域:是所有公司以相同方式执行的事务。它通常是现成的解决方案,但也可以外包或内部开发。它没有为主要业务带来特定的规则,即在大多数情况下可以作为服务采购使用,当然也可能派人参与运维配置,但是没有介入软件设计或代码修改等。
图中,是我对某个系统构建的子域划分,在这里做一下总结
如果我们需要去分析一个系统,那么我们必须要更合理的定义我们的业务问题。 这个系统是电子书生产与售卖的从业者,核心商业模式是平台从业者提供丰富优秀的书籍资源,通过移动应用,服务C端用户进行书籍阅读并支持购买。如图
这个系统存在两个用户:平台从业者和c端用户,平台从业者主要关心自己的书籍销量如何,赚了多少钱,如何可以卖更多的钱。C端用户主要关心自己想买哪本书,应该花多少钱。这两种用户关心的重点是不同的,平台从业者期望自己的书籍售卖价格越高越好,同时还可以让更多的人来买。而C端用户期望自己可以找到自己喜欢的书籍,同时买到他的价格越便宜越好。这导致了不同的业务问题,那么在这个海外系统中,需要表达出这两个问题的解决方案。
当我们从平台从业者的视角做分析,业务问题应该定义为:如何拥有优秀的内容,核心是内容,在现在商业模式下内容就是书籍,因此书籍资产是核心子域。如果我们再细化这个问题,应该描述为,如何让内容足够吸引用户,并且可以卖出更多的钱,拿到一个非常好的利润。这个业务问题,除去我们的核心书籍外,有个策略的存在,就是吸引和卖出钱拿到好利润。如果解决这个问题,对应我们上述的子域划分,我们可以通过业务问题的分解,得到一个个子域的由来。
按照整个内容部分的上下文来看,最核心的就是如何构建我们的内容,因此当内容指定是书籍的时候,书籍资源就是我们的核心子域,内容引入,内容制作,内容定级,价格子域都是支撑子域。
换个视角,当我们从C端用户的视角来做分析,业务问题应该定义为:如何可以低成本的得到内容。这个问题可以细化成,如何可以快速地找到自己满意的书籍,并将书籍以一个低廉的金钱成本转化成自己的资产。从这个问题本身,不难发现,在这里余额资产是核心的子域。问题本身,都在描述用户余额资产的过程。现在的模式主要是付费模式,所以我们把成本就定义为金钱成本。
事件风暴是围绕领域事件展开的头脑风暴会议,领域事件是什么,这是领域专家对其业务领域感兴趣的、发生的事情与情况。领域专家对数据库、网络接口或设计模式并不感兴趣,但关注业务领域中会发生的事实(动词),而领域事件就是捕获这些事实。
20世纪颇具影响力的哲学家维根斯坦说:世界是事实的总和,而非事物的总和。而传统哲学的观点是:世界是由存在着的万物组成的,他认为是世界是由发生着的事实组成的,这是他的《逻辑哲学论》中的基本出发点。“存在着的万物”和“发生着的事实”两者的区别其实是主语和谓语动词的区别,“万物”通常是主语名词,而“事实”是一种动态的过程活动,涉及谓语动词。从谓语动词中才能抽取出“万物对象”等名词的概念,他的逻辑分析观点认为:重要的不是对象,而是事实中对象之间的关系,对象所处的情势(事情的现实状况与发展趋势),对象只能在事实(对象之间的关系)中存在,离开事实的对象就毫无意义了。
在DDD建模领域,使用“领域事件”代表“事实”,表示对象之间的关系,既然领域事件表达了关系,需要涉及至少两个对象,领域事件通常也用于表达两个以上有界上下文之间的通信交流的方式。
通常情况下,人们的直觉是更关心主语,“哪些对象”发出领域事件才是人们关心的,但是维根斯坦认为应该倒过来,领域事件代表的“事实”概念重要于“对象”概念,它们的主次顺序与直觉相反。在事件风暴会议中,只有先找到发生的事实,将其标记为领域事件,才能发现这些事实涉及哪些对象,只有找到事实,才能分析出对象,对象之间的结构边界才能得到划分,而划分了边界的对象才可能是DDD中的有界上下文。
事件风暴不同于其他建模。
1)如果首先从数据建模开始,那么人们的思考和对话将很快转移到模式、事务和其他与业务领域无关的事情。
2)如果首先从行为建模开始,当人们将行为分解为任务并将其链接到流程时,会分心或不知如何下手。
虽然有很多方式来表达数据和行为的结合,但领域事件可能更合适。由于领域事件表示的是领域的事实,这些事件仅在基础业务发生更改时才会发生明显变化,因此,领域事件是DDD建模中更稳定和更具弹性的脚手架。
这里可以参考https://blog.csdn.net/MargYu/article/details/128781738?spm=1001.2014.3001.5502 ,这里对事件和命令会有进一步的描述。
从领域事件开始探索领域,按时间线向前和向后发现领域事件,探索领域事件的起源。某些事件是用户操作的直接后果,因此使用不同颜色的便签区分命令和事件。
蓝色便笺表示命令。
橙色便笺表示事件。
黄色便签表示用户角色。
在便签上写上命令或事件的名称,贴到墙上或白板上,所以需要有足够大的墙面。
如果讨论变得热烈,就需要固定前进的目标方向,以下是一些标准路线和方向。
首先,探索子域。每个专家只谈自己最擅长的子域,将领域中的其他部分留给其他人。不同的责任区域可以很好地映射到不同子域中的一部分。
其次,探索有界上下文。在讨论过程中,可能会出现一些冲突领域,具有DDD思维模式的开发人员和推动者会对同一术语有不同解释,这可能是在共享的多个同一模型之间划分边界的好时机。
然后,注意草绘用户角色。在谈论命令时,对话倾向于指向发出命令的有界上下文和触发动作的人:谁在什么情况下发出这些命令?谁在什么场景下发出这些命令?可以使用黄色便签表示主语角色。
最后,识别关键测试场景,因为除了明确定义验收测试之外,没有其他更好的方法来消除歧义。最好有一些图表输出。表格或图形对于给定用户特别有价值,只需将其绘制出来放置在与其关联的命令附近。
一个典型的事件风暴可以产出如下形式
在事件风暴会议中要有对CRUD的怀疑精神,警惕各种带有CRUD含义的领域事件,具有通用后缀(如Created,Updated,Changed,Changed和Deleted)的事件表示正在使用技术术语来描述领域而不是真实的领域术语。特别是Updated可能是最大的警告信号,它是一个非常笼统的术语,只是意味着已更改。当我们进行事件风暴时,需要试图捕获领域中的丰富性,因此应该追问“为什么更新了某些内容?”。
对CRUD命名的怀疑不仅会帮助开发更丰富的领域词汇表,改善技术人员与领域专家之间的协作,而且还将帮助发现领域内的其他流程。了解更改的每个不同原因,将发现许多领域中的见解。例如,不要用“价格更改”这样的领域事件,而是与领域专家进一步讨论,追问为什么价格会更改。你可能会发现的原因有“价格打折了”“黑五活动促销”等,使用这些业务原因来命名领域事件。其实更改或更新只是一种状态变更,而事件建模应该不是针对状态。
类似CRUD事件的还有:表单提交、按钮点按、数据库保存。这些事件也只是领域事件的技术事件,并不是真正的领域事件。表单提交(Form Submitted)可以使用带有业务含义的词语替代:工作提交;订单提交;帖子提交。虽然这些业务数据确实表现为一个通用的表单,但是使用技术表单术语超出了业务领域的边界。
当表单提交以后,通常是表单的数据创建了、更新了或删除了,这些CRUD事件只是肤浅的抽象事件,特别是某数据更新了只是一种状态更新。要培养对状态更新的敏感性,状态机几乎遍布每个领域。正在建模的概念会经历其不同的生命周期阶段,这些不同阶段使用“状态”一词来表达,虽然不能直接针对状态进行建模,但是可以通过发现状态机来发现领域事件,进而能发现领域中的上下文边界(也会成为微服务的边界)。状态机是边界的凝聚核心,领域事件是边界的边缘,与外界交互的事物。