领域驱动设计DDD(Domain-Driven Design)

1 背景

业务初期,我们的功能大都非常简单,普通的CRUD就能满足,此时系统是清晰的。随着迭代的不断演化,业务逻辑变得越来越复杂,我们的系统也越来越冗杂。模块彼此关联,谁都很难说清模块的具体功能意图是啥。修改一个功能时,往往光回溯该功能需要的修改点就需要很长时间,更别提修改带来的不可预知的影响面。


传统模型设计

订单服务接口中提供了查询、创建订单相关的接口,也提供了订单评价、支付、保险的接口。同时我们的表也是一个订单大表,包含了非常多字段。在我们维护代码时,牵一发而动全身,很可能只是想改下评价相关的功能,却影响到了创单核心路径。虽然我们可以通过测试保证功能完备性,但当我们在订单领域有大量需求同时并行开发时,改动重叠、恶性循环、疲于奔命修改各种问题。

2 软件系统复杂性应对

解决复杂和大规模软件的武器可以被粗略地归为三类:抽象分治知识
分治: 把问题空间分割为规模更小且易于处理的若干子问题。分割后的问题需要足够小,以便一个人单枪匹马就能够解决他们;其次,必须考虑如何将分割后的各个部分装配为整体。分割得越合理越易于理解,在装配成整体时,所需跟踪的细节也就越少。即更容易设计各部分的协作方式。评判什么是分治得好,即高内聚低耦合。
抽象: 使用抽象能够精简问题空间,而且问题越小越容易理解。举个例子,从北京到上海出差,可以先理解为使用交通工具前往,但不需要一开始就想清楚到底是高铁还是飞机,以及乘坐他们需要注意什么。
知识: 顾名思义,DDD可以认为是知识的一种。
DDD提供了这样的知识手段,让我们知道如何抽象出限界上下文以及如何去分治。

3 与微服务架构相得益彰

微服务架构众所周知,此处不做赘述。我们创建微服务时,需要创建一个高内聚、低耦合的微服务。而DDD中的限界上下文则完美匹配微服务要求,可以将该限界上下文理解为一个微服务进程。

上述是从更直观的角度来描述两者的相似处。

在系统复杂之后,我们都需要用分治来拆解问题。一般有两种方式,技术维度和业务维度。技术维度是类似MVC这样,业务维度则是指按业务领域来划分系统。

微服务架构更强调从业务维度去做分治来应对系统复杂度,而DDD也是同样的着重业务视角。 如果两者在追求的目标(业务维度)达到了上下文的统一,那么在具体做法上有什么联系和不同呢?

我们将架构设计活动精简为以下三个层面:
业务架构——根据业务需求设计业务模块及其关系
系统架构——设计系统和子系统的模块
技术架构——决定采用的技术及框架

DDD的核心诉求就是将业务架构映射到系统架构上,在响应业务变化调整业务架构时,也随之变化系统架构。而微服务追求业务层面的复用,设计出来的系统架构和业务一致;在技术架构上则系统模块之间充分解耦,可以自由地选择合适的技术架构,去中心化地治理技术和数据。

领域模型设计

4 业务拆分领域模型

1、 根据需求划分出初步的领域和限界上下文,以及上下文之间的关系;
2、 进一步分析每个上下文内部,识别出哪些是实体,哪些是值对象;
3 、对实体、值对象进行关联和聚合,划分出聚合的范畴和聚合根;
4 、为聚合根设计仓储,并思考实体或值对象的创建方式;
5 、在工程中实践领域模型,并在实践中检验模型的合理性,倒推模型中不足的地方并重构。

5 名词介绍

限界上下文
一个由显示边界限定的特定职责。领域模型便存在于这个边界之内。在边界内,每一个模型概念,包括它的属性和操作,都具有特殊的含义。
用来封装通用语言和领域对象,提供上下文环境,保证在领域之内的一些术语、业务相关对象等(通用语言)有一个确切的含义,没有二义性。这个边界定义了模型的适用范围,使团队所有成员能够明确地知道什么应该在模型中实现,什么不应该在模型中实现。

限界上下文划分

我们的实践是,考虑产品所讲的通用语言,从中提取一些术语称之为概念对象,寻找对象之间的联系;或者从需求里提取一些动词,观察动词和对象之间的关系;我们将紧耦合的各自圈在一起,观察他们内在的联系,从而形成对应的界限上下文。形成之后,我们可以尝试用语言来描述下界限上下文的职责,看它是否清晰、准确、简洁和完整。简言之,限界上下文应该从需求出发,按领域划分。

限界上下文之间的映射关系

合作关系(Partnership):两个上下文紧密合作的关系,一荣俱荣,一损俱损。
共享内核(Shared Kernel):两个上下文依赖部分共享的模型。
客户方-供应方开发(Customer-Supplier Development):上下文之间有组织的上下游依赖。
遵奉者(Conformist):下游上下文只能盲目依赖上游上下文。
防腐层(Anticorruption Layer 简称:ACL):一个上下文通过一些适配和转换与另一个上下文交互。
开放主机服务(Open Host Service 简称:OHS):定义一种协议来让其他上下文来对本上下文进行访问。
发布语言(Published Language 简称:PL):通常与OHS一起使用,用于定义开放主机的协议。
大泥球(Big Ball of Mud):混杂在一起的上下文关系,边界不清晰。
另谋他路(SeparateWay):两个完全没有任何联系的上下文。

通过上下文映射关系,我们明确的限制了限界上下文的耦合性,即在抽奖平台中,无论是上下文内部交互(合作关系)还是与外部上下文交互(防腐层),耦合度都限定在数据耦合(Data Coupling)的层级。

实体
当一个对象由其标识(而不是属性)区分时,这种对象称为实体(Entity)。

值对象

特性 : 没有唯一标识 不变性

在值对象中,不关心标识,只要我们能确定值对象的属性值都一样,我们就可以说这两个值对象是相同的。
比如说两个学生的家庭地址(省市县街道门牌号)是一样的,我们就可以认为是同一个地址,这就是相等性比较。
如果学生在修改地址的时候,无论修改的省或者市或者县,我们不关心改了多少,而是 将整个值对象覆盖,它具有不变性、相等性和可替换性。

聚合根
Aggregate(聚合)是一组相关对象的集合,作为一个整体被外界访问,聚合根(Aggregate Root)是这个聚合的根节点。
聚合是一个非常重要的概念,核心领域往往都需要用聚合来表达。
聚合由根实体,值对象和实体组成。
如何创建好的聚合?
边界内的内容具有一致性:在一个事务中只修改一个聚合实例。如果你发现边界内很难接受强一致,不管是出于性能或产品需求的考虑,应该考虑剥离出独立的聚合,采用最终一致的方式。
设计小聚合:大部分的聚合都可以只包含根实体,而无需包含其他实体。即使一定要包含,可以考虑将其创建为值对象。
通过唯一标识来引用其他聚合或实体:当存在对象之间的关联时,建议引用其唯一标识而非引用其整体对象。如果是外部上下文中的实体,引用其唯一标识或将需要的属性构造值对象。 如果聚合创建复杂,推荐使用工厂方法来屏蔽内部复杂的创建逻辑。

领域服务
一些重要的领域行为或操作,可以归类为领域服务。它既不是实体,也不是值对象的范畴。
领域中的服务表示一个无状态的操作,它用于实现特定于某个领域的任务。这里我们要搞清楚什么样的操作需要实体,值对象,什么样的操作需要采用领域服务。
另外,领域服务不是应用服务,在应用服务中我们不需要处理业务逻辑,业务逻辑都落在领域服务中。
领域服务发现:
执行一个显著的业务操作过程
对领域对象进行转换
以多个领域对象作为输入进行计算,产生一个值对象。
过度使用领域服务将会产生一个贫血模型,例如数据建模时,我们的实体常用只含有get/set方法,所有的业务逻辑都包含在了service。这样导致service变成了一个大泥球。注意区分领域服务与实体,值对象行为。

领域事件
领域事件是对领域内发生的活动进行的建模。
领域事件通常是用来与其他聚合解耦的,采用观察者模式,一个聚合订阅另外一个聚合的事件。

实例 抽奖DDD


image.png
image.png

注意: U 代表上游 D 代表下游

你可能感兴趣的:(领域驱动设计DDD(Domain-Driven Design))