【实践篇】领域驱动设计:DDD工程参考架构 | 京东云技术团队

news2024/10/5 15:25:26

背景

为什么要制定参考工程架构

不同团队落地DDD所采取的应用架构风格可能不同,并没有统一的、标准的DDD工程架构。有些团队可能遵循经典的DDD四层架构,或改进的DDD四层架构,有些团队可能综合考虑分层架构、整洁架构、六边形架构等多种架构风格,有些在实践中可能引入CQRS解决读模型与写模型的差异化等等。即使无法制定通用的、标准的工程应用架构,但为团队制定一个遵循领域驱动设计思想的参考架构依然有价值。基于以下原因:

  • 为团队实践DDD的战术设计提供可以快速开始的工程参考
  • 参考工程大量的命名和结构决策,显式的体现DDD的相关理念,有利于团队对DDD的战术实现达成一致认知
  • 同时,参考架构有助于沉淀团队对领域驱动设计的一些思考和最佳实践

参考架构的考量因素

虽然无法制定完全通用的DDD参考架构,但制定某个特定上下文下的参考架构却具有可行性和实践价值。针对于上下文的选择要尽量贴合实际的工程实践场景并考虑多维度的因素。

本文所述参考工程架构遵循以下原则:

  • 遵循领域驱动设计的本质思想
  • 充分考虑业务系统建设特点
  • 依赖最小化,保持轻量

希望工程参考架构能涵盖以下范围

  • 分离业务域与技术域

参考架构要遵循技术和业务隔离的特性,可以参考多种架构风格。业务与技术关注点的分离并不是DDD独有的特点,在六边形、整洁架构、洋葱架构中都遵循了这一重要原则。

  • 多限界上下文场景

大多数团队基于DDD进行微服务拆分的时候,特别是系统建设初期,对单个微服务应用内的限界上下文的粒度需要权衡。由于团队组织架构因素及微服务成本问题,单个应用容纳的限界上下文一般是多个(理想情况下是1:1)。这些限界上下文随着后续的逐步迭代有可能会迁移至独立应用。因此,参考架构将多上下文的应用场景作为重要考量因素。

  • 明确的组件、职责边界及依赖关系
  • 支持领域报表场景:报表场景在业务系统较为常见,DDD并没有体现该场景的处理方式。作为工程参考架构,还是希望能够从实际业务出发,体现对写模型和报表模型的显示支持
  • 外部依赖最小化:需要排除不必要的依赖,保持工程架构的轻量化

参考架构剖析

应用的多上下文结构

基于以上原则,参考工程考虑单个应用内多上下文的场景,以期在模块化和服务粒度及成本间进行权衡折衷。应用架构对多上下文的支持的逻辑示意图如下所示,在解决方案域对限界上下文进行识别和划分之后,基于其业务内聚性和关联性,把多个上下文实现单个工程应用中。单个应用内的多个限界上下文间可能存在交互,交互的形式可以是基于事件驱动,也可以是基于进程内调用。采用事件驱动的方式上下文间的耦合性对低一些,但一般需要引入事件总线的支持,额外组件的引入必然会导致复杂性的上升。进程内调用则耦合性会高一些,但从实现角度复杂度会低一些。具体选择哪种方式开发人员可以基于实际情况进行权衡。

需要再次说明的是,这种应用架构决策是一种多因素的权衡,并没有与子域与限界上下文1:1的理想化实践保持一致。

从上图的逻辑示意图我们再深入一层,从分层的维度去剖析一下详细的应用架构的展现形式,如下图所示:

分层关注点

客户端

客户端与应用处于不同的进程,是应用能力的消费端,在实际项目中可能是APP端、PC端、小程序端、公众号端或三方的业务调用端等等。

接入层

接入层是外部系统与应用内部业务能力的中间层,接入层是应用层对外的门面,是当前应用对外暴露业务能力的入口。该层的组成可能是对外提供的HTTP接口声明、分布式定时任务调度、消息监听器、RPC服务等等。其重要职责包括对外部系统的请求进行基础的参数校验、入参适配和服务路由(转发至系一层的应用服务)以及响应数据的适配

业务层:

该层是应用的业务逻辑所在层,整个架构风格采用模块化单体风格,在该层不同的限界上下文体现为不同的模块。在每个限界上下文内采用分层架构,独立划分为应用层、领域层和网关层

应用层:

协调领域对象、领域服务或外部依赖服务完成业务用例,该层只做能力协调,不处理任何领域逻辑

领域层:

领域层是整个分层的核心,与技术实现无关,主要负责领域模型、领域事件、领域服务定义,以及业务相关外部服务的接口抽象以及仓库的接口抽象等。

领域层与应用服务的本质区别是:应用层不包含领域逻辑,领域逻辑全部下沉到领域层实现。

网关层:

网关层定位是应用的出口网关,是应用与外部基础设施交互的防腐层,处理所有技术相关实现

该组件的命名有多种方式,比如有些团队将其命名为 “rpc”,也有些团队将其命名为 “infrastructure”,不同的命名体现了团队对其背后所表达的隐喻的决策选择。在本文的参考架构中选择了 网关-Gateway这一命名,决策原因是:限界上下文自身是高内聚的,其与外部的交互需要统一出口,Gateway所表达的网关的含义恰当的表现了这种统一出口的理念。如果Facade层是应用的北向网关,是外部系统请求进入内部的入口。则此时的Gateway则表达的是限界上下文的南向网关,是内部应用连接外部的出口。

组件及依赖

从宏观的分层我们再深入一层看下每层的组件划分。如下图所示:

Start组件:

整个应用的启动入口、加载应用配置信息等等。

Common组件

提供在不同的限界上下文间复用的领域模型元素的抽象,比如对Command、Query、Event、Entity、ValueObjec通用抽象等。当然,领域模型的通用抽象不是必须在Common组件内以提供复用,也可以作为一个独立的限界上下文,并以共享内核方式与其它上下文进行共享,或者也可以实现为独立的jar包组件。

API 组件

RPC类型服务的接口声明组件,以公司内部使用的JSF为例,该组件是应用对外部系统暴露的JSF API的组件。该组件可以是独立的工程,当然,有些团队会将其作为一个Module放入应用工程中。

统一门面组件:Facade

外部客户端触达应用系统的入口,也是内部应用服务的统一门面,类似于六边形架构风格下的适配器。参考架构中基于不同场景划分为 provider(RPC服务)、task(定时任务)、listener(MQ监听)、rest(http接口)等几个子包。外部请求进入系统后,由Facade组件完成入参基本校验、入参转换、服务路由以及出参转换等操作。另外,还可以承担处理登录态、鉴权以及日志等相关能力。

应用服务组件:Application Service

应用服务代表着用例以及系统行为,其通过委托到领域层和基础设施层(参考架构中的Gateway组件)完成用例的应用逻辑逻辑处理,可以理解为应用服务是领域层的客户端。该组件典型的职责:

从存储层加载领域对象、委托领域对象执行领域逻辑、保存领域对象

  • 重要事件通知到外部
  • 出入参转化适配
  • 事务处理外部
  • 非领域逻辑的服务调用

External API

应用服务的逻辑不仅仅需要协调领域层,有时还需要依赖于外部的三方服务。External API 组件负责对这些外部服务进行接口声明定义,不做具体实现。

应用服务组件不直接依赖这些外部服务实现,而是依赖其接口抽象。同时,此处的模型定义是基于该限界上下文的语义,是一种对外部模型的适配。

该组件不依赖其它组件,且仅被应用服务组件和Gateway组件依赖。网关组件依赖External API 组件的接口声明并提供底层技术实现,应用服务组件依赖其接口,并通过IOC方式将具体实现注入完成服务调用。

注意,该组件所依赖的服务不涉及领域逻辑,只是用于支撑应用服务的编排。如果是涉及了领域逻辑,则对外部服务依赖的接口定义需要下沉到Domain层。

Query

Query组件解决领域相关的报表查询场景,在限界上下文内作为与应用服务对等的组件存在,两个组件分别负责业务的查询和命令逻辑。

虽然引入了Query组件对报表场景提供支持,但没有完全的引入CQRS这一模式。在很多资料中CQRS与DDD同时提及的概率比较高,因为,在DDD下我们解决了复杂的面向领域的写侧模型,但在报表场景下,这种富领域模型有可能并不是最佳选择。如果读侧和写侧都基于统一的领域模型,一般会导致领域模型的折衷设计。为了满足查询侧诉求,领域模型不得不引入额外的、领域无关的属性,由此造成领域模型的污染。

Domain

Domain组件是领域逻辑核心,承担整个系统领域逻辑的实现,其定义了领域模型、领域服务、领域事件以及仓储层的抽象。该组件不依赖其它组件(除了通用的领域模型抽象组件Common之外。

上图所体现的参考架构使用了DDD的战术设计的经典建模元素,比如聚合、实体、值对象、仓储、工厂以及领域事件等。在实际落地过程中,这些设计元素的抽象具有一定的挑战,设计过程中需要经过不断分析、权衡和重构以完成建模,这正是核心设计所在。

Gateway

网关层承担整个技术相关性的实现,是内部应用的出口网关。

技术相关性是网关组件区别于其它组件的根本特性。在该组件内要处理技术实现的所有细节,比如与外部服务、中间件、DB的交互等。同时,其与Domain组件以及External API组件的接口抽象协作,共同承担了系统与外部依赖间(包括外部服务以及应用依赖的中间件、DB等基础设施)的防腐层职能,负责内部模型到外部模型转化、外部模型到内部模型转化以及具体交互。基于网关组件的特性,也非常适合在该层做统一的外部服务数据缓存及降级熔断处理。

网关层依赖于Domain、Application Service、External API和Query组件,负责上述四个组件定义的接口实现。在Gateway组件通过子包对实现进行隔离:

  • query:查询服务组件的实现
  • external:External API 组件中依赖外部服务的接口实现
  • repository:仓储接口的实现

最后

应用架构模式的选择是系统架构设计的重要维度之一,结构不仅仅是简单的包结构和命名,其传达的是一种顶层抽象,背后包含了大量的实践和知识。制定符合团队情况的工程参考架构,并在团队成员间达成共识非常重要。领域驱动设计并没有统一的、通用的架构,试图定义标准架构是不切实际的。本文描述的工程架构只是一个参考,实践过程中应该基于团队特定情况而有所差异,但原则上都应该遵循业务域与技术域分离的核心理念

作者:京东科技 倪新明

内容来源:京东云开发者社区

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/553823.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据仓库选择Greenplum还是SQL-on-Hadoop

Greenplum和Hadoop都是为了解决大数据并行计算而出现的技术,二者的相似点在于: 分布式存储数据在多个节点上。采用分布式并行计算框架。支持向外扩展来提高整体的计算能力和存储容量。支持X86开放集群架构。 但两种技术在数据存储和计算方法上&#xf…

ADS-600树脂,除COD有机物树脂,大孔树脂型号,矿井水有机物

基于吸附功能的聚苯乙烯特种树脂 Tulsimer ADS-600 是一款没有离子官能基的,由交联聚苯乙烯合成的功能强大的吸附型树脂。 Tulsimer ADS-600 主要应用于水溶液中吸附酚及其化合物,氯代烃等含氯物质,表面活性剂,氨基酸&#…

Python系列模块之标准库OS详解

感谢点赞和关注 ,每天进步一点点!加油! 目录 ​一、模块 1.1 模块的定义 1.2 模块的分类 1.3 模块的基本导入语法 二、Python中的包 三、标准库之os模块 实战: 钉钉告警应用 一、模块 1.1 模块的定义 Python 模块(Module)&a…

【JAVA程序设计】(C00139)基于Springboot+Thymeleaf的药店管理系统

基于SpringbootThymeleaf的药店管理系统 项目简介项目获取开发环境项目技术运行截图 项目简介 本项目为基于SpringbootThymeleaf的药店管理系统(医院药品管理系统、医院管理系统),本项目主要功能为: 药品库存管理(登记…

0基础学习VR全景平台篇第27章:场景管理-视角功能

大家好,欢迎收看蛙色平台免费教程! 功能位置示意 一、本功能将用在哪里? 视角功能:指观看者打开场景时,默认展示的位置内容,可针对视角显示范围、视角远近范围进行设置。 应用场景:几乎每个作品…

通过nginx解决vue项目打包后跨域代理失效问题

这里 我的接口是写在 http://localhost:80上的 接口名是user 需要一个id路径参数 我们知道 80是默认 所以就可以不带端口因此访问就是http://localhost/user/1 然后 我vue项目中的 vue.config.js 配置如下 const { defineConfig } require(vue/cli-service) module.exports …

00后职场人,怎么做到刚来就涨薪5k? 原因竟是学会了这个宝藏工具

经常有粉丝问我有“有哪些适合绝大多数普通职场人的制作大屏的工具?” 确实,在这个内卷的时代,大家想要的是更加炫酷的汇报效果。要满足大多数职场人的大屏工具首先一定要满足以下三点: (1)简单易上手&am…

使用Python技术开发一个社区婴幼儿预防接种系统

婴幼儿预防接种作为社区预防保健的主要工作之一,是儿科临床和公共卫生保健的基础,目前国内外对婴幼儿预防接种工作非常重视。我们社区在工作中改变传统服务理念,根据不同年龄段婴幼儿的心理状态,采取不同心理护理应用,进行婴幼儿预防接种。 本论文主要论述了如何使用Python技术…

国际原油如何投资?国际原油期货投资策略有哪些?

国际原油产品的规模比较大,市场的交易环境是非常安全的,相信很多人都对国际原油产品的投资比较感兴趣,但是个人投资者需要注意:要买卖实体原油几乎是不可能的,投资者参与国际原油投资最方便快速的方式,就是…

【TCP】滑动窗口

滑动窗口 概念: 我们都知道 TCP 是每发送一个数据,都要进行一次确认应答。当上一个数据包收到了应答了, 再发送下一个。 这个模式就有点像我和你面对面聊天,你一句我一句。但这种方式的缺点是效率比较低的。 如果你说完一句话…

大数据面试题:请描述MapReduce中shuffle阶段的工作流程,如何优化shuffle阶段?

map阶段处理的数据如何传递给reduce阶段,是MapReduce框架中最关键的一个流程,这个流程就叫shuffle。 shuffle: 洗牌、发牌——(核心机制:数据分区,排序,合并)。 shuffle是Mapreduce的核心&…

【社区图书馆】记:读完《网络工程师的Python之路:网络运维自动化实战(第2版)》读后感【书评】

🍁博主简介 🏅云计算领域优质创作者   🏅华为云开发者社区专家博主   🏅阿里云开发者社区专家博主 💊交流社区:运维交流社区 欢迎大家的加入! 文章目录 书籍整体感受书籍中分了11大章&#…

springboot+vue实习管理系统(源码+文档)

风定落花生,歌声逐流水,大家好我是风歌,混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的实习管理系统。项目源码以及部署相关请联系风歌,文末附上联系信息 。 💕💕作者:风歌&a…

数据库链接池从100ms优化到3ms!

数据库连接池的配置是开发者们常常搞出坑的地方,在配置数据库连接池时,有几个可以说是和直觉背道而驰的原则需要明确。 1万并发用户访问 想象你有一个网站,压力虽然还没到Facebook那个级别,但也有个1万上下的并发访问——也就是…

开发中造成空指针的常见用法,如何避免

1. 前言 《手册》的第 7 页和 25 页有两段关于空指针的描述: 【强制】Object 的 equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用 equals。 【推荐】防止 NPE,是程序员的基本修养,注意 NPE 产生的场景: 返回类型为…

AIGC周报|让AI来画《海贼王》;苹果限制员工使用ChatGPT;李彦宏:不担心大模型会让工作消失

AIGC(AI Generated Content)即人工智能生成内容。近期爆火的 AI 聊天机器人 ChatGPT,以及 DallE 2、Stable Diffusion 等文生图模型,都属于 AIGC 的典型案例,它们通过借鉴现有的、人类创造的内容来快速完成内容创作。 …

实现BIM的Revit软件学习资料

实现BIM的Revit软件学习资料 一、BIM与Revit的关系二、Revit使用方法总结(一)快捷键(二)一些技巧 一、BIM与Revit的关系 链接: BIM与Revit是什么关系?看完秒懂系列! REVIT是实现BIM理念的工具之一。 二、Revit使用方…

pyton打包成exe程序简易教程

我们知道,Python程序需要有python环境才能够运行的,但当我们开发出一个程序之后,给到用户 那么用户是不会去添加这个python环境的。 所以为了用户也能够正常使用,我们就需要将这个程序打包成exe程序。 1.打包之前需要先下载一个模…

Java面试知识点(全)-dubbo面试知识点

Dubbo是什么? Dubbo是阿里巴巴开源的基于 Java 的高性能 RPC 分布式服务框架,现已成为 Apache 基金会孵化项目。 面试官问你如果这个都不清楚,那下面的就没必要问了。 官网:http://dubbo.apache.org 为什么要用Dubbo&#xff1f…

基于 Socket 接口实现自定义协议通信

访问【WRITE-BUG数字空间】_[内附完整源码和文档] 根据自定义的协议规范,使用 Socket 编程接口编写基本的网络应用软件。 掌握 C 语言形式的 Socket 编程接口用法,能够正确发送和接收网络数据包 开发一个客户端,实现人机交互界面和与服务器的…