# DDD-Domain-Driven Design

> 有次面试被问到了

> 本文讨论的是及其复杂的场景下的解决方案,当然绝大多数公司压根就用不到DDD,也没有必要使用DDD。在业务初期,都是业务先行,普通的开发模式完全可以覆盖
<!-- ### 有些话
有次面试被问到了,因为之前从未接触过DDD的实际开发,只是知道,今天在这里梳理一遍

其实很多公司为了DDD而DDD,就像很多公司为了分布式而分布式。体量没有达到,就将应用构建的那么复杂,既影响最初期的MVP,也耗费了大量的现有资源。

对于公司来说,短期收益是看不见的 <small style="color:grey">**不过大部分公司活不过3年,所以他们到死也不明白是哪里做的不够好,其实就是温水(初期过于复杂的软件设计)煮青蛙了**</small> ,长期来看才可以慢慢看的见收益,前提是这个收益是有熟悉DDD的人一直在维护和纠正,否则市面上的大多数的软件开发者会慢慢地将完善的DDD渐渐的弱化掉 -->

DDD核心在 **领域建模** 和 **设计**


<!-- 用DDD则可以很好地解决领域模型到设计模型的同步、演化，最后再将反映了领域的设计模型转为实际的代码。 -->

<!-- 注：模型是我们解决实际问题所抽象出来的概念模型，领域模型则表达与业务相关的事实；设计模型则描述了所要构建的系统。 -->

### 什么情况下启用DDD?
**过渡耦合**

系统已经达到牵一发而动全身的程度了。原本只想改一个小功能,却不曾想影响核心接口的功能了。虽然重构可以解决,但是确实暂时的,只是解决了庞大系统中的一小部分痛点,长期来看,类似的问题并没有被彻底解决。

用DDD则可以解决领域模型到设计模型的同步、演化，最后再将反映了领域的设计模型转为实际的代码。

模型是我们解决实际问题所抽象出来的概念模型
- 领域模型则表达与业务相关的事实
- 设计模型则描述了所要构建的系统

### 什么是贫血症和失忆症
贫血领域对象（Anemic Domain Object）是指仅用作数据载体，而没有行为和动作的领域对象。—— 在大部分公司都是这样,对象只是数据的载体，没有行为。

业务逻辑复杂时，业务逻辑、状态会散落到在大量方法中，原本的代码意图会渐渐不明确，我们将这种情况称为由贫血症引起的失忆症。因为我做电商系统做的比较多,就经常会遇到这个问题,一个订单的状态散落的到处都是,如果对系统代码不熟悉或者对业务不熟悉,经常会奔波在修Bug的途中,不是这里缺少,就是那里缺少

### 解决大规模和复杂的软件可以通过哪些方法
- 分而治之。将大的问题划分。模板就是在每一个分支内实现高内聚低耦合
- 抽象。先不谈具体的实现,直接说出怎么可以做到。举一个简单的例子,我饿了,我要吃东西。而不是优先考虑我要吃什么,是吃面还是吃饭。这个就是一个抽象
- 框架。DDD就是一种,按照成熟的框架,在这个框架内做好该做的事。所谓的框架更多的是一种限制,是一种高效。在大的框架内做事,绝对比自己或者靠团队的臆想靠谱的多,会减少很多不必要的低级错误。

#### 分而治之
一般通过2个维度进行,即技术维度和业务维度。技术维度就是分层,例如我们常用的MVC模式,业务维度则考研对于业务的理解,需要划分出业务领域。

我们经常聊的微服务其实就是从业务维度来划分的。例如订单服务、商品服务等等。那这里的DDD和微服务的区别在哪里呢?
我们一般做架构设计时,主要集中在一下3个方面:

![](/software-engineer/drawio/Moatkon-DDDArchitecture.drawio.svg)

- 业务架构: 根据业务需求设计业务模块及其关系
- 系统架构: 设计系统和子系统的模块
- 技术架构: 决定采用的技术及框架

熟悉了这3个方面之后,就可以说区别了。DDD的侧重点是业务架构,而微服务侧重于系统架构和技术架构,以一套系统架构和技术架构来承接所有的业务,在常规模式下会一直用很久;而DDD的业务架构,总是业务先行,在响应业务变化调整业务架构时,系统架构也会随着业务架构来调整,所以DDD是在一直服务着业务,一点点的逼迫技术靠近业务,而不是想微服务架构一样,吃老本。

不知道你们在日常工作中,会不会遇到这种场景,就是业务提一个需求,技术就是暂时的系统能力支撑不了。这个其实技术就很被动,也是微服务架构会带来的问题。如果使用DDD架构,则这种问题会少很多,也不是说完全没有哈,只是DDD这种模式的运行会让技术慢慢的贴合业务,所以当业务提出新的诉求时,才不会有那么多不能支撑的时候。

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

#### 限界上下文
一个由显示边界限定的特定职责。领域模型便存在于这个边界之内。在边界内，每一个模型概念，包括它的属性和操作，都具有特殊的含义。

#### 如何划分限界上下文
实际操作过程中,要根据产品需求来,这是根本。当然这并不是一次就能搞定的事情,在后面的开发中可能还会不断的推翻及重构

#### 清晰的上下文划分的好处
1. 任务拆分方便
2. 成员之间沟通方便
3. 如果是跨团队的,则沟通便利


### 细化上下文
- 实体: 当一个对象由其标识（而不是属性）区分时，这种对象称为实体（Entity）。理解上来说就是id,例如,人可以通过身份证号这种独一无二的标识来进行区分。而不是人的属性,例如身高,年龄等
- 值对象: 当一个对象用于对事物进行描述而没有唯一标识时，它被称作值对象（Value Object）。例如年龄28岁。使用值对象,可以更好的做系统优化和设计的精简优化。在实践中，需要保证值对象创建后就不能被修改，即不允许外部再修改其属性。
- 聚合根: 聚合一组相关对象的集合，作为一个整体被外界访问，聚合根是这个聚合的根节点。聚合由根实体、值对象和实体构成。聚合是一个非常重要的概念，核心领域往往都需要用聚合来表达。其次，聚合在技术上有非常高的价值，可以指导详细设计。
- 领域服务: 一些重要的领域行为或操作，可以归类为领域服务。它既不是实体，也不是值对象的范畴。当我们采用了微服务架构风格，一切领域逻辑的对外暴露均需要通过领域服务来进行。如原本由聚合根暴露的业务逻辑也需要依托于领域服务。
- 领域事件: 领域事件是对领域内发生的活动进行的建模

#### 如何创建好聚合?
- **边界内的内容具有一致性**：在一个事务中只修改一个聚合实例。如果你发现边界内很难接受强一致，不管是出于性能或产品需求的考虑，应该考虑剥离出独立的聚合，采用最终一致的方式。
- **设计小聚合**：大部分的聚合都可以只包含根实体，而无需包含其他实体。即使一定要包含，可以考虑将其创建为值对象。
- **通过唯一标识来引用其他聚合或实体**：当存在对象之间的关联时，建议引用其唯一标识而非引用其整体对象。如果是外部上下文中的实体，引用其唯一标识或将需要的属性构造值对象。 如果聚合创建复杂，推荐使用工厂方法来屏蔽内部复杂的创建逻辑。

### DDD的工程
落地

#### 模块
一般的工程中包的组织方式为{com.公司名.业务.上下文.*}，这样的组织结构能够明确的将一个上下文限定在包的内部。
```java
```

对于模块内的组织结构，一般情况下我们是按照领域对象、领域服务、领域资源库、防腐层等组织方式定义的。
```java
```

#### 上下文集成
通常集成上下文的手段有多种，常见的手段包括开放领域服务接口、开放HTTP服务以及消息发布-订阅机制等

---
- 防腐层(适配层): 通过适配器和转换,和另一个上下文交互。做隔离
- 在DDD的落地过程中,要灵活

参考: [领域驱动设计在互联网业务开发中的实践](https://tech.meituan.com/2017/12/22/ddd-in-practice.html?utm_source=moatkon.com)