最后更新:2020-03-24 14:39:41 手机定位技术交流文章
域驱动设计(DDD)是Eric Evans提出的一种软件设计方法和思想,主要解决业务系统的设计和建模。DDD有大量难懂的概念,尤其是出于翻译的原因。有些词非常粗糙,如:模型、绑定上下文、聚合、实体、价值对象等。
事实上,DDD的概念和逻辑并不复杂。许多概念和名词被引入来解决一些特定的问题,并且与面向对象的思想兼容。可以说,DDD也是面向对象思维的子集。如果遵循奥卡姆剃刀的原则,“如果没有必要,不要添加实体”,我们将首先放弃DDD的概念,并在必要时从案例研究中引入这些概念。
从纸笔思考信息技术系统的工作逻辑
让我在餐馆吃饭时对计算机软件和建模有了更深的理解几年前,我还在一家初创公司负责餐饮软件服务器端的开发。因为工作的原因,我经常在外出就餐时仔细观察餐厅的点餐系统,从而改进我们自己产品的设计。
在一个意外的情况下,我们吃饭的餐馆停电了。幸运的是,它对我们白天的用餐没有影响。当收银机系统无法工作时,我突然对这家商店如何保持其业务运转感到好奇,所以我饶有兴趣地等待服务员接受我们的订单。
的故事没有超出预期。服务员拿起纸和笔,平稳地点了餐,撕下复写纸,递给后面的厨房。我这时候才清醒过来。
软件工程师没有创造任何新东西。他们只是数字世界中的泥瓦匠。计算机系统中的逻辑过程使人类在断电后使用纸和笔变得合乎逻辑。
符合现实世界的逻辑和规则。用鼠标和键盘代替纸和笔是软件设计的基本逻辑。如果我们只关注数据库的添加、删除、修改和检查(CRUD),但实际上我们没有正确识别业务,这就是代码组织混乱的根本原因。
会计、餐饮、购物、人事管理、仓储,这些都是各个领域的真实事件,分析业务逻辑,找出固定模式,将它们抽象成计算机系统中的对象并存储起来。这是DDD软件开发的一般过程和面向对象的思想。你可能会想,这不是我们通常做的吗?
的现实是,我们经常立即密切关注数据库的设计,理所当然地设计一些数据库表,然后进行接口、网络请求和如何操作数据库。业务逻辑被封装到一个名为服务的对象中,该对象不携带任何状态。业务逻辑是通过修改数据库来实现的

(一个简单的应用系统)
一般来说,这种方法没有什么大问题,甚至工作得很好。福勒称这个方法为事务脚本还有其他设计模式。用户界面、业务逻辑和数据存储被视为一个“模块”。简单的编程可以通过拖动用户来实现。net和VF提供了这种设计模式。这种设计模式被称为SMART用户界面。
有一些优点
非常直观,开发人员在学习了编程的基本知识和数据库的CRUD操作后可以高效地开发,并且可以在很短的时间内完成应用程序开发模块之间的完全独立问题是这种模式在业务复杂时会带来一些问题。虽然
最终是对数据库的修改,但是中间有很多业务逻辑,并且没有很好地封装。客人还盘并不像把菜从订单上拿走那么简单。订单的总金额需要重新计算,并且需要通知厨师尝试撤回正在做的菜。
没有长眼睛的新手程序员未经授权修改数据片段,整体业务逻辑被破坏。这是因为没有真正的“顺序”对象来负责执行相关的业务逻辑,并且Sevice上的方法直接修改数据库来维护业务逻辑的完整性,这完全取决于程序员对系统的理解

(由简单的添加、删除、修改和检查引起的业务逻辑问题)
当我们在各种餐馆交谈时,我们发现这不是it系统问题在一些管理不善的餐馆里,所有的服务员都可以结账离开,而不是去收特别税。税务局职员划掉了盘子,没有更新小计,其他服务员在结账时会出错。根据编程语言,这些餐厅员工的职责不明确,不符合一些面向对象的原则。
我们将这些业务逻辑融入到信息技术系统中,意识到系统中有一些隐藏的模型:
点菜我们决定抽象出点菜和点菜的对象,不能直接修改菜,只能通过点菜。在任何情况下,菜肴状态的变化都将通过订单来完成。
复杂系统的状态是明确定义的。服务负责处理各种应用场景的差异,模型对象处理一致的业务逻辑。在
接触到Eric Evans的DDD概念之前,我们没有找到这种开发模式的名称,这种开发模式暂时被称为幼稚模型驱动开发。

(最基本的存储库模式)
模型和域模型
从上述示例中,模型是一个可以表达系统业务逻辑和状态的对象
模型是一个非常宽泛的概念,任何东西都可以是一个模型,我们试图给这个模型下一个定义,然后继续缩小领域模型的概念外延。
模型是一个用来反映事物某一部分特征的对象。古代的人们,无论是真实的还是虚拟的,都使用八个占卜符号作为世界运行规则的模型。地图使用线条和颜色作为地理信息的模型。信息技术系统使用电子病历作为对象或数据库表关系的模型;
我们知道,为了打造一个可持续的信息技术系统,我们实际上需要对业务进行充分的抽象,找出这些隐藏的模型,并将它们移入系统。如果餐厅发生的一切都必须在系统中找到相应的对象,那么系统的业务逻辑就非常完整。
现实世界业务逻辑在分析信息技术系统业务时适用于某个行业和领域,因此也称为领域。
域是指特定行业或场景中的业务逻辑
DDD中的模型是指反映信息技术系统的业务逻辑和状态的对象,是从特定的业务(领域)中提取出来的,因此也称为领域模型。
专注于实际业务,而不是立即进行数据库和程序设计。通过识别固定模式并将这些业务逻辑的承载抽象到一个模型中这个模型负责处理业务逻辑和表达当前系统状态这个过程是领域驱动的设计
我从中学到了什么?
我们构建的计算机系统实际上取代了现实世界中的一些操作。根据面向对象的设计,我们的系统是一个电子餐厅。真实餐馆中的实体应该与我们的系统相对应,并用于承载业务,如收银员、顾客、厨师、餐桌和菜肴。这些虚拟实体表示系统的状态,并且在某种程度上可以引用系统。这是模型。如果找到这些元素,就很容易设计软件。
后来,如果我想不清楚任何业务逻辑,我会切断电源,假装成服务员,用纸和笔沿着业务流程走。
分析业务、设计领域模型、编写代码这是领域驱动设计的基本过程。稍后,我将介绍如何设计领域模型。在我们建立了领域模型之后,我可以考虑使用领域模型来指导开发工作。
指导数据库设计指导模块分包和代码设计指导RESTful应用编程接口设计指导交易政策指导特权指导微服务部门(如有必要)
(使用DDD的通用模式)
在我们之前的示例中,收银员负责处理收银员的操作,并表达了该餐厅的收入状况。收税员收到钱并将其记录在账簿中,账簿负责处理记录钱的业务逻辑,并表示系统中有多少钱的状态。当
分析域模型时,请切断电源。当我们开发业务系统时,大多数人会同意这样的观点,即在明确设计业务和模型之后,开发它们会容易得多。
但在实际开发过程中,我们必须分析业务并处理一些技术细节,如如何响应表单提交、如何存储到数据库、如何处理事务等。
在使用域驱动设计方面还有另一个优势。我们可以隔离这些技术细节,首先进行业务逻辑建模,然后完成技术实现。因为业务模型已经建立,技术细节只不过是对用户操作和持久模型的响应。
我们可以将系统的复杂问题分为两类:
服务复杂性技术复杂性
(技术复杂性和服务复杂性的分离)
技术复杂性、软件设计和技术实现相关的问题,如处理用户输入、持久性模型、处理网络通信等。
业务复杂性、软件设计和业务逻辑相关问题,如向订单添加商品、需要计算订单总价、应用折扣规则等。
我们在分析业务和模型时,过于关注技术实现,这将带来很大的干扰。我学到的最实用的思维方式是在这个过程中切断“电”。技术复杂性中的用户交互想象人机对话,坚持想象用纸和笔记录。
DDD还强调业务建模应该完全与业务专家相关联,而不应该仅仅是软件工程师的自我改进。业务专家是一个虚拟角色,可能是一线业务人员、项目经理或软件工程师。
由于建模是与业务专家一起完成的,请尽量不要使用非常专业的绘图工具和技术语言。DDD只是一个建模的想法,并没有指定要使用的具体工具。我在这里使用PPT的线条和形状以电子逆向的方式表达领域模型,如果每个人都熟悉UML,这也是可能的。即使在实际工作中,我们也广泛使用便利贴和白板来完成建模工作。
这个建模过程可以由技术人员和业务专家一起讨论,也可以通过使用研讨会(如“事件风暴”)来完成
是一个非常重要的过程,DDD称之为协同设计。
通过这个过程,我们得到了域模型

(原始域模型)
上图使我们能够通过业务分析获得非常基本的域模型。在我们的点餐系统中,将会有几种模式,包括座位、点餐、菜肴和评估。一个座位可以有多个订单,每个订单可以有多个菜肴和评价
同时,菜品将由不同的订单使用。
上下文,歧义,统一语言
我们使用这个模型开发系统,并使用领域模型驱动开发。与事务脚本相比,它简单明了得多,但仍然存在一些问题。
有一天,市场告诉我们这个系统会有逻辑问题。即使系统中的菜肴被删除,也无法查看订单。在我们以前的认知中,订单和菜肴是一种多对多的关系。盘子不存在。这个订单有什么用
菜在这里有致命的歧义!!!这里的菜肴实际上有两层含义:
在订单中,这个消费项目的记录被表示出来,也就是订单项目。例如,一份鱼香肉丝在表5中食用在菜品管理方面,价格是30元的鱼香肉丝,包括菜单图片、文字说明和折扣信息。菜品管理中的菜品下架后,不会产生新的订单,同时也不会对订单中的菜品造成影响。
这些问题是由于技术专家和商业专家之间没有统一的语言。DDD认识到这个问题。统一的语言是实现一个好的领域模型的前提,所以它应该“大声建模”在这个过程中,我目睹了许多有意义的争吵。正是这些争论使得领域模型更加清晰。
这个过程称为统一语言

(域模型v2)
与现实生活中的相同。歧义的原因是我们的谈话发生在不同的语境中,我们必须在特定的语境中谈论一个概念,它才有意义。在不同的场景中,即使使用的词是相同的,业务逻辑的本质也是不同的。想象一下在《武林外传》中与傅客栈的对话

(对话)
在这个对话中实际上有三个上下文。“dish”这个词在这里出现了三次,但实际上商业的含义是完全不同的。
大嘴巴说要买食物。这里的食物应该被抽象为食品原料购买。如果店主管理这种食物,它应该有购买者,名字,购买者,购买价格等。这位学者说,实习生算错了账单上的菜的价格,这位学者需要管理账单。这里的菜肴应该是指账单科目,实际上通常是一个会计科目。老白说客人点了一份腌鸭。这里,老白关注订单下面的订单项目。订单项目中包含的属性包括价格、数量、小计、折扣和其他信息。事实上,货架上有一个隐藏的模型——商品店主需要在客人点菜之前把菜加到菜单上。这种商品是我们通常的概念商品。
我们再次统一了语言,得到了一个新的模型。

(域模型v3)
4在以红色虚线为界的区域中,我们都可以使用“dish”一词(尽量不要这样做),但每个人都清楚“dish”有不同的含义这个区域被称为上下文当然,语境不仅由歧义决定,还可能由完全不相关的概念产生。例如,订单形式和座位的实际概念之间没有很强的相关性。当我们谈论座位时,我们完全是在谈论其他事情,所以座位也应该是一个单独的上下文。
将上下文的边界确定为DDD最困难的部分,并且上下文边界随着业务变化而动态变化。我们称识别边界的上下文为有界上下文。边界上下文是一个非常有用的工具。边界上下文可以帮助我们识别业务的边界并进行适当的分割。
界限上下文的识别很难有一个明确的标准,上下文的界限很模糊,需要有经验的工程师和充分的讨论才能得到一个好的设计。同时,应该注意的是,划分边界上下文没有对错,只有它是否合适。跨界背景下的模型关联存在本质差异。稍后我们将使用虚线来标记差异。

(域模型v4)
在使用上下文后带来了另一个收获模型之间基本上没有多对多的关系。如果有,这意味着有一个隐含的成员关系。这种关系没有得到充分的分析,会给以后的发展带来很大的麻烦。
以上的模型集合了根、实体和值对象
,特别是解决了歧义问题后,在实际开发中得到了很好的应用。然而,仍有一些问题没有得到解决。在实际开发中,每个模型的身份可能不同。订单项目必须取决于订单的存在。如果它们能够反映在领域模型图中,那就更好了。
例如,当我们删除一个订单时,订单项目应该一起删除,并且订单项目的存在必须依赖于订单的存在。这样,业务逻辑是一致和完整的。除非有特殊的业务要求,否则免费订单项目对我们没有任何意义。
为了解决这个问题,模型不再被平等对待。我们一起考虑高度相关的领域模型,数据的一致性必须解决,生命周期也需要保持同步。我们称之为集合聚合。在
汇总中,应选择一名代表负责全球沟通,类似于部门的接口人员,以确保数据一致。我们称这个模型为聚合根当聚合服务足够简单时,聚合可能只包含一个模型,即聚合根,常见的模型与配置和日志相关。
被称为相对于非聚合根模型的实体。

(域模型v5)
让我们完善这个图。聚合还通过虚线链接,用橙色标记聚合根识别聚合根需要一些技巧
聚合本质上也是一个实体,属于领域模型,用于承载业务逻辑和系统状态。一个实体的生命周期被附加到一个聚合根,并且被聚合根删除的实体也需要被删除,以保持系统一致性和避免自由脏数据。聚合根负责与其他聚合进行通信,因此聚合根通常具有全局唯一标识。例如,订单有一个订单标识和一个订单编号,订单编号是一个全局业务标识符,订单标识在聚合中关联使用。订单号用于聚合之外的关联应用程序。还有一种特殊类型的模型,它只负责携带多个值在我们酒店的例子中,如果我们需要报表来支持多国货币,我们会将纯数字的价格字段固定为价格类型。
公共字符串价格(){私有字符串单位;私有BigDecimal值;公共价格(字符串单位,十进制数值){ this.unit =单位;this.value =值;价格模型没有自己的生命周期。一旦创建,就不需要修改,因为修改会改变值本身。因此,我们将给这种对象一个构造方法,然后删除所有setter方法。
我们称模型和对象没有自己的生命周期,但仅用于将多个字段的值表示为值对象。
值对象起初不是很容易理解,但理解会使系统设计非常清晰。“地址”是一个重要的值对象订单交付时,地址中的一个属性不应该单独修改,因为修改后,这个“地址”不再是刚才的“地址”。为了判断地址是否相同,我们将使用它的具体值:省、市、土地、街道等。
值对象是相对于实体的,比较如下

(实体和值对象的比较)
还值得一提的是,模型是否被视为值对象或实体并不是不变的。在某些情况下,它需要被设计成一个实体,但是在其他情况下,最好设计成一个值对象。
地址在大型系统中充满了歧义。当订单中的接收地址为
时,不需要管理,只需要表达街道号、门牌号等信息,应该设计为价值对象。为了避免歧义,可以将其重命名为接收地址。就系统地理位置信息管理而言,它有自己的生命周期,应设计为一个实体,并重命名为系统地址。作为用户添加的用户定义的地址,用户可以根据该标识进行管理,并应被视为一个实体,并重命名为用户地址。我们用蓝色来区分实体和集合根。更新后的模型图如下:

(域模型v6)
虽然我们使用E-R来描述模型和模型之间的关系,但是这个E-R图使用的颜色和虚线与传统的E-R图非常不同。这个图暂时被称为分类实体关系图DDD没有指定如何绘制,可以使用任何其他绘制方法来表达域模型。
使用域模型来指导编程
。在学习DDD之前,应该使用一对多和多对多的关系吗?在设计RESTful API时,应该选择哪个对象作为资源地址,评估应该放在订单路径下还是单独放置?交易管理中应包含多少与订单删除相关的对象?
在没有领域模型之前,这些大概率是由经验决定的。当我们设计领域模型时,领域模型可以帮助我们做出这些指示。领域模型不仅用于编写业务逻辑代码,这对领域模型来说太糟糕了。
以下是指导软件开发的领域模型的一些方面。细节将在后面逐一讨论。
指导数据库设计
通过CE-R图,我们可以明显地设计数据库然而,仍有一些细节需要注意。
首先,在以前的认知中,多对多关系是很正常的然而,在分析了域模型之后,发现当处理多对多关系时,需要一个额外的关联表,它本质上是一个尚未被发现的“关系”实体。否则,在实际开发中会出现系统耦合,使用ORM时会出现混乱。
菜肴和点菜之间有多对多的关系吗?
如果是的话,这道菜是和订单一起上的。事实上,菜品的管理处于系统操作的上游,菜品不依赖于订单的任何操作,也就是说,不需要关心订单的任何变化。
订单有多个订单项目,每个项目都从盘子中读取数据并复制,或者引用一个盘子的全局标识(盘子被聚集在另一个盘子中)这样,当设计桌子结构时,订单与订单项目相关联,而订单项目与菜肴不相关联。点菜项目应该从程序中读取菜肴信息。经过仔细分析,多对多关系似乎变成了一对多关系。

(数据库设计)
当使用ORM时,好的领域模型特别有用不恰当的关联不仅会使ORM关联变得混乱,还会使ORM性能变差。
使用域模型建立数据库的要点:
注意多对多关系,分解成一对多关系值对象和实体。当ORM经常用于一对一关系时,聚合根和实体可以配置为级联删除和更新关联的禁止聚合根指导应用编程接口设计
RESTful应用编程接口已成为主流应用编程接口设计方法。当领域对象设计得好的时候,API设计的难度就大大降低了
使用聚合根作为URI的根路径,使用实体作为子路径将标识作为路径参数传递

(应用编程接口设计)
值对象没有标识,只能在附加到实体的路径下更新

(API design v2)
另外,根据这种关系,批量操作应该在实体的上一级完成,如批量增加订单的订单项目。可设计为:
post/orders/{ order id }/items-batch
不设计为:
post/orders/{ order id }/items/batch
guide object design
实际上,像Java和Typescript一样都有语言类型系统,对象很容易被误用如果用户对象既被用作数据库操作又被用作接口表示,那么这个类最终会变成一个具有大量可选属性的上帝类例如,
,用户在注册时需要输入一个重复的密码。如果将确认密码属性添加到用户对象中,则在存储它时不需要该属性。
因此,在DDD,应该为不同的场景设计数据库中各种对象的使用回到我们上面提到的技术复杂性和业务复杂性领域模型解决了业务复杂性的问题。领域模型应该只用于处理业务逻辑。存储和业务性能应该独立于域模型。

(对象设计)
简单来说,这些普通对象可分为三类:
DTO、用于交叉关联的实体或与后端和第三方服务的接口、数据库表映射模型、域模型。此外,在使用域模型时,还必须尽可能地注意
域对象的组合,而不是继承,这在实际业务逻辑中很少被继承。例如,菜肴的设计包括热菜、汤菜和冷菜。事实上,这不是菜肴的继承,而是分类模型应该抽象。不要滥用域模型,有些业务逻辑,找不到域模型是正常的,所以在DDD中有一个域服务。例如,生成一个UUID某些业务逻辑不支持系统业务状态。埃里克的书把它比作像加油站一样的商业逻辑。指南代码组织
代码组织,一般来说是如何分包的对DDD的狭义理解是指按照DDD风格组织代码,尽管DDD远不止于此。
长期以来,我对DDD的分包策略感到困惑。后来我意识到,在讨论DDD式分包时,有必要分别考虑单一参考和微服申请。
微服务应用程序在逻辑上是一致的,并且分离了单个应用程序
,但是微服务是一种分布式体系结构,它映射到各个应用程序,每个包都分布到不同的服务器我们首先从单个应用程序开始,然后讨论如何将单个应用程序体系结构映射到微服务。
在事务脚本模式中,我们通常将代码分为三层架构DDD有一个叫做应用程序的特殊层这一层是DDD的精髓。领域模型关注业务逻辑,而不是业务场景。
应用程序用于隔离业务场景,这非常重要例如,当用户被添加到系统中时,域模型处理:256个以上的用户被添加到系统中以授予基本权限和信用规则来创建帐户创建(三门模型,客户、用户和帐户通常是分开的)和客户数据输入
。但是,向系统添加用户是由多个应用程序场景触发的邀请256个以上用户注册用户注册管理员添加用户
应用程序需要隔离应用程序场景并组织配置域服务,以使域服务真正得到重用因此,应用程序需要进行诸如事务管理、权限控制、数据校验和转换等操作。当调用域服务时,它们应该是纯粹的业务逻辑,与场景无关
如果我们将三层体系结构与DDD体系结构进行比较,右图显示了DDD体系结构。

(三层架构比较)
我们将扩展DDD的代码架构并查看更多详细信息DDD代码实现需要像仓库和工厂这样的概念,但是这些是可选的。稍后我们将详细说明代码结构。

(单一DDD体系结构)
让我们看看DDD的单一应用体系结构如何映射到微服务体系结构

(单体到微服务)
微服务必须认为它不再是服务,域层是分离的,作为域服务器存在,域服务器不关心业务场景,因此不需要应用层应用服务器需要由后端域服务器提供的应用层。
还增加了一些DDD代码组织的基本逻辑:
使用接口隔离隔离业务复杂性和技术复杂性必要的耦合和依赖倒置/ThoughtWorks林宁
原始:https://insights.thoughtworks.cn/ddd-business-design/
本文由 在线网速测试 整理编辑,转载请注明出处。