领域驱动设计

1. 领域

1.1 领域的定义

  • 领域用来确定业务的边界与范围。是这个边界和范围内要解决的业务问题域。

1.2 领域的划分

  • 核心域
    • 决定产品和公司核心竞争力的子域。如腾讯的社交。阿里的电商。
  • 支撑域
    • 用于支持和协作建立核心域的其他子域。如很多公司的支付业务。
  • 通用域
    • 一般已有开源解决方案,不能直接给公司带来价值但又不可或缺。如认证系统,日志管理。

1.3 为何需要划分领域

  • 公司的资源是有限的,为了最优分配资源,实现利益最大化。
  • 优先做好核心域。还有余力可以将支撑域也做成核心域。等到公司体力做够大了再精力投入通用域。

2. 限界上下文

2.1 限界上下文的定义

  • 限界上下文定义了领域的边界
  • 在同一界限上下文中,我们可以使用统一的语言进行交互。
  • 用来封装通用语言和领域对象,提供上下文环境,保证在领域之内的一些术语、业务相关对象等有一个确切的含义,没有二义性。
  • 我们将限界上下文内的领域模型映射到微服务,就完成了从问题域到软件的解决方案
  • 一个限界上下文理论上就可以设计为一个微服务。

2.2 如何划分限界上下文

  • 首先进行事件风暴。
  • 然后提取领域对象。
  • 最后对领域对象聚合并分组,通过业务的内聚性和关联度划分边界,结合限界上下文的定义进行判断,并给出上下文名称。
  • 核心是将大的领域分解成高内聚低耦合的子域

3. 领域对象

3.1 什么是领域对象

  • 领域模型由领域对象组成。
  • 领域对象主要分为实体对象和值对象。

3.2 什么是实体

  • 实体对象拥有唯一标识符,该对象在经历各种状态变更仍能保证标识符一致。
  • 实体对象强调唯一标识。如商品是商品上下文的一个实体,通过商品的唯一ID标识一个商品,不管商品其他数据如何变化,只要这个ID不变,他始终代表同一个商品。
  • 在领域驱动设计中,实体类通常采用充血模型。

3.3 实体的数据库形态

  • DDD设计中是现有领域模型,再针对业务场景构建实体对象,最后将实体对象映射到数据持久化对象
  • 所以,实体对象和数据库持久化对象之间不一定是一一对应。可能是一对一,多对一,一对多,以及一对零。
    • 如权限实体对应user和role两个持久化对象
    • 如为了减少链表查询,将客户和账户两个实体映射为一个持久化对象。

3.3 什么是值对象

  • 没有标识符的对象叫做值对象。
  • 值对象时若干相互关联的属性集合。
    • 省、市、县和街道等属性构成了地址这个值类型。该类的一个具体实例就是一个值对象。
  • 实体类会包含业务逻辑,包含修改数据的行为。而值类型则基本不包含业务逻辑,很少涉及修改数据的行为。
  • 值类型一般是实体类型的一部分,用于描述实体的特征。

// 实体类
public class Person {
    public String ID;  // 实体类的标识字段
    public String name;
    public int age;
    public boolean gender;
    public Address address;  // 值类型作为实体类的属性。
    
    // 各种实体方法,省略
}

public class Address {
    public String province;
    public String city;
    public String country;
    public String streat;
    
    // 值对象方法,省略。
}


3.4 值对象的两种运行形态

属性嵌入

// 实体类
public class Person {
    public String ID;  // 实体类的标识字段
    public String name;
    public int age;
    public boolean gender;
    public Address address;  // 值类型作为实体类的属性。
    
    // 各种实体方法,省略
}

public class Address {
    public String province;
    public String city;
    public String country;
    public String streat;
    
    // 值对象方法,省略。
}

序列化大对象

// 实体类
public class Person {
    public String ID;  // 实体类的标识字段
    public String name;
    public int age;
    public boolean gender;
    public String address;  // json类型,将Address序列化后嵌入实体中。
    
    // 各种实体方法,省略
}

public class Address {
    public String province;
    public String city;
    public String country;
    public String streat;
    
    // 值对象方法,省略。
}

3.5 值对象如何持久化

  • 在DDD中,我们将值对象嵌入到实体对象对应的数据库表中。
  • 实体对象通过序列化大对象将值对象嵌入,简化了数据库的设计。
    领域驱动设计_第1张图片
  • 总结:在领域建模时,我们将部分对象设计为值对象,保留对象的业务含义,同时又减少了实体的数量;在数据建模时,我们可以将值对象嵌入实体,减少实体表的数量,简化数据库设计

3.6 值对象的优缺点

  • 优点
    • 简化数据库设计,减少实体表的数量。
  • 缺点
    • 无法满足基于值对象的快速查询。
    • 值对象缺乏概念完整性。(一般值对象作为实体表中的一个json字段)

4. 聚合

4.1 聚合的定义

  • 一个限界上下文中会有多种不同类型的实体。我们会按照高内聚低耦合的要求,将这些不同的实体进行聚合。
  • 每一个聚合里面一定要包含一个聚合根,以及它的上下文边界。
  • 70%的场景下,一个聚合内都只有一个实体,那就是聚合根。
  • 一个限界上下文可能包含多个聚合,但一个聚合只能存在于一个限界上下文
  • 同一个聚合下的实体通过领域服务进行交互,不同聚合下的实体交互通过应用服务进行交互

4.2 聚合根

  • 大多数情况下一个聚合里面只有一个实体,那就是聚合根。
  • 聚合内聚合根对实体和值对象采用直接对象引用的方式进行组织和协调。(所以当我们拿到聚合根实体后,那么该聚合下的所有实体和值对象也可以得到)
  • 聚合之间是通过关联外部聚合根ID的方式引用,而不是直接对象引用的方式。
  • 如果想要访问另一个聚合下 的实体,只能通过聚合根来访问。
  • 如:
    • 帖子--回复:帖子是一个聚合根,回复是一个聚合根,回复只聚合帖子的唯一标识
    • 用户--消息:用户是一个聚合根,消息是一个聚合根,消息值聚合根用户的唯一标识
    • 订单--订单子项:订单是一个聚合根,订单需要聚合订单子项,订单子项不是聚合根,它需要依托订单存在
    • 商品--收货地址:商品是一个聚合根,收货地址是一个聚合根,他俩之间无直接依赖关系。所以不需要聚合对方的唯一标识。

领域层包的划分规则通常为

----限界上下文
–domain
------聚合A
-------- (聚合根、实体、值对象、领域服务、资源库、领域事件)
------聚合B
-------- (聚合根、实体、值对象、领域服务、资源库、领域事件)

  • 各个职责
    • 资源库:提供聚合根或者持久化聚合根
    • 聚合根:负责封装实现业务逻辑
    • 领域服务:负责封装实现业务逻辑(对于不能直接通过聚合根完成的业务操作就需要通过领域服务)
    • 应用服务:不处理业务逻辑,只是对领域服务/聚合根方法调用的封装。

4.3 一次业务处理的经过

应用服务 ->领域服务  ->通过资源库获取聚合根
        		  ->通过资源库持久化聚合根
     		      ->发布领域事件
应用服务->通过资源库获取聚合根
       ->通过资源库持久化聚合根
       ->发布领域事件

4.4 调用原则

  • 聚合根不能直接操作其他聚合根,聚合根之间只能通过聚合根ID引用
  • 同限界上下文内的聚合之间的领域服务可直接调用
  • 两个限界上下文的交互必须通过应用服务层抽离接口(适配层适配)

5. 领域事件

5.1 领域事件驱动设计

  • 领域事件驱动设计可以切断领域模型之间的强依赖关系,事件发布完成后,发布方不必关心后续订阅方事件处理是否成功,实现了领域模型的接口。
  • 在领域模型映射到微服务系统架构时,领域事件可以解耦微服务,微服务之间的数据不必要求强一致性,而是基于事件的最终一致性。
  • 领域事件可以发生在微服务内的聚合之间,但是绝大多数是发生在微服务之间。
  • 事件驱动本质是将同步调用转为异步调用,由强一致性转为最终一致性

5.2 微服务内的领域事件

  • 用的不多,同一个微服务进程内,编程语言层面上几乎可以很好地进行业务编排交互。

5.3 微服务之间的领域事件

  • 跨微服务的事件机制要考虑事件构建,发布与订阅,事件数据持久化,消息中间件以及分布式事务机制
  • 如果微服务之间不使用领域事件,则微服务之间的访问也可以采用应用服务直接调用的方式,但是这个需要引用分布式事务机制,以确保数据的强一致性。

5.4 事件的基本属性与业务属性

基本属性

  • 事件唯一标识
  • 发生时间
  • 事件类型
  • 事件源

业务属性

  • 记录是按发生那一刻的业务数据。会随事件传输到订阅方

5.5 事件构建与发布

  • 事件基本属性和业务属性一起构成了事件实体,事件实体依赖聚合根。
  • 领域事件发生后,事件中的业务数据不再修改,因此业务数据往往以序列化值对象的形式保存。这种存储格式在消息中间件中也比较容易解析和获取。

public class DomainEvent {
    
    public String ID;
    public long timeStamp;
    public String source;
    public String type;
    public String data;

    // 事件的各个方法,省略
}

5.6 事件处理流程

  • 事件构建
  • 事件发布
  • 事件持久化
  • 事件接收
  • 事件处理

6. DDD分层架构

领域驱动设计_第2张图片

6.1 用户接口层

  • 负责向用户显示信息和解释用户指令。

6.2 应用层

  • 应用层是很薄的一层,理论上不应该有业务规则和逻辑。
  • 应用层位于领域层之上,因为领域层包含多个聚合,所以它可以协调多个聚合的服务和领域对象完成服务编码和组合,协助完成业务操作。
  • 此外应用层也是微服务之间交互的通道,它可以调用其他微服务的应用服务,完成微服务之间的服务组合和编排。
  • 应用服务应该在应用层,他负责服务的组合,编排和转发,负责处理业务用例的执行顺序以及结果的拼装,以及粗粒度的服务通过API网关向前端发布。
  • 应用服务还可以进行安全验证,权限校验,事务控制,发送和订阅领域事件等。

6.3 领域层

  • 实现企业核心业务逻辑。
  • 领域层包含聚合根、实体、值对象、领域服务等领域模型中的领域对象
  • 领域中的某些功能单一实体不能实现时,领域服务就会出马,它可以组合聚合内的多个实体,实现复杂的业务逻辑。

6.4 基础层

  • 基础层贯穿所有层,它的作用是为其他各层提供通用的技术和基础服务。
  • 如第三方工具,驱动,消息中间件,网关,文件,缓存以及数据库,还有数据持久化。

6.5 分层架构的原则

  • 每层只能与位于其下方的层发生耦合

你可能感兴趣的:(设计模式,java,运维,大数据)