为什么要进行领域驱动设计? - Vincent


尽管DDD的理念已经存在了10多年,但最近几天却获得了很多人的青睐。我认为这主要是因为人们开始注意到DDD所提出的思想与某类复杂性之间的关联,这类复杂性在涉及多个团队的大规模组织所构建的软件中反复出现。

尽管所有的现代架构原则和实践都为常见的问题提供了解决方案,但这种出现在软件开发核心的特殊类型的复杂性通常没有得到解决,而DDD承诺为其提供一个解决方案。

本博客的目的是强调DDD所解决的问题类型,并对该解决方案进行高层次的介绍。

柏拉图主义

让我们先来探讨一些哲学思想,我相信这些思想会帮助你获得一些关于DDD的新观点。
柏拉图认为,存在这样一个抽象物体的领域,它是非物理的(存在于空间和时间之外)和非精神的,抽象物体可以通过推理被感知。
举例来说。当我们谈论一个完美的圆时,我们感知到了完美的圆的概念(它只作为一个抽象的对象存在),尽管在物理世界中产生一个完美的圆的任何尝试在某种程度上都是不完美的。
心理或认知世界是我们看到/感觉到物理世界中的物体,并发现柏拉图世界中抽象形式的存在的地方,也就是说,完美圆的概念是被发现的,而不是被发明的!
同样,当我们看一个软件模型或架构时,我们只是看到了一个真实系统的投影,你可以把真实系统称为领域(类似于抽象对象)。

领域与软件模型
在软件中构造的模型来表示特定问题或领域称为“领域模型”,您可能已经猜到了
立即想到的问题是,“领域模型应该有多准确?”
这与“一个圆应该有多完美?”是同一个问题,这两个问题的答案都只是“对解决特定问题有用的程度”。
是的,没有办法对领域进行一对一的精确建模,但您始终可以以一种对解决手头问题有用的方式进行建模。
让我们以模拟我们自己的地球为例。

上图模型与真实
右边是地球的真实图片,虽然真实但用处不大。
但左边是地球的投影(或模型),称为墨卡托地图,尽管尺寸变形(看看绿地和非洲的尺寸),但它在确定任意两个位置的方向时非常有用。
关键是,墨卡托地图是为特定目的建模的——有一个有用的投影,可以帮助水手确定方向!
本质上,准确性不是很重要,但实用性很重要!

有效建模的工具
既然我们同意对领域/问题建模的重要性,那么我们可以使用哪些工具来进行有效建模?
建模工具可以大致分为原则、模式和哲学。

设计原则
有大量的设计原则可以指导您在结构和行为方面对软件进行建模。
例如, GRASP原则让您思考并考虑如何将职责分配给各个类,从而在低耦合的情况下实现高内聚。
另一方面,SOLID原则与 GRASP 并没有太大区别,它试图为您提供一个指南来实现——再一次,高内聚和低耦合——事实上,我认为 SOLID 只是翻新的 GRASP :-)

除了掌握和扎实之外,还有其他通用的面向对象和函数式编程原则。
本质上,所有原则都可以帮助您以高度内聚和松散耦合的方式对软件进行建模——它们是精心设计的软件的构建块。

设计模式
到处都有模式,模式是一种重复的东西。
"房子 "是一种模式,"村庄"、"城镇"、"大学 "等也是如此。
有一些特征在城镇之间是共同的。例如,每个镇通常会有一个镇中心,一个公园,一个购物中心,咖啡馆,图书馆,火车站等。
当城镇的所有设施都被放置在正确/最佳的位置时,城镇就会变得活泼而和谐。没有人希望在镇外5公里处有一个公园,也没有人希望在公园内有一个公共汽车的车站!
你看到了吗?在我们周围有一些固有的模式在重复。每个模式都存在于一个上下文中,并为上下文增加一些价值。
如果你用一种正式的语言来捕捉模式的本质,并给它一个名字,那么在交流过程中分享它就会变得非常有用,也可以复制它。
这正是 Christopher Alexander 在他的著作《The Timeless way of Building 》和《The Pattern Language》中所描述的。他在他的书中列出了大约 250 种模式,您可以开始使用这些模式进行交流和复制。
Christopher Alexander 对四位作者的帮派产生了直接影响,他们提出了一本名为设计模式的非常受欢迎的书——就像模式语言一样,它是软件中大约 23 种常见模式的目录。

现在,您可能会问这个问题:“原则和模式之间有什么区别?” 答案是模式源于原则。
原则是软件模型的基本构建块,而模式是原则之上的一层,有助于捕获、记录和共享常见问题的解决方案。


如您所见,在设计模式之上还有更高级别的架构模式,例如 CQRS 和事件溯源。

设计理念
好的,现在我们知道我们有原则和模式来帮助我们对软件进行建模,还有其他基本的设计哲学,但这些哲学中的大多数想法都可以概括为“抽象思考”
当您开始在抽象中思考并在您的代码中创建抽象时,无论是自上而下还是自下而上,您最终都会产生一个好的软件设计。
这里有一些谈论软件开发哲学的书,这些书对我如何建模/构建软件产生了很大的影响。

现有工具的局限性
好的,现在我们终于可以谈谈 DDD 了:-)
快速回顾!我们现在同意,我们有很多原则、模式和哲学,使我们能够创建良好的软件模型。
但不幸的是,如果您在处理复杂领域的大型组织中工作,上面列出的原则、模式和哲学仍然存在未解决的问题。
让我们看看仍然需要注意的一些问题。

断裂模型

如果两个工程师或团队被分配使用所有已知原则和模式对域建模的任务,他们将提出两个完全不同的模型!— 不仅模型在结构上不同,而且在语言上也不同!
这表明一个大问题,团队之间没有共同点来在某种程度上统一模型。

破碎的语言

上述“脱节模型”问题的原因之一是用于建模的语言。组织中的每个人都可以在他们的模型中自由使用任何名称和术语。
这不仅会导致模型难以理解,还会产生歧义和误解。
例如,如果您正在构建一个支付系统,您将客户向其帐户添加新借记卡/信用卡的过程称为什么? 有人可能称之为“卡链接”,有人可能称之为“卡授权”,还有人可能称之为“添加新卡”。
随着组织构建越来越多的系统而没有就通用词汇达成一致,您会看到人们使用不同的词来描述相同或有时完全相反的概念,从而导致很多混淆和误解。

分裂的人

支离破碎的语言不仅是技术团队之间的问题,它还会导致整个组织中出现更多分歧。
领域专家可能有自己的语言来表达某些领域概念,领域专家使用的词汇可能与技术专家使用的词汇完全不同。
这种分歧在大型组织中非常普遍,您可以在来自两个不同群体(技术与领域)的人们试图定义问题或探索问题的可能解决方案的任何讨论中注意到它。
如果一个人不能清晰地表达出每个人都同意的问题的定义,那么表达解决方案就更难了!

DDD(关键概念)
领域驱动设计 (DDD) 是一种通过将实现与核心业务概念的演化模型深度联系起来来开发满足复杂需求的软件的方法。
DDD 提供了很多原则和实践来解决复杂的问题,我只会尝试向您简要介绍关键概念。

无处不在的语言UL
DDD 中出现的最重要的概念之一是无处不在的语言,它试图解决上面列出的主要问题,即断裂的语言、脱节的模型和脱节的人。

无处不在的语言UL是在开发人员和组织的其他成员之间构建通用共享语言的实践。领域专家和开发人员在任何讨论中使用的相同语言和术语应该与软件模型中使用的术语相同。
这让人能够毫不含糊地清晰地表达问题和解决方案,这是一种强大的力量!
语言本身作为一种协作努力而发展,这需要有意识的纪律来保持语言的更新和管理。
整个想法听起来很简单,但这正是大多数组织所缺少的元素。一旦你将一种无处不在的语言作为领域的官方语言,你将开始在代码、讨论、测试、文档、需求等中看到它的一致使用。

限界上下文
无处不在的语言的想法应该立即让你质疑在一个大型组织中使用一种全局语言是否现实?
答案是不!我们甚至不应该尝试拥有这样一种统一的全局语言,它实际上会对我们不利并造成更多混乱!
假设您有两个团队,一个负责支付系统,另一个负责订单管理系统。这两个系统虽然可能会相互作用,但专注于解决截然不同的问题。
试图在两个团队之间共享一种共同的语言意味着每个团队都会试图将语言弯曲到非常具体的问题空间,从而完全污染语言。

每个单词/语句在特定上下文中都有意义。同样,每个领域概念在(有界的)上下文中都有含义。
有界上下文只是应用特定通用语言或领域模型的领域内的边界。
例如,如下所示,您在组织中有两个限界上下文(或子域):Billing和市场营销。
两个子域处理完全不同类型的问题,但是,在两个模型中都需要引用Customer.
尽管客户Customer的ID标识在Billing营销系统和营销系统之间是相同的,但Customer两者的定义不同Billing系统对账单地址和未结发票感兴趣,而营销系统对客户的联系方式及其最近的订单感兴趣。

当我在计费范围内提到“客户”时,很明显我指的是包含计费相关信息的客户表示。另一方面,如果我Customer在市场营销有界上下文中引用,很明显我指的是包含联系方式及其最近订单的客户表示。因此,组织中的歧义较少。
这意味着每个子域(限界上下文)都有自己的通用语言和软件模型。

DDD原则
到目前为止,我们涉及的所有 DDD 概念都是高级实践和约定,主要有助于解决组织问题。
还有一些原则可以帮助您在代码中有效地为您的领域建模。我不会涉及所有原则,但会涉及以下几个关键原则。

实体、值对象和聚合
让我们看看下面显示的订单管理系统示例。

Money是一个价值对象。在DDD社区,价值对象的形式定义是。"一个描述某些特征或属性的对象,但不带有身份概念"。
非常直接的是,Money不需要有一个唯一的标识符。20美元的金额可以由Money类的任何实例表示,其价值和货币设置为20&$。
另一方面,在你的模型中,有一些类需要一个唯一的标识符。这些类被称为实体。
实体的正式定义是。"一个对象从根本上说不是由它的属性来定义的,而是由一条连续性和身份的线来定义的"。
所有的实体都会有一个非常明确的生命周期,也就是说,它在一个特定的时间点上出现,并经历各种转变,最终可能以终止的状态结束。
在上面的例子中,当客户下订单时,订单就会出现,它必须有一个唯一的标识符,以便于客户跟踪和修改。它经历了各种状态,如待定、已发货等。最终可能以完成或取消的状态结束。
然而,一个订单的LineItem虽然也是一个实体,但因为我们需要识别确切的行项目,所以不需要在 "订单 "的上下文中是唯一可识别的。
这意味着所有需要访问LineItem的用例都应该首先访问订单,然后访问订单中的行项目--这意味着LineItem只需要在订单中拥有一个唯一的标识符。

正如你在这个例子中所看到的,订单实体(有一个全局唯一的标识符),LineItem实体(有一个本地唯一的标识符)和Money值对象共同构成了一个叫做Aggregate的对象群。

另外,注意到订单有一个特殊的性质,它是唯一可识别的实体,这使得它本质上成为外界与订单相关的任何事物互动的第一个接触点。因此,秩序虽然是一个实体,但也被称为聚合根!
正式定义:"一组相关的对象,在数据变化时被视为一个单位。外部引用被限制在聚合AGGREGATE的一个成员上,指定为根。一套一致性规则适用于AGGREGATE的边界内。"

Repository库
存储库是一种抽象,使系统能够存储、检索和搜索对象的集合(通常是聚合根)。
我见过一些案例,人们使用存储库几乎就像使用数据访问层一样,但它远不止于此,存储库应该总是被视为 "聚合根的存储库"
例如。"订单库"、"客户库 "等。
这允许你在你的领域代码中像其他领域概念一样使用资源库接口,存储聚合根只是资源库的额外责任,这是一个实现细节。
但这里的关键是,在你的领域模型中,存储库应该被视为一流的公民,而不是存储的抽象概念!

概括

  • DDD 是一种文化转变,是一种思维方式。
  • 这不是一种技术,而是一种实践。
  • 它需要整个组织的协作努力才能充分获得收益
  • 您的代码中的软件模型与您的测试一起成为您所在领域的真实来源
  • 独特的语言是关键!