DDD
DDD(Domain Driven Design),直译过来就是领域驱动设计。
那么这里首先就有两个问题: 什么是领域,领域如何驱动设计的?
一、 领域
Domain 其实指的是Business,或者应该合起来叫 Business Domain(业务领域),
分析Domain就是分析实际的业务,把握业务逻辑中具体的规则和约束,
理解过程中业务对象之间如何进行交互,数据如何流转。
但是往往一个业务领域都比较大,分析的时候需要将其按照业务再进行划分,
这个时候划分出的就叫 子域(subdomain)。
有些文章中也会引入问题空间的概念,实际上也是将业务需求具象为一系列的问题,并将其划分为细分的子问题集合,形成一个个问题空间。
在Domain-Driven Design (DDD) [参2] 中,将领域驱动设计分为了两类:Tactical design tools and Strategic design tools,
直译为 战术设计工具和 战略设计工具,本文暂译为 细节设计 和 宏观设计
1.1 宏观设计的工具
宏观设计的工具主要有以下三个:
- 领域模型(Model) :
- 通用语言(Ubiquitous Language)
- 界限上下文(Bounded Context)
1.1.1 领域模型
一般领域模型是对业务模型的进一步抽象,DDD的领域模型指的是一套综合系统分析和设计的面向对象建模方法,
DDD的领域模型一般采用充血模型,关于 贫血模型和充血模型的区别,此处不做展开。
1.1.2 通用语言
Ubiquitous Language(通用语言)的词汇包括类和主要操作的名称,
通用语言中的术语,有些用来讨论模型中已经明确的规则,还有一些则来自施加于模型上的高级组织原则。
其目的是保证团队内对具体业务、术语、主体的描述统一,保证后续的开发顺利进行。
在事件风暴过程中,通过团队交流达成共识的,能够简单、清晰、准确描述业务涵义和规则的语言就是通用语言。 [参7]
在统一的过程中,此业务领域的专家应该抵制不合适或无法充分表达领域理解的术语或结构,
而开发人员应该密切关注那些将会妨碍设计的有歧义和不一致的地方。
1.1.3 界限上下文
我们可以将限界上下文拆解为两个词:限界和上下文。限界就是领域的边界,而上下文则是语义环境。通过领域的限界上下文,我们就可以在统一的领域边界内用统一的语言进行交流。即用来封装通用语言和领域对象,提供上下文环境,保证在领域之内的一些术语、业务相关对象等(通用语言)有一个确切的含义,没有二义性。
正如电商领域的商品一样,商品在不同的阶段有不同的术语,在销售阶段是商品,而在运输阶段则变成了货物。同样的一个东西,由于业务领域的不同,赋予了这些术语不同的涵义和职责边界,这个边界就可能会成为未来微服务设计的边界。
美团的实践方案是,考虑产品所讲的通用语言,从中提取一些术语称之为概念对象,寻找对象之间的联系;或者从需求里提取一些动词,观察动词和对象之间的关系;我们将紧耦合的各自圈在一起,观察他们内在的联系,从而形成对应的界限上下文。形成之后,我们可以尝试用语言来描述下界限上下文的职责,看它是否清晰、准确、简洁和完整。简言之,限界上下文应该从需求出发,按领域划分。
1.1.4 三者的联系
通用语言定义上下文含义,限界上下文则定义领域边界,以确保每个上下文含义在它特定的边界内都具有唯一的含义,领域模型则存在于这个边界之内。
1.2 细节设计工具
1.2.1 实体(Entity)
实体是指拥有唯一标识符,且标识符在历经各种状态变更后仍能保持一致。对这些对象而言,重要的不是其属性,而是其延续性和标识,对象的延续性和标识会跨越甚至超出软件的生命周期。我们把这样的对象称为实体。
比如 客户系统里的客户、订单系统里的订单、会员系统里的会员,都有其唯一识别的信息。
一般在代码中以实体类的形式存在,DDD中一般处理为充血模型,这里可以查看参考文章1中对充血模型的描述。
在领域模型映射到数据模型时,一个实体可能对应 0 个、1 个或者多个数据库持久化对象。大多数情况下实体与持久化对象是一对一。
1.2.2 值对象
值对象指的是没有唯一标识符,用于描述领域中某方面的对象。
比如:用户实体关联着 地址值对象,用户id永远不会变更,而地址的信息却可能经常变更。当然这个前提是在用户的界限上下文内,在地址管理器的界限上下文中,地址就是一个实体了。
1.2.3 领域服务
一些重要的领域行为或操作,可以归类为领域服务。它既不是实体,也不是值对象的范畴,但是作用于实体和实体、值对象和值对象以及实体和值对象之间。
当我们采用了微服务架构风格,一切领域逻辑的对外暴露均需要通过领域服务来进行。
如原本由聚合根暴露的业务逻辑也需要依托于领域服务。
1.2.4 聚合
Aggregate(聚合)是一组相关对象的集合,作为一个整体被外界访问,聚合根(Aggregate Root)是这个聚合的根节点。
领域模型内的实体和值对象就好比个体,而能让实体和值对象协同工作的组织就是聚合,它用来确保这些领域对象在实现共同的业务逻辑时,能保证数据的一致性。
如何创建好的聚合?
- 边界内的内容具有一致性:在一个事务中只修改一个聚合实例。如果你发现边界内很难接受强一致,不管是出于性能或产品需求的考虑,应该考虑剥离出独立的聚合,采用最终一致的方式。
- 设计小聚合:大部分的聚合都可以只包含根实体,而无需包含其他实体。即使一定要包含,可以考虑将其创建为值对象。
- 通过唯一标识来引用其他聚合或实体:当存在对象之间的关联时,建议引用其唯一标识而非引用其整体对象。如果是外部上下文中的实体,引用其唯一标识或将需要的属性构造值对象。 如果聚合创建复杂,推荐使用工厂方法来屏蔽内部复杂的创建逻辑。
聚合内部多个组成对象的关系可以用来指导数据库创建,但不可避免存在一定的抗阻。如聚合中存在List<值对象>,那么在数据库中建立1:N的关联需要将值对象单独建表,此时是有id的,建议不要将该id暴露到资源库外部,对外隐蔽。
1.3 通用域、支撑域、核心域
在领域不断划分的过程中,领域会细分为不同的子域,子域可以根据自身重要性和功能属性划分为三类子域,它们分别是:核心域、通用域和支撑域。
- 决定产品和公司核心竞争力的子域是核心域,它是业务成功的主要因素和公司的核心竞争力。
- 没有太多个性化的诉求,同时被多个子域使用的通用功能子域是通用域。
- 既不包含决定产品和公司核心竞争力的功能,也不包含通用功能的子域,它就是支撑域。
这三类子域相较之下,核心域是最重要的。
通用域和支撑域如果对应到企业系统,举例来说的话,通用域则是你需要用到的通用系统,比如认证、权限等等,这类应用很容易买到,没有企业特点限制,不需要做太多的定制化。而支撑域则具有企业特性,但不具有通用性,例如数据代码类的数据字典等系统。
二、领域如何驱动设计?
2.1 驱动
- 领域驱动领域模型设计
- 领域模型驱动代码实现
2.2 设计
DDD中的设计主要指领域模型的设计。
为什么是领域模型的设计而不是架构设计或其他的什么设计呢?
因为DDD是一种基于模型驱动开发的软件开发思想,强调领域模型是整个系统的核心,领域模型也是整个系统的核心价值所在。每一个领域,都有一个对应的领域模型,领域模型能够很好的帮我们解决复杂的业务问题。
从领域和代码实现的角度来理解,领域模型绑定了领域和代码实现,确保了最终的代码实现就一定是解决了领域中的核心问题的。因为:1)领域驱动领域模型设计;2)领域模型驱动代码实现。
我们只要保证领域模型的设计是正确的,就能确定领域模型可以解决领域中的核心问题;同理,我们只要保证代码实现是严格按照领域模型的意图来落地的,那就能保证最后出来的代码能够解决领域的核心问题的。这个思路,和传统的分析、设计、编码这几个阶段被割裂(并且每个阶段的产物也不同)的软件开发方法学形成鲜明的对比。
//todo 补充示例
以上文章均为个人理解,如有纰漏,敬请指出,不胜感激。
参考文章:
【1】领域驱动设计在互联网业务开发中的实践
【2】Domain-Driven Design (DDD)
【3】浅谈我对DDD领域驱动设计的理解
【4】Tackling Complexity in the Heart of DDD
【5】What is domain logic?
【6】DDD中的通用语言是什么
【7】极客时光-DDD实战课