DDD战略4 架构
2019-04-29 本文已影响0人
莫小归
GitChat课程《领域驱动设计--战略篇》笔记,课程作者张逸
一.分层架构
- 分层架构是运用最为广泛的架构模式,软件系统通过层(Layer)隔离不同的关注点(Concern Point),以应对不同需求的变化,同时隔离业务复杂度与技术复杂度,而引起技术实现变化的原因与引起领域逻辑变化的原因显然不同,这将导致基础设施和领域逻辑问题会以不同速率发生变化
1.经典的三层架构
- 用户界面层(User Interface Layer) + 业务逻辑层(Business Logic Layer) + 数据访问层(Data Access Layer)
- 当时企业系统都较为简单,本质上都是一个单体架构(Monolithic Architecture)的数据库管理系统,传统三层架构有效隔离了业务逻辑与数据访问逻辑,使得这两个关注点能够相对自由和独立地演化
2.DDD的经典分层架构
- User Interface Layer + Application Layer + Domain Layer + Infrastructure Layer
层次 | 职责 |
---|---|
用户界面/展现 层 | 向用户展现信息以及解释用户命令 |
应用层 | 很薄的一层,用来协调应用的活动。不包含业务逻辑,也不保留业务对象的状态,但保有应用任务的进度状态 |
领域层 | 包含关于领域的信息,是业务软件的核心所在。保留业务对象的状态,但对业务对象和它们状态的持久化被委托给了基础设施层 |
基础设施层 | 本层作为其他层的支撑库存在。提供层间的通信,实现对业务对象的持久化,包含对用户界面层的支撑库等作用 |
3.分层的依据与原则
- 分层架构模式有助于构建这样的应用:应用能被分解成子任务组,其中每个子任务组处于一个特定的抽象层次上
- 分层的依据
1)基于关注点为不同的调用目的划分层次。如传统三层架构:其上面向用户的体检与交互,居中面向应用与业务逻辑,其下面对各种外部资源与设备
2)面对变化。分层时应针对不同的变化原因确定层次的边界,严禁层次之间互相干扰,至少把变化对各层带来的影响降到最低。层与层之间应该是正交关系,即两层之间唯一相关的依赖点是两层之间的协作点
3)分层时应保证同一层的组件处于同一抽象层次
4.层之间的协作
- 传统分层架构的依赖都是自顶向下传递,层次越处于下端,与具体的业务隔离得越远,越通用越公用。而越通用的层越可能与外部平台或框架形成强依赖,若依赖的传递方向仍然自顶而下,就会导致系统的业务对象也随之依赖于外部平台或框架
- 依赖倒置原则(Dependency Inversion Principle,DIP)对传统的自上而下的依赖提出了挑战,要求高层模块不应依赖于底层模块,二者都应依赖于抽象,抽象不应该依赖于细节,细节应该依赖于抽象,因为抽象更稳定
- 高层模块不依赖于低层模块的优点有
1)低层模块的细节实现可以独立变化,避免变化对高层模块产生污染
2)编译时,高层模块可以独立于底层模块单独存在
3)对于高层模块,低层模块的实现是可替换的 - 根据依赖倒置原则,高层依赖于底层的抽象,即高层通过接口隔离了对具体实现的依赖,意味着整个具体依赖被转移到了外部,即发生了依赖注入(Dependency Injection)
- 结合依赖倒置原则和依赖注入,可以有效地解除高层对低层的依赖
二.分层架构的演化
1.整洁架构
- 在架构设计时,设计出干净的应用层和领域层,保持它们对业务逻辑的专注,而不掺杂任何具体的技术实现,从而完成领域与技术之间的完全隔离
- 分为四层
1)企业业务规则(Enterprise Business Rules)
2)应用业务规则(Application Business Rules)
3)接口适配器(Interface Adapters)
4)框架与驱动器(Frameworks & Drivers)
其中Application层面向应用,实现支持领域逻辑正常流转的非业务功能,通常为一些横切关注点,如日志、安全、事务等
- 整洁架构包括以下特征
1)层次越靠内的组件依赖的内容越少,处于核心的Entities没有任何依赖
2)层次越靠内的组件与业务的关系越紧密,因而越不可能形成通用的框架
3)Entities层封装了企业业务规则,准确地讲,它应该是一个面向业务的领域模型
4)User Cases层是打通内部业务与外部资源的通道,提供了输出端口与输入端口,但它对外的接口展现其实是应用逻辑,或者说是一个用例
5)Gateways、Controllers与Presenters本质都是适配器(Adapter),用于打通应用业务逻辑与外层的框架和驱动器,实现逻辑的适配以访问外部资源
6)系统最外层包括框架和驱动器,负责对接外部资源,不属于系统开发的范畴,但选择这些框架和驱动器,是属于设计决策要考虑的内容 - 属于适配器的Controllers、Gateways与Presenters可以视为网关(Gateway),对下,针对数据库、消息队列或硬件设备的适配器是南向网关,对于当前限界上下文是一种输出的依赖,对上,针对Web和UI是北向网关,对于当前限界上下文是一种输入的依赖
2.六边形架构
- 六边形架构(Hexagonal Architecture)在满足整洁架构思想的同时,更关注与内层与外层以及与外部资源之间通信的本质
- 内部六边形对应于DDD的应用层与领域层,外部六边形之外则为系统的外部资源,两个六边形之间的区域,均被视为适配器,并通过端口完成内外区域之间的通信与协作,故六边形架构又被称为端口-适配器模式
3.微服务架构
- 自顶向下
- 由内至外
三.领域驱动架构的演进
1.避免贫血的领域模型
- 传统的"贫血模式",位于数据访问层的Java Bean只包含get和set方法
- 合理将操作数据的行为分配给领域模型对象(Domain Model),即Entity和Value Object,而不是Service对象。同时,由于领域模型对象包含了领域逻辑,就需要从数据访问层转移到业务逻辑层。而那些不属于任何领域模型对象的领域逻辑,仍然应放在Service对象中
- 演进后的领域驱动架构如下
2.保证领域模型的纯粹性
- 上述架构中领域层与基础设施层之间产生了“双向依赖”,根据稳定依赖原则,需要让领域层依赖于一个更加稳定的基础,改进办法是增加DAOs的抽象
- 领域驱动将管理领域对象的数据结构抽象为资源库(Repository),通过资源库访问领域对象自然就成为一种领域行为,演进后的架构如下
3.用户展现层的变迁
- 用户展示层需要不同的用户交互方式与呈现界面,例如Web、Windows或者多种多样的移动客户端,因此在分层架构中,无法再用“用户展现层”涵盖整个业务系统的客户端概念
- 我们需要采取前后端分离的架构思想,使用户展现出分离为一个完全松耦合的前端层
- 为了简化前端与后端的通信集成,可以为系统引入一个开放主机服务(OHS),为前端提供统一而标准的服务接口,即Controllers组件,演化后的分层架构如下
4.引入应用层
- 由于领域层的设计粒度过细,Controllers需要与封装了Entity与Value Object的Aggregate、Services以及抽象的Repositories接口协作。基于KISS原则或最小知识原则,调用者了解的知识越少越好,调用越简单越好,这就需要引入一个间接的层来封装,这就是应用层存在的主要意义,演进后的架构如下
- 应用层是高层抽象的概念,表达的是业务的含义。领域层是底层实现的概念,表达的是业务的细节。DDD要求应用层不包含业务逻辑,但对外提供一个一致的体现业务用例的接口
5.基础设施层的本质
- 网关的含义有助于理解基础设施层的本质
- 网关组件自身会参与到业务中,但真正做的事情只是对业务的支撑,提供了与业务逻辑无关的基础功能实现
四.限界上下文与架构
1.限界上下文的架构范围
- 限界上下文不仅是针对领域模型的边界划分,还是对整个架构(包括基础设施层以及需要使用的外部资源)垂直方向的划分
- 对于基础设施层需要访问的外部资源,以及为了访问它需要重用的框架或平台,不属于限界上下文的代码模型,但仍属于架构设计的考量范围
2.限界上下文的通信边界
- 限界上下文之间是否为进程边界隔离,直接影响架构设计,这是因为进程内核进程间在如下方面存在迥然不同的处理方式
1)通信
2)消息的序列化
3)资源管理
4)事务与一致性处理
5)部署
6)系统对各个组件(服务)的重用方式与共享方式 - 进程内的通信边界
1)进程内通信意味着限界上下文运行在同一个进程中,可以通过实例化的方式重用领域模型或其他层次的对象。存在命名空间级别和模块级别两种设计方式,两者仅仅存在编译器的差异,模块级别的解耦更加彻底
2)限界上下文在进程内通信意味着彼此之间的协作更加容易和高效,但也更容易产生耦合,注意使用防腐层(ACL)隔离不同限界上下文 - 进程间的通信边界
1)进程间通信意味着限界上下文以进程为边界,彼此之间不能直接调用彼此的方法,需要通过分布式的通信方式,产生了数据库共享和零共享两种风格的架构
2)数据库共享架构
分开考虑代码模型和数据库模型,多个限界上下文代码运行是进程分离的,但共享彼此的数据库
3)零共享架构
每个限界上下文都有自己的代码库、数据存储以及开发团队,每个限界上下文选择的技术栈和语言平台也可以不同,限界上下文之间仅仅通过限定的通信协议和数据格式进行通信。
零共享保证每个限界上下文可以独立演化,易于管理。但需要考虑通信的健壮性,数据库的一致性,运维和监控的复杂度