架构思想之DDD

Posted 我叫小八

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了架构思想之DDD相关的知识,希望对你有一定的参考价值。

分层模式

分层模式是最常见的一种架构模式。甚至说分层模式是很多架构模式的基础,本章下面讲到的一些内容实际上都和分层模式相关联。
分层描述的是这样一种架构设计过程:从最低级别的抽象开始,称为第1层。这是系统的基础。通过将第J层放置在第J-1层的上面逐步向上完成抽象阶梯,直到到达功能的最高级别,称为第N层。
因而分层模式就可以定义为:将解决方案的组件分隔到不同的层中。每一层中的组件应保持内聚性,并且应大致在同一抽象级别。每一层都应与它下面的各层保持松散耦合。
分层模式的关键点在于确定依赖:即通过分层,可以限制子系统间的依赖关系,使系统以更松散的方式耦合,从而更易于维护。
相对于分层,还有一种概念叫分区。分层是对架构的横向划分,而分区是对架构的纵向划分。
常见的分层架构模式:

  • 单层模型:早期批处理系统会使用到。
  • 二层模型:客户端-服务器模型(C/S)
  • 三层模型:MCV(用户表示层、业务逻辑层、数据层)、网络系统常用(核心层、汇聚层和接入层)
  • 四层模型:RUP典型分层(应用层、专业业务层、中间件层、系统软件层)
  • 六层模型:功能层(用户界面)、模块层、组装层(软件总线)、服务层(数据处理)、数据层、核心层。

传统三层架构
传统的三层架构便是三层模式了,三层式的集中式架构,采用面向对象的设计方法,业务逻辑分表现层、领域层、数据访问层,这种架构很容易某一层或者几层变得臃肿,扩展性较差, 另外摩尔定律失效, 单台机器性能有限。

分布式架构

三层是的集中式架构会引发很多问题,于是便有了分布式架构,简单的说,“分工协作,专人做专事”就是分布式架构的概念。
基于某种分层策略,把系统拆分成许多个小模块,这是分布式架构必须干的事情,也是最难的事情,因为分层策略的设定会影响到整个系统的迭代发展。
以微服务来举例子,微服务的粒度应该多大 ?微服务如何设计呢?微服务如何拆分 ?微服务边界在哪里 ?
很长时间人们都没有解决这一问题,就连Martin Fowler在提出微服务架构的时候也没有告诉我们这该如何拆分微服务。
甚至在很长的时间里人们对微服务拆分产生了一些误解, 有人认为:“微服务很简单,就是将之前的单体应用拆分成多个部署包, 或者将原来的单体应用架构替换为一套支持微服务的技术架构,就算是微服务了。” 还有人认为微服务应该拆分得越小越好。
鉴于上述情形, 很多项目因为前期拆分过度, 导致复杂度过高, 导致后期难以运维甚至难以上线。
可以得出一个结论:微服务拆分困境产生的根本原因就是不知道业务或者微服务的边界到底在什么地方。换句话说,确定了业务边界和应用边界,这个困境也就迎刃而解了。
而DDD就是解决了这个确定业务边界的问题,可见DDD并不是一种技术架构,而是一种划分业务领域范围的方法论。DDD的兴起是由于很多熟悉领域驱动建模(DDD)的工程师在进行微服务设计时, 发现用DDD的思路进行业务梳理可以很好规划服务边界, 可以很好实现微服务内部和外部的"高内聚、低耦合"。于是越来越多的人将DDD作为业务划分的指导思想。
那么,什么是DDD呢?

DDD

DDD(domain driver designer,也就是领域驱动设计),它有三个关键词:领域,驱动,设计。领域,是要探索业务的边界;驱动,表示前者是后者的决定性因素;设计,包括产品设计,UIUE设计,软件设计。它不仅仅是开发架构的方案,而是完整的解决方案实施思路。正是因为它是完整的方案,才能让领域专家,产品和研发真正在同一个角度去思考和沟通,避免推诿扯皮,含糊不清。
从上文就可以知道DDD是一种拆解业务、划分业务、确定业务边界的方法, 是一种高度复杂的领域设计思想,将我们的问题拆分成一个个的域, 试图分离技术实现的复杂性,主要解决的是软件难以理解难以演进的问题,DDD不是一种架构, 而是一种架构方法论, 目的就是将复杂问题领域简单化, 帮助我们设计出清晰的领域和边界, 可以很好的实现技术架构的演进。下面为常用的的设计架构图,先不用刻意去理解名词,后面会逐步说明。

既然是领域驱动设计,那么说明设计是最重要的。比如产品经理提出了一个需求,我们一般都会以自底向上的数据驱动模式来开发,也就是先建好数据库,再进行编程。而领域驱动则是一种自顶而下的模式,先设定好领域内的功能,设定业务活动,最后才去数据库建表。
DDD有两个设计理念,战略设计和战术设计(一般用四色建模法,用例分析法,事件风暴,领域故事讲述法)

  • 战略设计:主要从业务视角出发,建立业务领域模型,划分领域边界,建立通用语言的限界上下文,限界上下文可以作为微服务设计的参考边界。
  • 战术设计则:主要从技术视角出发,侧重于领域模型的技术实现,完成软件开发和落地,包括:聚合根、实体、值对象、领域服务、应用服务和资源库等代码逻辑的设计和实现。

可以看出,上面两个设计理念主要是为了划分出模型与边界,一般情况下我们可以通过三步来确定领域模型和微服务边界:

  1. 在战略讨论中(比如事件风暴)中梳理业务过程中的用户操作、事件以及外部依赖关系等,根据这些要素梳理出领域实体等领域对象。
  2. 根据领域实体之间的业务关联性,将业务紧密相关的实体进行组合形成聚合,同时确定聚合中的聚合根、值对象和实体。
  3. 根据业务及语义边界等因素,将一个或者多个聚合划定在一个限界上下文内,形成领域模型。限界上下文之间的边界是第二层边界,这一层边界可能就是未来微服务的边界,不同限界上下文内的领域逻辑被隔离在不同的微服务实例中运行,物理上相互隔离,所以是物理边界,边界之间用实线来表示。

战略设计

在某个领域里,核心围绕上下文的设计,主要关注上下文的划分、上下文映射的设计、通用语言的定义。
通俗来说就是指在某个系统内,核心围绕子系统的设计,主要关注子系统的划分,交互方式,核心术语的定义。

限界上下文分离领域模型

限界上下文原文说它是语义和语境上的边界,个人理解是,它是在描述组织交付出来,面向客户的交付边界。如果是在SaaS场景,一个限界上下文应该是一个独立交付的软件;在PaaS场景,它应该说的是一个独立售卖的模块。它的含义是找到一个边界,要把这个边界以外的当成是无法改变的客观环境,不要幻想这个边界以外的人会配合你一起完成交付。那这一步设计就很好理解了,就是找到你业务对外承诺的边界,你要发展的业务在这个边界内,而不在此之外。如果你是对内交付的系统,那么你对其他同事交付的业务边界,就是你的业务限界上下文。
一个组织里,最核心的限界上下文被称为核心域。通常除了它,还有通用子域和支撑子域。通用子域是很成熟的业务,通常可以外包或者购买现成的解决方案,比如搜索子域可以通过ES来支持;支撑子域通常没有现成产品,但是它没有核心域重要,因此也可以一定程度的外包,避免在核心域之外浪费资源,比如大多数公司的数据库中间件是在开源产品上做了一些定制开发和维护。
限界上下文这个概念的目的是为了在业务扩展的时候,防止向领域内注入概念,导致业务变得没有边界,纠缠在一起。

限界上下文发展通用语言

当我们有了业务的限界上下文以后,就需要在这个限界上下文中发展一种语言用于表达软件模型,这个语言就叫做这个限界上下文里的通用语言。它可以是任何计算机语言、人类语言或者图形,只要能让团队内的每个人都能看懂。
通用语言不止是名词,它应该使用一系列具体的模型场景来描述领域模型。它描述了各种业务组件(不是技术组件)做什么,而不是用例或者用户故事。
比如微信朋友圈点赞这个场景,通用语言可能是:用户可以通过点赞,使得某个朋友圈的Feed发出人收到被点赞的通知,达到互动的目的。

使用上下文映射来集成多个限界上下文
上下文映射是两个限界上下文之间的连线,表示了这两个概念之间的关系,也表示这这两个概念的通用语言的翻译。通常来说,不同的限界上下文是不同的团队在维护,那么此时它也代表着两个团队之间合作的关系。
我们常见的映射关系是RPC接口。然而在领域设计里,限界上下文之间使用RPC是有风险的方案,因为会承受网络风险,还意味着两个限界上下文之间存在紧耦合。如果系统A阻塞请求系统B,B又请求C,就很容易导致集成火车事故:火车里某一节车厢有问题就会变成整列火车的问题。
最好的限界上下文映射关系采用事件的订阅,但是这要求领域专家在设计的时候就考虑不同领域之间通知的延迟对于业务的影响,以及如何消除影响。如果不采用DDD的方式,领域专家通常无法意识到领域之间的同步成本,技术人员也很容易一头撞进集成火车里。

使用子域处理遗留系统

我们代码不是在真空里运行,它们免不了会跟一些遗留系统打交道,这些遗留系统的边界并不清晰。因此我们会将遗留系统放到一个子域里,把它们的问题放到我们的设计之外。这一步做完后我们的图案与之前没有本质上的区别,无非是多了一点子域。

战术设计

核心关注上下文的实体建模,定义值对象,实体等,也就是对战略技术的基础上对类的具体的设计的讨论。

重点名词

实体

描述了领域中唯一且可持续裱花的抽象模型。
白话解释就是具有唯一id跟值对象组成的类,比如订单对象,并且拥有实现业务逻辑的方法。

值对象

不关心唯一值,具有检验逻辑,等值判断逻辑,只关心值的类。
比如订单对象里的收货地址对象,这个地址包括了收货人的一些信息。

聚合

实体跟值对象会形成聚合,每个聚合一般是在一个事务中操作,一般都有持久化操作。比如买东西这个操作,有下单,支付等操作,买东西就相当于聚合。

领域服务

领域服务是用来协调领域对象完成某个操作,用来处理业务逻辑的,它本身是一个行为,所以是无状态的。状态由领域对象(具有状态和行为)保存。
上面也说了,领域对象是具有状态和行为的。那就是说我们也可以在实体或值对象来处理业务逻辑。那我们该如何取舍呢?
一般来说,在下面的几种情况下,我们可以使用领域服务:

  • 执行一个显著的业务操作过程
  • 对领域对象进行转换
  • 以多个领域对象为输入,返回一个值对象。

在使用领域服务时,我们又要避免过度使用,因为这样会导致又回到了传统的三层架构的老问题。

领域事件

领域事件可以理解为是发生在一个特定领域中的事件,是你希望在同一个领域中其他部分知道并产生后续动作的事件。但是并不是所有发生过的事情都可以成为领域事件。一个领域事件必须对业务有价值,有助于形成完整的业务闭环,也即一个领域事件将导致进一步的业务操作。
比如上面买东西后“订单支付成功”就是一个领域事件。这时候其他聚合(比如发短信给用户)就可以通过订阅这个领域事件来达到对应关联操作。

聚合设计

一个限界上下文里通常有多个聚合,聚合逻辑上是相对独立的。怎么理解聚合的概念呢?
在DDD实践中,聚合是事务的边界,并且聚合之间并不保证事务,只能用最终一致性。任何需要事务保护的逻辑都应该在一个聚合内。
在限界上下文里,将其他聚合能力整合在一起对外提供能力的聚合,被称为聚合根;其他聚合也被称为实体。

由于聚合是事务的边界,那么每个聚合在设计阶段,最重要的是找到业务的不变性,也就是说,在事务提交前后,数据的约束条件。比如说,你在知乎对一条回答点赞,那么这条回答的点赞数量必须立刻多1,那么点赞的动作和点赞的计数,就应当在一个聚合内。

在聚合被设计出来以后,我们的模型图看起来会是这样的:

领域事件设计

我们说聚合之间要采用最终一致性,而通常的做法是采用领域事件实现最终一致性。领域事件的名称应该采用通用语言命名,才能符合领域专家的心智。完整的时间名词应该是名词和动词构成的,动词应该是过去时。领域事件的名字和属性应该能够完整描述这个事件的含义。
事件里通常至少包含业务动作和其业务参数,也可以增加更多的下游关注的事件信息,避免下游为了完成处理还需查询。
对于领域事件,我们可以这样理解:
通过将领域中所发生的活动建模成一系列的离散事件,并将每个事件都用领域对象来表示,来跟踪领域中发生的事情。也可以简要理解为:领域事件 = 事件发布 + 事件存储 + 事件分发 + 事件处理。

事件风暴

事件风暴(Event Storming)于2013年首次被提出,2015年被ThoughtWorks技术雷达添加到“实验”阶段,2018年被ThoughtWorks技术雷达添加到“采纳”阶段。
事件风暴是一种快速探索复杂业务领域和对领域建模的实践。
事件风暴从领域中关注的业务事件出发,在此过程中团队经过充分讨论,统一语言,最后找到领域模型。
那到底什么是领域中关注的业务事件呢?
以宠物为例,如果做为宠物主人,你的问题域是如何养好一只猫,那么是不是已经打了疫苗,给宠物饲喂食物等将成为你关注的事情,领域事件会有:疫苗已注射,猫粮已饲喂等。
如果你是宠物医生,问题域是如何治好宠物的病,关注的事情是宠物的身体构成,准确的诊断宠物病情,对症下药,领域事件会有:病情已确诊,药方已开治。虽说二者关注的都是宠物,在不同的问题域下领域事件是不同的。
如果在通用语言中存在“当a发生时,我们就需要做到b。”这样的描述,则表明a可以定义成一个领域事件。领域事件的命名一般也就是“产生事件的对象名称+完成的动作的过去式”的形式,比如:订单已发货(OrderDispatchedEvent)、订单已收货和订单已确认(OrderConfirmedEvent)等事件。

领域事件可以是业务流程的一个步骤,例如订单提交,客户付费100元,订单完工等。领域事件也可以是定时发生的事情,例如每晚对账完成。或者是一个事件发生后引发的后续动作,比如确认收货7天后自动将钱打到卖家账户,比如客户输错密码三次后发生锁定账户。

事件风暴流程

物料准备

在事件风暴开始之前,需要准备以下物料:

  • 便利贴:一大堆,最少要四五种不同的颜色;
  • 记号笔:人手一支,用于在便利贴上写写写;
  • 白板:最好足够长,用来贴便利贴;
  • 开放空间:用于小组成员之间的充分讨论。

参与人员
组织者:组织者应当熟悉事件风暴的整个流程,能够组织大家顺利完成事件风暴;

领域专家:领域专家应该是精通业务的人,在事件风暴过程中,要负责澄清一些业务上的概念,思考业务上有没有遗漏的事件;

项目成员:负责开发这个项目的成员,所有角色都可参加,比如BA、QA、UX、DEV。因为事件风暴可以快速让整个团队了解整个项目的业务流程。

寻找领域事件

工作坊由寻找领域事件开始。领域事件一般用橘色的便利贴表示,书写领域实践的规则是使用被动语态,并按照时间顺序贴在白纸上。
最开始可能很多成员都不知道该怎么写,或者不知道该怎么寻找领域事件。可以由组织者写下领域中发生的第一个事件。其它参与者会迅速的开始模仿,这时我们可以让大家快速的进入状态。
在遇到有疑惑的事件时,不必长时间阻塞在那里讨论,把它作为标记记下来即可,后续再进行重点优化。可以贴一个比较醒目的便签纸(比如紫色)在事件旁边。
随着我们对业务认识的不断加深,可以随时回顾和总结之前添加的内容,对于有问题的描述进行更正,对于表述不清楚的内容可以进行重写。
事件是有相对顺序的。可以把一系列有相对顺序关系的事件放在一行上,从左到右排好。这样有助于梳理领域事件,查看是否有遗漏。

寻找命令和角色

在收集完领域事件后,我们可以在此基础上进一步探索系统核心事件的运行机制。这里我们在之前的领域事件的基础上加入指令和角色的概念。

指令代表系统中用户的意图、动作和决定,一般用蓝色的便利贴表示;角色表一类特定用户,一般用黄色便利贴表示。它们之间的关系是“角色”发送“指令”产生了“领域事件”(指令也可由外部系统触发,外部系统通常用粉色的便利贴表示)。
通常来说,一个命令将对应到我们后续应用程序开发的一个API。
在寻找命令和角色的过程中,你可能会遇到某些命令会在“特定的条件下”触发。比如:“当用户通过新的设备登入时,系统会发送提醒通知”。通常,我们将这种系统的行为逻辑称为策略,通常记录在紫丁香色的便利贴上,放在命令旁边。
寻找领域模型和聚合
当我们做完了上一个环节,就可以开始寻找系统中的领域模型和聚合了。我们把跟一个概念相同的指令和事件集合到一起,并用黄色的较大的便利贴表示领域模型。

把跟这个领域模型相关的命令放到左边,事件放到右边。需要注意的是,这个时候会去掉“事件的相对顺序”这个概念,因为我们已经不需要了。
可能有些领域模型不能作为一个独立存在的对象。它应该被另一个领域模型持有和使用。那这时候,可以考虑把两个模型合起来,形成一个聚合。在最上面的模型就是这个聚合的聚合根,其之下的模型都是它的实体或值对象。

划分子域和限界上下文

找到领域模型以后,我们应当就可以比较轻松地划分子域和限界上下文了。

在划分限界上下文的时候也可以反过来检验领域模型和通用语言的正确性。如果发现一个模型有歧义,那它就应该是限界上下文边界的地方,我们应该重新思考这个模型,必要时进行拆分。

常见的问题

在实施事件风暴的过程中会遇到一些问题,这里列举一些常见的问题及解决方案。

事件的粒度

我们在讨论这个问题之前,首先要思考事件是什么。事件是领域专家关心的业务事件。所以它不能比领域专家关心的业务更细,因为那将毫无意义。

举个例子,如果我们关心的是一个人一天的作息,那我们可能关心的是用户已起床,用户已吃早餐,用户已上班。但我们不会关心到更细节,比如:用户已睁眼,用户已洗漱,用户已出门,用户已上地铁……

同时,事件粒度也不能太粗,因为太粗粒度的事件不利于寻找领域模型。比如我们在平台上发一篇文章的业务。如果你只写一个“文章已发布”,那就可能会丢失掉一些比较重要的业务流程。

尝试改成:文章已保存,文章已申请审核,文章已通过审核,文章已审核失败,文章已对外发表,文章已加入分类,文章已推荐……你会发现,中间多了一个审核的过程,如果不找到这些命令,就很有可能遗漏掉“文章审核单”之类的模型。

对某个事件有歧义

这是好事情,说明你们团队需要讨论了,有时还可以发掘出原本可能没有注意到的业务细节。但在实施事件风暴的时候,不必刚开始就花太多的时间在上面,阻塞了后面的事件发掘。而是应该先前面说的那样,用一个醒目的标记记下来,后面再回过头来充分讨论。

或许最开始有歧义的地方,在事件逐渐完善,领域模型定义出来后,就没有歧义了。

一个命令产生多个连锁事件

这个是正常的,一个命令可能会触发一个事件或者多个事件。也有可能一个事件触发了另一个事件,只需要把它们贴在一起即可。

领域模型周围的事件过多

这个时候你们应该警惕了。一个领域模型不应该包含过多的领域事件,因为这会让这个模型变得很大,很复杂。你们需要考虑把这个领域模型拆分开了。

仔细思考一下,这个领域模型是不是可以拆成两个?一些下面的实体是不是可以拿出来单独作为一个聚合根?它们中的一些事件表述是不是有歧义?可不可以拆开来划分到两个限界上下文中?

比如“用户”在权限上下文中我们关注的是它的角色和权限,它是否登录成功,它的密码等等。

而在商品上下文中,我们关注的是它的姓名,电话,地址等等。

这种情况,是应该把它们拆开的。

感觉命令就是事件的动词?

很多时候其实就是这样的。比如角色是用户,命令是发布,产生了事件文章已发布。但也不完全是这样,因为在这个过程中可以统一语言。比如:用户,喝水,产生的事件可以是用户已补充水分,而不是用户已喝水。

也有可能会有一些定时任务或者策略,这都有利于我们熟悉业务。更何况,找到命令可以指导我们后续的API开发,所以寻找命令是有必要的。

成员完全不熟悉业务怎么办?

可以由领域专家先进行业务大概流程的讲解。如果有UX已经设计好的图就更好了。大家可以在这个环节发出自己的疑问,澄清一些关键信息。

领域专家也可以把主要的业务流程写下来,打印到纸上或者反映到大屏幕上。比如:

产品运营人员可以添加新的商品,编辑产品库存,并发布到京西商城,用户可以进行购买;当商品销售价格和库存数量发生变化后,产品运营人员会进行修改,并重新发布到商城。

没有领域专家怎么办?

团队总得是有人了解业务的。比如BA(有些团队可能是PM、TL等)。如果实在没有,可以让领域专家写一份上面那种主要的业务流程,大家按照这个业务流程来做。

但还是最好有一个领域专家,因为出现分歧的时候是很需要沟通达成一致的。如果没有领域专家在,团队有可能得到一些不准确的模型和语言。

架构设计篇之领域驱动设计(DDD)

点击上方蓝字关注我

架构设计篇之领域驱动设计(DDD)


本篇文章基于Eric Evans作者和翻译作者孙向晖,霍泰稳的书,做的软件思想笔记,向作者们致敬。

不要畏惧你所不知道的领域,如果你有需要,那么就搞定它。

                                                           - 刘晓成@小诚信驿站


一、定义

领域驱动设计的定义:

领域驱动设计,其实就是业务驱动设计,通过软件开发架构师和开发人员与最熟悉该领域的业务专家一起勾勒出需求领域模型去实际解决业务场景遇到的问题。

二、核心

DDD核心是什么?

  DDD核心是关注精简的业务模型及实现的匹配。

三、为什么需要DDD?

DDD希望解决什么问题?

目前我们使用的面向对象程序设计,也有面向过程程序设计,为什么还需要领域驱动设计?

  • 设计之选择性的三个问题

  1. 在面向对象编程的过程中,哪些对象是对我们的系统有用的?(精简业务模型

  2. 哪些是对我们拟建系统没有用处的?(克制系统边界,需求边界

  3. 我们应该如何保证我们选取的模型对象恰好够用?(适度不蔓延,不泛化

  • 设计之封装边界的问题

  1. 对象封装解决的一部分对象关联依赖关系。但是如何更高点的层次上,通过保留对象之间有用的关系去除无用的关系,并且限定变更影响的范围来降低系统的复杂度?(去除无用的关系,更好的封装不变需求范围)

  • 设计之业务和研发思维问题

  1. 开发人员会沉溺于技术思维,对业务理解和深入付出很少。业务理解对于技术实现不能理解,只想表达业务视角看到的内容。

   解决方式:看清楚我们提炼出的模型,在整个架构和整个开发过程中所处的位置和地位。

  1. 如何处理模型和对象实现

   解决方式:哪些具有什么样素质和技能的人来处理模型和对象实现,应该用什么样团队模型来匹配业务模型

  1. 如何应用开源项目设计思想和历史项目设计思想?

   解决方式:尽信书不如无书,切勿生搬硬套,要有自己的方法论,进行切合实际场景的使用。

四、DDD方法论

4.1、理解你的领域,熟悉你的业务

    所有的技术实现都是围绕业务场景开展,在一个系统设计之前一定要充分理解你的应用场景。业务的需求(痛点),你要从哪个切入点(抓手)开展?从根本上看,你解决了什么问题?

  1. 软件和领域和谐相处其实就是软件更好的为你所涉及的业务领域服务。我们需要做的是理解你的业务领域核心概念和元素,并精确实现它们之间的关系。软件需要对领域进行建模

  2. 那么这里的问题点是我们应该保留什么?放弃什么?软件需要对领域进行抽象

   我们比如是滴滴的客服系统,则我们应该更好的记录用户的问题,解答客户的问题,放弃商业化的售卖东西。

   我们比如是淘宝的运营系统,则我们应该更好的记录用户的购买转化率,点击率,曝光率,直接下单率,双11同环比GMV,以及净利润营收等,而不应该关注用户是否可以用滴滴APP打到车,美团APP是否可以下单订到外卖。

  1. 那么领域专家和技术专家如何共享知识和信息,软件需要对领域进行交流

        3.1、比如将模型图形化:图、用例、画和图片等(UML少量元素的场景来描述)。

        3.2、针对要交流的领域内的特定问题建立一种语言(prd原型图)

        3.3、用代码作为语言沟通,优秀的高可维护性的代码(伪代码来约定)


4.2、构建领域知识,实践你的业务

对于你的业务和你不熟悉的领域,都需要从0-1开始构建你的业务领域知识体系。

  1. 构建的方式,最好的方式是交流

   与领域专家交谈,比如淘宝的运营系统是和运营和商家交谈,比如滴滴客服系统是跟滴滴的客服和司机乘客交谈。你需要尽可能多地从专家处学 习领域知识。通过提出正确的问题,正确地处理得到的信息,你和专家会开始勾勒领域的骨架视图,也就是领域模型。这种骨架视图既不完整也不能保证是正确的,但它却是你需要的开始点,可以尽 力判断出领域的基础性概念。比如美人鱼电影中的警察根据报警人员的描述,勾勒出美人鱼的画像(初期领域建模不一定非常正确或者完美,但一定是尽力还原领域业务专家的描述)。

架构设计篇之领域驱动设计(DDD)


2.构建时间损耗,多次沟通不耐其烦

软件架构师和开发人员和领域专家,会在一起创建领域的模型,可能这个过程需要耗时很久很久,但是为了目标最后的建模的准确性,这个多次沟通是最有效的方式

4.3、模型驱动设计,模型设计完成编码

4.3.1、我们应该如何动手处理从模型到代码的转换

从分析模型出发,中间可以过程化编程(函数调用和数据结构算法来表达),如果是复杂的模型驱动,则不建议过程化编程,可以伪代码实现。

4.3.2、模型驱动设计的基本构成要素

4.3.2.1、分层架构设计之逻辑清晰(MVC)

架构设计篇之领域驱动设计(DDD)


架构设计篇之领域驱动设计(DDD)


上面的的4个概念层其实就是我们所说的view- controller- service(domain)- dao(mapper),是不是非常符合我们的MVC架构。

  4.3.2.2、实体与值对象(entity与各种O)

建议选择那些符合实体定义的对象作为实体,将剩下的对象处理成值对象,没有标识符,值对象就可以被轻易地创建或者丢弃。没有人关心创建一个标识符,在没有其他对象引用时,垃圾回收会处理这个对象。

一条箴言是:值对象就是我们所说的dto或者vo,根据entity或者domain实体产生的多个副本(派生的对象)。

架构设计篇之领域驱动设计(DDD)


    4.3.2.3、服务(系统服务)

一个服务应该不是对通常属于领域对象的操作的替代。我们不应该 为每一个需要的操作来建立一个服务。但是当一个操作凸现为一个领域中的重要概念时,就需要为它建立一个服务了。

以下是服务的3个特征:

1. 服务执行的操作涉及一个领域概念,这个领域概念通常不属于一 个实体或者值对象。

2.被执行的操作涉及到领域中的其他的对象。

3.操作是无状态的。

   4.3.2.4、模块(功能模块)


  • 对一个大型的复杂项目,模型趋向很大。模型到达了一 个作为整体很难讨论的点,理解不同部件之间的关系和交互变得很 困难。将模型组织进模块。模块被用来作为组织相关概念和任务以便降低复杂性的一种方法。

4.3.2.5、聚合(对象的封装组合)
  • 聚合是一个用来定义对象所有权和边界的领域模式。工厂和数据库,用来帮助我们处 理对象的创建和存储问题

  • 一个模型会包含众多的领域对象。不管在设计时做了多少考虑,我们都会看到许多对象会跟其他的对象发生关联,形成了一个复杂的关系网

架构设计篇之领域驱动设计(DDD)


4.3.2.6、工厂(工厂方法和抽象工厂)

工厂方法和抽象工厂,只关心对象的创建和强化所有的不变量,返回对那个对象的引用或者拷贝

4.3.2.7、资源库(数据库)

从数据库或者new创建到删除或者gc回收的过程

使用一个资源库,它的目的是封装所有获取对象引用所需的逻辑。领域对象不需处理基础设施,以得到领域中对其他对象的所需的引用。只需从资源库中获取它们,于是模型重获它应有的清晰和焦点。

资源库会保存对某些对象的引用。当一个对象被创建出来时,它可以被保存到资源库中,然后以后使用时可从资源库中检索到。如果 客户程序从资源库中请求一个对象,而资源库中并没有它,就会从 存储介质中获取它。换种说法是,资源库作为一个全局的可访问对 象的存储点而存在。

资源库可能包含一定的策略。它可能基于一个特定的策略来访问某个或者另一个持久化存储介质。它可能会对不同类型的对象使用不 同的存储位置。最终结果是领域模型同需要保存的对象和它们的引 用中解耦,可以访问潜在的持久化基础设施。(如下图式例)

架构设计篇之领域驱动设计(DDD)

资源库的接口是纯粹的领域模型(比如根据业务场景实现的查找用户或者添加用户的功能)

架构设计篇之领域驱动设计(DDD)


4.3.2.8、规约(比如API约定,接口文档,交互协议等)

指定一个查询条件,规约允许定义更复杂的条件。来提供服务支持。

4.3.3、面向深层理解的重构

基于模型驱动设计从模型我们开发用代码进行了表达,以及对于业务领域进行了基本构成拆分形成了一个服务或者多个服务系统。正如我们之前所说的领域模型初期不一定非常完美切合业务场景。那么随着不断深入了解业务以及功能逐渐完善,重构也是在这个过程中必不可少的环节。持续重构将是保证我们的好的方案以及应对变化的需求的保障护航舰队。


  • 一个好的模型产生于深层的思考、理解、经验和才能

4.3.3.1、持续重构

设计方案应对需求的变化

4.3.3.2、凸显关键概念

业务领域建模的时候,约束(限制条件,校验参数)、过程(实现业务的逻辑过程)和 规约(约定的接口文档交互方式等)这些是一些关键的核心概念,而这些需要我们不断完善,但并不是要经常变动的。

4.3.3.3、保持模型一致性(每个领域保证业务领域模型与整体一致)

之前我们说过,针对业务专家描述的业务领域可能会有部分需要独立部署服务系统,那么这些单独部署的服务系统,从整体上来看应该都符合整体业务领域模型的内容。

  • 划清边界,界定上下文

比如分层领域,需要界定应用上下文,每个系统服务的职能。


  • 持续集成

持续集成是基于模型中概念的集成,然后再通过测试实现。任何不完整的模型在实现过程中都会被检测出来。持续集成应用于界定的上下文,不会被用来处理相邻上下文之间的关系。


  • 上下文映射

(Context Map)是指抽象出不同界定上下文和它们之间关系的文 档,它可以是像下面所说的一个试图(Diagram),也可以是其他任何写就的文档。

架构设计篇之领域驱动设计(DDD)

  • 共享内核(共享基础服务)

共享内核的目的是减少重复,但是仍保持两个独立的上下文。对于 共享内核的开发需要多加小心。两个开发团队都有可能修改内核代 码,还要必须整合所做的修改。如果团队用的是内核代码的副本, 那么要尽可能早地融合( Merge)代码,至少每周一次。还应该使 用测试工具,这样每一个针对内核的修改都能快速地被测试。内核 的任何改变都应该通知另一个团队,团队之间密切沟通,使大家都 能了解最新的功能。

架构设计篇之领域驱动设计(DDD)


  • 客户和供应商---实际上是上下游系统

  • 顺从者(如何应对内部需求堆积,合理协配外部需求)

  • 防奔溃层(实际上指的是如何应对模型针对不同外部需求的服务变化可以用门面模式,适配层处理)


架构设计篇之领域驱动设计(DDD)


  • 独立方法(独立服务部署)

实际上指的是如果我们的领域模型 不相关的服务内容可以作为独立的服务部署和实现。

  • 开放主机服务(提炼核心服务,核心业务)

系统的核心域要看我们如何理解系统。精炼模型。找到核心域,发现一个能轻松地从支持模型和代码中区 分核心域的方法。强调最有价值和特殊的概念。使核心变小。

4.3.4、实践DDD的方式


  1. 购买现成的方案。这个方法的好处是可以使用别人已经做好 的全套方案。随之而来的是学习曲线的问题,而且这样的方 案还会引入其他麻烦。比如如果代码有凑五,你只得等待别 人来解决。你还需要使用特定的编译器和类库版本。和自己 系统的集成也不是那么容易。

  2. 外包。将设计和实现交给另外一个团队,有可能是其他公司 的。这样做可以使你专注于核心域,从处理其他领域的重压 下释放出来。不便的地方是集成外包的代码。需要和子域通 信的结构需要预先定义好,还要和外包团队保持沟通。

  3. 已有模型。一个取巧的方案是使用一个已经创建的模型。世 面上已经有一些关于分析模型的书,可以作为我们子域的灵 感来源。直接复制原有的模型不太现实,但确实有些只需要 做少许改动就可以用了。

  4. 自己实现。这个方案的好处是能够做到最好的集成,但这也 意味着额外的付出,包括维护的压力等。


五、DDD的思维陷进


  1. 事必躬亲。模型需要代码。

  2. 专注于具体场景。抽象思维需要落地于具体案例。

  3. 不要试图对任何事情都进行领域驱动设计。画一张范围表,然后 决定哪些应该进行领域驱动设计,哪些不用。不要担心边界之外的事情。也就是说不要蔓延,要克制。

  4. 不停地实验,期望能产生错误。模型是一个创造性的流程。





架构设计篇之领域驱动设计(DDD)

听说转发文章

会给你带来好运

架构设计篇之领域驱动设计(DDD)
你点的每个赞,我都认真当成了喜欢



扫码关注!您将得到及时的文章推送信息


以上是关于架构思想之DDD的主要内容,如果未能解决你的问题,请参考以下文章

基于DDD思想的微服务架构学习导向

谈架构设计中DDD思想的运用

浅析 DDD 领域驱动设计

解构领域驱动设计:领域驱动设计的核心之分层架构

DDD领域驱动设计之面向对象思想

DDD CQRS架构和传统架构的优缺点比较