领域驱动设计(DDD)

战略设计

通用语言

通用语言是团队共享的语言。领域专家和开发者使用相同的通用语言进行交流。 image.png

是什么?团队共享的语言 怎么产生的?领域专家和开发任务一起创建的 有什么用?提高沟通效率,软件模型的直接反映

有什么难点? 自然的,领域专家对通用语言有很大影响,因为他们最了解业务。 通用语言本身是关于业务本身是怎么思考和运作的,不同的领域专家、开发者在概念上会产生分歧,他们一起建领域模型时会达成一致,创造最适合项目的通用语言。通用语言也会随着时间推移而不断演化改变。

DDD所面临的挑战: 为创建通用语言腾出时间和精力 持续的将领域专家引入到项目 改版开发中对领域的思考方式

领域

从广义来讲,领域即是一个组织所做的事情以及其中所包含的一切。每个组织都有它自己的业务范围和做事方式,这个业务范围以及在其中所进行的活动便是领域。

是什么,确定问题域的业务范围 为什么,划分子域,每个子域对应一个更细的子域

领域这个词承载了太多含有,领域既可以表示整个业务系统,也可以表示其中的某个核心域或者支撑子域,尽可能区分这些概念,当谈及业务系统的某个方面时,使用诸如核心域或者子域区分。

DDD 的领域就是这个边界内要解决的业务问题域,也就是范围、边界

领域可以进一步划分为子领域。我们把划分出来的多个子领域称为子域,每个子域对应一个更小的问题域或更小的业务范围。

子域

领域可以进一步划分为子领域。我们把划分出来的多个子领域称为子域,每个子域对应一个更小的问题域或更小的业务范围。 image.png

是什么 为什么 3w1h

领域可以进一步划分为子领域。我们把划分出来的多个子领域称为子域,每个子域对应一个更小的问题域或更小的业务范围

在领域不断划分的过程中,领域会细分为不同的子域,子域可以根据自身重要性和功能属性划分为三类子域,它们分别是:核心域、通用域和支撑域。

为什么要划分核心域、通用域和支撑域 领域的核心思想就是将问题域逐级细分,来降低业务理解和系统实现的复杂度。通过领域细分,逐步缩小微服务需要解决的问题域,构建合适的领域模型,而领域模型映射成系统就是微服务。

核心域、支撑域和通用域的主要目标是:通过领域划分,区分不同子域在公司内的不同功能属性和重要性,从而公司可对不同子域采取不同的资源投入和建设策略,其关注度也会不一样。

核心域:决定产品和公司核心竞争力的子域是核心域,它是业务成功的主要因素和公司的核心竞争力。

通用域:没有太多个性化的诉求,同时被多个子域使用的通用功能子域是通用域。

支撑域:还有一种功能子域是必需的,但既不包含决定产品和公司核心竞争力的功能,也不包含通用功能的子域,它就是支撑域。

限界上下文

限界上下文是一个显示的边界,领域模型存在于这个边界之内。在边界内,通用语言的所有术语和词组都有特定含义,而模型需要准确的反应通用语言。

image.png

image.png

是什么 为什么

重要

限界上下文并不是只包含模型。诚然,模型是限界上下文的主要公民。但是限界上下文并不只限于容纳模型,他通常标定了一个系统,一个应用程序或者一种业务服务。有时候限界上下文所包含的内容可能比较少,比如一个通用子域便只可能包含领域模型。

试图创建一个“大而全”的软件模型,其中每个概念在全局范围内只有一种定义,这是一个陷阱。首先,要使所有人对某个概念的定义达成一致几乎不可能。有些项目太庞大、太复杂。

底线是:如果你没有采用语言驱动,那么你就不算和领域专家在一起工作来创建限界上下文,认真考虑限界上下文大小,不要急于小型化。

子域和限界上下文

在DDD中一个领域被分成若干子域,领域模型在限界上下文中完成开发。 image.png

试图创建一个全功能的领域模型是非常困难的,并且很容易导致失败。 对领域的拆分有助于我们成功。

在DDD中一个领域被分成若干子域,领域模型在限界上下文中完成开发。

为了清晰的模型边界,DDD在战略设计上提出了“限界上下文”这个概念,用来确定语义所在的领域边界。

限界上下文定义:用来封装通用语言和领域对象,提供上下文环境,保证在领域之内的一些术语、业务相关对象等(通用语言)有一个确切的含义,没有二义性。

通用语言定义上下文含义,限界上下文则定义领域边界。

一个抽象的业务领域

image.png

核心域是整个核心领域的一部分,也是业务成功的主要促成因素。从战略层面上讲,企业应该在核心域上胜人一筹,我们应该给予核心域最高的优先级、最资深的领域专家和最优秀的开发团队。

支撑子域:有时我们会创建或者购买某个限界上下文来支撑我们的业务,这些限界上下文对应着业务的某些方面,但不是最重要的。 通用域:如果一个子域被用于整个业务系统,那么这个子域便是一个通用子域。

支撑子域和通用域不能说不重要,他们是重要的,只是并不像核心域要求那样高。

上下文映射图

一个简单的框图来表示两个或者多个限界上下文之间的映射关系。该框图表示了不同的限界上下文在解决方案中是如何通过集成相互关联的。 U 上游,D 下游 image.png

架构

DDD不需要使用特定的架构。 DDD 的好处在于并不需要使用特定的架构,由于核心域位于限界上下文中,可以在整个系统中使用多种代码风格的架构。目标是选择适合自己的架构和架构模式。

分层架构

分层架构模式被认为是所有架构的鼻祖。它将一个应用程序或者系统分为不同层次,支持N层架构系统,被广泛应用于Web、企业级应用和桌面应用。

image.png

是什么,严格,松散

分层架构的一个重要原则是:每层只能与其下方的层发生耦合。 严格分层架构:每层只能与直接位于其下方的层发生耦合。 松散分层架构:允许任意上方的层与任意下方的层发生耦合。

基础设施层: 提供技术支持

六边形架构

六边形架构又称端口与适配器。对于每种外界类型,都有一个适配器与之相对应,外界通过应用层API与内部进行交互。 image.png

如果设计得当,内部六边形-也即是应用程序和领域模型是不会泄露到外部区域的,这样有助于形成一种清晰的应用程序边界。

面向服务架构

六边形架构的功能如此强大,以致于它可以用来支持系统中的其他架构。 我们将SOA原则与六边形架构结合起来,此时服务边界位于最左侧而领域服务位于中心位置。 消费方可以通过REST、SOAP和消息机制获取服务,一个六边形架构支持多种服务类型的端点。 image.png

CQRS

一个方法要么是执行某种动作的命令,要么是返回数据的查询,而不能两者皆是。 image.png

指导原则:一个方法要么是执行某种动作的命令,要么是返回数据的查询,而不能两者皆是。 在对象层面, 一个方法修改了对象,该方法就是命令,它不应该返回数据。void 一个方法返回了数据,该方法便是一个查询,此时它不应该通过直接或间接的手动修改对象的状态。

延迟问题,数据最终一致性。

事件驱动架构

一种用于处理事件的生成、发现和处理等任务的软件架构。 image.png

三角形表示限界上下文所使用的消息机制。 在一个事件驱动架构中融入了六边形架构风格。该事件驱动架构通过消息机制完成了对所有系统的解耦。 输入、输出事件所用端口与其他客户端所使用端口不同 相关问题:延迟、重复、乱序等问题 解决 :幂等、反向查询、延迟查询、时间戳 优点,缺点?

战术设计

实体

一个实体是一个唯一的东西,并且可以在相当长的一段时间内持续的变化。 唯一身份标识、可变性是实体和值对象的区别。

image.png

为什么使用实体 当我们需要考虑一个对象的个体特征,需要区分不同的对象时,我们引入了实体的概念。 挖掘实体的关键行为

创建实体 构造函数

验证 验证属性 — 断言

public void valid() {
    Preconditions.checkArgument(StringUtils.isNotEmpty(clueName), "clueName不能为空!");

验证实体 — 验证类 验证组合实体(聚合)—领域服务

唯一标示

  1. 用户提供唯一值
  2. 算法自动生成
  3. 依赖持久化存储
  4. 另一个限界上下文提供

image.png

值对象

值对象用于度量和描述事物。值对象可以非常容易地对值对象进行创建、测试、使用、优化和维护,应该尽量使用值对象来建模而不是实体对象。 image.png

值对象特征

  1. 度量或者描述领域中一件东西
  2. 可以作为不变量
  3. 将不同的相关属性组成一个整体概念
  4. 可以用另一个值对象予以替换
  5. 可以和其他值对象可以进行相等性比较
  6. 不会对协作对象造成副作用
@AllArgsConstructor
@NoArgsConstructor
@Getter
@EqualsAndHashCode
public class KeyPerson {
    
    private final String name;
    private final List<Contact> contactList;
    private final ExtensionPropertyCollection extensionPropertyCollection;

    public KeyPersonSnapshot generateSnapShot() {

        return KeyPersonSnapshot.builder()
                .name(name)
                .contacts(contactList)
                .extensionPropertyMap(extensionPropertyCollection.plainPropertyMap())
                .build();
    }
}

领域服务

什么是领域服务? 当一个方法不便放在实体或值对象上时,使用领域服务便是最佳的解决方式。

什么不是领域服务 领域服务不要和应用服务,rpc服务混淆了,在应用服务中不会处理业务逻辑,而领域服务恰恰是用来处理业务逻辑的。 不要滥用领域服务,滥用领域服务将导致贫血领域模型这种反模式。

什么场景使用领域服务

  1. 执行一个显著的业务操作流程
  2. 对领域对象进行转换
  3. 以多个领域对象作为输入进行计算,结果产生一个值对象

有没有必要为独立的服务提供接口?优缺点,是个有争议的话题 优点:实现与接口解耦、有利于代码阅读和定位 然后与服务工厂与依赖注入相比DDD更倾向于将领域服务作为构造参数传入,这样的代码有很好的测试性 缺点:包过大

领域事件

领域专家所关心的发生在领域中的一些事件。 将领域中所发生的活动建模成一系列的离散事件,每个事件都用领域对象来表示。 当…… 如果发生…… 当……的时候,通知…… 发生……时

领域事件产生、存储、分发

聚合创建并发布事件。订阅方可以先存储事件,然后再将其转发到远程的订阅方中。或者不经过存储,直接转发。除非消息中间件共享了模型的数据存储,不然即时提交需要XA(分布式事务).

image.png

创建具有聚合特征的领域事件

  1. 富有行为和状态
  2. 唯一标识 事件相关问题:延迟、重复、乱序

一个领域事件应该具有聚合特征

如果订阅方还需要更多的操作,那么我们可以向事件中添加额外的行为和状态。这样订阅方便不用回头再对聚合进行查询,而只需要对接收到的事件进行查询即可。

image.png

模块

在DDD中,模型的模块表示了一个命名容器,用于存放领域中内聚在一起的类。 模块应该包含一组具有高内聚性的概念集合,这样做的好处是不同的模块直接实现松耦合。

在Java中,模块称为包。 模块应该和领域保持一致。

聚合

将实体和值对象在一致性边界之内组成聚合。 原则1:在一个事务中只修改一个聚合实例 原则2:设计小聚合 原则3:通过唯一标识引用其他聚合 原则4:在边界之外使用最终一致性

image.png

将实体和值对象在一致性边界之内组成聚合。乍看起来是一项轻松的任务?聚合只是将一些共享父类、密切关联的对象聚集成一个对象树吗? 在DDD众多战术性指导中,聚合是最不容易理解的。

不正确的聚合模型: 为了对象组合上的方便而将聚合设计得很大。 过于贫瘠,丧失了了保护真正不变条件的目的。

我们通常使用单事务来管理一致性。在提交事务时,边界之内的所有内容都必须保证一致。 对于一个设计良好的聚合来说,无论由于何种业务需求而发生改变,在单个事务中,聚合中的所有不变条件都是一致的。

对于一个设计良好的限界上下文来说,无论在哪种情况下,它都能保证在一个事务中只修改一个聚合实例。

大聚合不易保证事务成功执行。 大聚合事限制了系统性能和可伸缩性。

无论如何都应该将事务设计得尽量小 小聚合不仅有性能和可伸缩性的好处,它还有助于事务的成功执行,即它减少了事务提交冲突。

在不持有对象引用情况下,我们是不能修改其他聚合的,因此可以避免在同一个事务中修改多个聚合。

可能会需要使用最终一致性,而不是原子一致性。

如果单次用户需求,需要修改多个聚合实例,使用最终一致性。延迟问题很多时候是可以接受的, 在一个大规模、高吞吐量的企业系统中,要使用所有的聚合实例完全一致是不可能的。认识到这一点,你便知道在较小规模的系统中使用最终一致性也是有必要的。

遵守原则,我们不应该找各种理由打破聚合原则。

避免在聚合中注入资源库或者领域服务。不然会造成失联领域模型 通过唯一标识引用其他聚合,我们应该在聚合命令方法执行之前进行查找,然后再将其传入命令方法。

可以向应用服务中注入资源库和领域服务。

工厂

将创建复杂对象和聚合的职责分配给一个单独的对象,该对象并不承担领域模型中的职责。 工厂提供了一个创建对象的接口,该接口封装了所有创建对象的复杂过程。

image.png

除了创建对象之外,工厂并不需要承担领域模型中的其他职责。 它只是一个工厂而已。

对于聚合来说,应该一次性创建整个聚合,并且确保它的不变条件得到满足。

资源库

资源库通常表示一个安全的存储区域,并且对其中所存放的物品起保护作用。 通常来说,聚合和资源库之间存在一对一的关系。 一个资源库应该模拟一个Set集合。

image.png

通常来说,聚合类型和资源库之间存在一对一的关系。 然而有时,两个或者多个聚合位于同一个对象层级中时,他们可以同享资源库。

一个资源库应该模拟一个Set集合,任何时候,重复添加相同的元素是没有效果的。 目标是设计并实现类似HashSet的资源库,但是采用的不是内存,而是真正的持久化存储。

事务放在应用层,应该尽量少使用事务。 注意资源库与Dao的区别。

集成限界上下文

  1. RPC
  2. 消息机制
  3. RESTFUL HTTP
  4. 共享文件和数据库

image.png

防腐层: 对TenantService来讲,它根本看不到对远程系统的访问,以及如何将远程系统的发布语言翻译成本地对象。

消息机制的问题: 延迟、重复、乱序

解决方法: 幂等 延迟重试

分布式系统计算原则:

  • 网络是不可靠的
  • 总会存在时间延迟,有时甚至非常严重
  • 带宽是有限的
  • 不要假设网络是安全的
  • 网络拓扑结构将发生变化
  • 知识和政策在多个管理员直接传播
  • 网络传输是有成本的
  • 网络是异构的

应用程序

应用程序通过用户界面向外展示领域模型的概念,并且允许用户在模型上执行各种操作。 用户界面使用应用服务来协调任务、管理实务,并执行一些必要的安全授权。

image.png

虚线表示依赖注入原则 实线表示操作分发

一个提供多个技术服务端口的业务服务通常称为系统。

应用程序通过用户界面向外展示领域模型的概念,并且允许用户在模型上执行各种操作。 用户界面使用应用服务来协调任务、管理事务,并执行一些必要的安全授权。

区分应用服务、领域服务

将应用服务和领域服务等同起来是错误的,他们并不相同, 我们应该将所有的业务领域逻辑放在领域模型中,不管是聚合、值对象还是领域服务。 而应用层是很薄的一层,并且只用他们来协调对模型的任务操作。

基础设施层的职责是为应用程序的其他部分提供技术支持。

Q&A

通过上面学习试一试回答下面问题吧?

  • Q: 领域事件中加入业务逻辑合不合适?
  • A: 合适
  • Q: 通过消息集成限界上下文需要注意什么问题?
  • A: 乱序、延迟、重复、丢失
  • Q: 如果业务用例一次修改多个聚合实例怎么处理?
  • A: 改业务
  • Q: 应用服务和领域服务的区别?
  • A: 顾名思义
  • Q: 资源库和DAO的区别?
  • A: dao特指数据库,资源库是泛指资源
  • Q: 实体的唯一标识在持久化时生成会有什么问题?
  • A: 持久化之前没有标识
  • Q: 子域和限界上下文的区别?
  • A: 问题域与解决方案域
0 0 投票数
Article Rating
订阅评论
提醒
guest
0 评论
最旧
最新 最多投票
内联反馈
查看所有评论
0
希望看到您的想法,请您发表评论x