背景
为什么要制定参考工程架构
即使无法制定通用的、标准的工程应用架构,但为团队制定一个遵循领域驱动设计思想的参考架构依然有价值。基于以下原因:
- 为团队实践DDD的战术设计提供可以快速开始的工程参考
- 参考工程大量的命名和结构决策,显式的体现DDD的相关理念,有利于团队对DDD的战术实现达成一致认知
- 同时,参考架构有助于沉淀团队对领域驱动设计的一些思考和最佳实践
参考架构的考量因素
制定某个特定上下文下的参考架构却具有可行性和实践价值。针对于上下文的选择要尽量贴合实际的工程实践场景并考虑多维度的因素。
- 遵循领域驱动设计的本质思想
- 充分考虑业务系统建设特点
- 依赖最小化,保持轻量
希望工程参考架构能涵盖以下范围
- 分离业务域与技术域
- 多限界上下文场景
大多数团队基于DDD进行微服务拆分的时候,特别是系统建设初期,对单个微服务应用内的限界上下文的粒度需要权衡。由于团队组织架构因素及微服务成本问题,单个应用容纳的限界上下文一般是多个(理想情况下是1:1)。这些限界上下文随着后续的逐步迭代有可能会迁移至独立应用。因此,参考架构将多上下文的应用场景作为重要考量因素。
- 明确的组件、职责边界及依赖关系
- 支持领域报表场景:报表场景在业务系统较为常见,DDD并没有体现该场景的处理方式。作为工程参考架构,还是希望能够从实际业务出发,体现对写模型和报表模型的显示支持
- 外部依赖最小化:需要排除不必要的依赖,保持工程架构的轻量化
参考架构剖析
应用的多上下文结构
模块化和服务粒度及成本间进行权衡折衷。应用架构对多上下文的支持的逻辑示意图如下所示,在解决方案域对限界上下文进行识别和划分之后,基于其业务内聚性和关联性,把多个上下文实现单个工程应用中。单个应用内的多个限界上下文间可能存在交互,交互的形式可以是基于事件驱动,也可以是基于进程内调用。采用事件驱动的方式上下文间的耦合性对低一些,但一般需要引入事件总线的支持,额外组件的引入必然会导致复杂性的上升。进程内调用则耦合性会高一些,但从实现角度复杂度会低一些。具体选择哪种方式开发人员可以基于实际情况进行权衡。
多因素的权衡,并没有与子域与限界上下文1:1的理想化实践保持一致。
分层关注点
客户端
接入层
接入层是外部系统与应用内部业务能力的中间层,接入层是应用层对外的门面,是当前应用对外暴露业务能力的入口。该层的组成可能是对外提供的HTTP接口声明、分布式定时任务调度、消息监听器、RPC服务等等。其重要职责包括对外部系统的请求进行基础的参数校验、入参适配和服务路由(转发至系一层的应用服务)以及响应数据的适配。
业务层:
在每个限界上下文内采用分层架构,独立划分为应用层、领域层和网关层。
应用层:
不处理任何领域逻辑。
领域层:
领域层与应用服务的本质区别是:应用层不包含领域逻辑,领域逻辑全部下沉到领域层实现。
网关层:
应用的出口网关,是应用与外部基础设施交互的防腐层,处理所有技术相关实现。
rpc”,也有些团队将其命名为 “infrastructure”,不同的命名体现了团队对其背后所表达的隐喻的决策选择。在本文的参考架构中选择了 网关-Gateway这一命名,决策原因是:限界上下文自身是高内聚的,其与外部的交互需要统一出口,Gateway所表达的网关的含义恰当的表现了这种统一出口的理念。如果Facade层是应用的北向网关,是外部系统请求进入内部的入口。则此时的Gateway则表达的是限界上下文的南向网关,是内部应用连接外部的出口。
组件及依赖
Start组件:
Common组件
API 组件
统一门面组件:Facade
外部客户端触达应用系统的入口,也是内部应用服务的统一门面,类似于六边形架构风格下的适配器。参考架构中基于不同场景划分为 provider(RPC服务)、task(定时任务)、listener(MQ监听)、rest(http接口)等几个子包。外部请求进入系统后,由Facade组件完成入参基本校验、入参转换、服务路由以及出参转换等操作。另外,还可以承担处理登录态、鉴权以及日志等相关能力。
应用服务组件:Application Service
应用服务代表着用例以及系统行为,其通过委托到领域层和基础设施层(参考架构中的Gateway组件)完成用例的应用逻辑逻辑处理,可以理解为应用服务是领域层的客户端。该组件典型的职责:
- 重要事件通知到外部
- 出入参转化适配
- 事务处理外部
- 非领域逻辑的服务调用
External API
应用服务的逻辑不仅仅需要协调领域层,有时还需要依赖于外部的三方服务。External API 组件负责对这些外部服务进行接口声明定义,不做具体实现。
该组件不依赖其它组件,且仅被应用服务组件和Gateway组件依赖。网关组件依赖External API 组件的接口声明并提供底层技术实现,应用服务组件依赖其接口,并通过IOC方式将具体实现注入完成服务调用。
注意,该组件所依赖的服务不涉及领域逻辑,只是用于支撑应用服务的编排。如果是涉及了领域逻辑,则对外部服务依赖的接口定义需要下沉到Domain层。
Query
在限界上下文内作为与应用服务对等的组件存在,两个组件分别负责业务的查询和命令逻辑。
如果读侧和写侧都基于统一的领域模型,一般会导致领域模型的折衷设计。为了满足查询侧诉求,领域模型不得不引入额外的、领域无关的属性,由此造成领域模型的污染。
Domain
上图所体现的参考架构使用了DDD的战术设计的经典建模元素,比如聚合、实体、值对象、仓储、工厂以及领域事件等。在实际落地过程中,这些设计元素的抽象具有一定的挑战,设计过程中需要经过不断分析、权衡和重构以完成建模,这正是核心设计所在。
Gateway
网关层承担整个技术相关性的实现,是内部应用的出口网关。
网关层依赖于Domain、Application Service、External API和Query组件,负责上述四个组件定义的接口实现。在Gateway组件通过子包对实现进行隔离:
- query:查询服务组件的实现
- external:External API 组件中依赖外部服务的接口实现
- repository:仓储接口的实现
最后
应用架构模式的选择是系统架构设计的重要维度之一,结构不仅仅是简单的包结构和命名,其传达的是一种顶层抽象,背后包含了大量的实践和知识。制定符合团队情况的工程参考架构,并在团队成员间达成共识非常重要。领域驱动设计并没有统一的、通用的架构,试图定义标准架构是不切实际的。本文描述的工程架构只是一个参考,实践过程中应该基于团队特定情况而有所差异,但原则上都应该遵循业务域与技术域分离的核心理念。
内容来源:京东云开发者社区