DDD领域驱动开发标准

领域驱动和微服务的关系

领域驱动和微服务的关系如下图所示:


领域驱动和微服务的关系

领域驱动划分微服务的方法论支持,其中限界上下文bounded context:定义了每个模型的应用范围,在单个Bounded Context中确保领域模型的一致性,不同的限界上下文中,可以不用保证一致性。通常我们根据团队的组织、软件系统的每个部分的用法及物理表现(如组件划分,数据库模式)来设置模型的边界。

领域驱动分层架构

领域驱动分层架构主要包含四层: 用户接口层、应用层、领域层、基础层。

用户接口层:面向前端提供服务适配,面向资源层提供资源适配。这一层聚集了接口适配相关的功能。

应用层:位于领域层的上一层,理论上不应该有业务规则或逻辑。主要用来实现服务组合和编排,协作完成业务操作,负责处理业务用例的执行顺序以及结果的拼装,以粗粒度的服务*API网关向前端发布提供安全认证、权限校验、事务控制、发送或订阅领域事件等功能。

领域层:实现领域的核心业务逻辑。这一层聚集了领域模型的聚合、聚合根、实体、值对象、领域服务和事件等领域对象,以及它们组合所形成的业务能力。

基础层:贯穿所有层的,为其它各层提供通用的技术和基础服务,包括三方工具、驱动、消息中间件、网关、文件、缓存以及数据库等。比较常见的功能还是提供数据库持久化。基础层包含基础服务,它可以采用依赖倒置设计,封装基础资源服务,实现应用层、领域层与基础层的解耦,降低外部资源变化对应用的影响。

领域驱动的分层设计

领域驱动分层架构开发标准

领域驱动的开发套件

User Interface Layer:

DTO
信息传输对象, DTO可以通过infrastructure utils 进行校验例如JRS-303工具

Controller/Provider/Listener
支持不同访问协议的控制器实现,比如:http/restful风格、tcp/二进制流协议、mq消息/json对象等等。

Application Layer

Assembler
组装器,负责dto对象和领域对象的转换例如:将多个domain领域对象组装为需要的dto对象,比如查询帖子列表,需要从Post(帖子)领域对象中获取帖子的详情,还需要从User(用户)领域对象中获取用户的基本信息。
组装器中不应当有业务逻辑在里面,主要负责格式转换、字段映射等职责。

Service
应用服务层,组合domain层的领域对象和基础设施层的公共组件,根据业务需要包装出多变的服务,以适应多变的业务服务需求。
应用服务层主要访问domain领域对象,完成服务逻辑的包装。
应用服务层也会访问基础设施层的公共组件,如rabbitmq,完成领域消息的生产等。

Domain Layer

业务领域层,是我们最应当关心的一层,也是最多变的一层,需要保证这一层是高内聚的。确保所有的业务逻辑都留在这一层,而不会遗漏到其他层。

Aggregate Root
一系列可以看成单一单元的领域对象的组合。如订单(order)和购物清单(line-items)都是单独的对象,但是将他们当成一个独立的单元(aggregate)。每个aggregate都有一个aggregate root,任何和外部交互应该只能通过aggregate root,这样aggregate root就可以确保aggregate的完整性。一个aggregate内部具有事务(数据一致性)边界。

聚合根

建议:设计聚合根的时候,尽量不要使聚合根产生分布式事务,尽量保证聚合根中的业务逻辑能在一个原子中。

1). 聚合根、实体、值对象的区别?

从标识的角度:
  聚合根具有全局的唯一标识,而实体只有在聚合内部有唯一的本地标识,值对象没有唯一标识,不存在这个值对象或那个值对象的说法
从是否只读的角度:
  聚合根除了唯一标识外,其他所有状态信息都理论上可变;实体是可变的;值对象是只读的
从生命周期的角度:
  聚合根有独立的生命周期,实体的生命周期从属于其所属的聚合,实体完全由其所属的聚合根负责管理维护;值对象无生命周期可言,因为只是一个值

2). 聚合根、实体、值对象对象之间如何建立关联?

聚合根到聚合根:通过ID关联
  聚合根到其内部的实体,直接对象引用
  聚合根到值对象,直接对象引用
  实体对其他对象的引用规则:
    ①能引用其所属聚合内的聚合根、实体、值对象
    ②能引用外部聚合根,但推荐以ID的方式关联,另外也可以关联某个外部聚合内的实体,但必须是ID关联,否则就出现同一个实体的引用被两个
    聚合根持有,这是不允许的,一个实体的引用只能被其所属的聚合根持有;
  值对象对其他对象的引用规则:只需确保值对象是只读的即可,推荐值对象的所有属性都尽量是值对象;

3). 如何识别聚合与聚合根?

明确含义:一个Bounded Context(界定的上下文)可能包含多个聚合,每个聚合都有一个根实体,叫做聚合根
  识别顺序:先找出哪些实体可能是聚合根,再逐个分析每个聚合根的边界,即该聚合根应该聚合哪些实体或值对象;最后再划分Bounded Context
  聚合边界确定法则:根据不变性约束规则(Invariant)。不变性规则有两类:
    ①聚合边界内必须具有哪些信息,如果没有这些信息就不能称为一个有效的聚合
    ②聚合内的某些对象的状态必须满足某个业务规则

聚合根示意
聚合根示意

Domain Object
领域实体。有唯一标识,可变的业务实体对象,它有着自己的生命周期。比如社区这一业务领域中,‘帖子’就是一个业务实体,它需要有一个唯一性业务标识表征,同时他的状态和内容可以不断发生变化。

Domain Value Object
领域值对象。可以没有唯一性业务标识,且一旦定义,他是不可变的,它通常是短暂的。这和java中的值对象(基本类型和String类型)类似。比如社区业务领域中,‘帖子的置顶信息’可以理解为是一个值对象,不需要为这一值对象定义独立的业务唯一性标识,直接使用‘帖子id‘便可表征,同时,它只有’置顶状态‘和’置顶位置‘,一旦其中一个属性需要发生变化,则重建值对象并赋值给’帖子‘实体的引用,不会对领域带来任何负面影响。

Domain Factory
领域对象工厂。用于复杂领域对象的创建/重建。重建是指通过respostory加载持久化对象后,重建领域对象。

Domain Service
领域服务。区别于应用服务,他属于业务领域层。由于ddd提倡充血模型的缘故,我们在建模的时候要尽量避免制造domain service。尽量把业务逻辑放到其他的domain object中(比如entity, value object中)。可以认为,如果某种行为无法归类给任何实体/值对象,则就为这些行为建立相应的领域服务即可, 传统意义上的util static方法中,涉及到业务逻辑的部分,都可以考虑归入domain service。

Domain Event
领域事件。领域中产生的一些消息事件,通过事件通知/订阅的方式,可以在性能和解耦层面得到好处。aggregate之间为了保证数据的一致性,使用事件驱动架构(Event-Driven Architecture)来实现数据的最终一致性(Eventual consistency)。而这些事件是通过aggregate root发布的domain events。

Repository
仓库。我们将仓库的接口定义归类在domain层,因为他和domain entity联系紧密。仓库用户和基础实施的持久化层交互,完成领域对应的增删改查操作。仓库的实际实现根据不同的存储介质而不同,可以是redis、oracle、mongodb等。鉴于现在社区服务的存储介质有三套:oracle、redis、mongodb,且各个存储介质的字段属性名不一致,因此需要使用translator来做翻译,将持久化层的对象翻译为统一的领域对象。

Facade
调用外部系统的门面,同Repository平级,在domain中被使用。

Translator(Convertor)
翻译器。将持久化层的对象翻译为统一的领域对象。翻译器中不应当有业务逻辑在里面,主要负责格式转换、字段映射等职责。

Infrastructure Layer

基础设施层提供公共功能组件,供controller、service、domain层调用。

Repository
对domain层repository接口的实现,对应每种存储介质有其特定实现,如oracle的mapper,mongodb的dao等等。repository impl会调用mybatis、mongo client、redis client完成实际的存储层操作。

Exception
异常分类及定义,同时提供公共的异常处理逻辑,具体由ExceptionHandler实现。

Transaction
提供事务管理,交给Spring管理。

Logging
日志模块

各层对象隔离转换

上一层对象的变动不应该影响下一层对象,所以领域驱动的开发模式中需要做到对象隔离,一般通过对象间的转换达到目的。
DTO: Data Transfer Object
DO: Domain Object (entity)
PO: Persistent Object

对象隔离转换

你可能感兴趣的:(DDD领域驱动开发标准)