什么才是正确的领域驱动实现架构?

作为一种系统建模方法,DDD同样涉及系统的体系架构设计。区别于分布式、事件驱动、消息总线等架构设计方法,DDD中的架构设计关注前面各章所介绍的聚合、实体、值对象、领域事件、应用服务以及资源库之间的交互方式和风格,并在设计思想上有其独特的考虑。本节内容将针对DDD特有的架构模式展开讨论,包括经典分层架构、整洁架构以及六边形架构。

DDD经典分层架构

在软件开发过程中,分层架构是最常见、也是最基础的一种架构模式。例如,针对一个Web应用程序,我们可以梳理如下图所示的架构图。


上图展示的就是经典的三层架构,包括用户界面层、业务逻辑层和数据访问层。最终,系统操作数据库完成了业务数据的持久化。本节内容将在经典三层架构的基础上,详细分析DDD中的分层架构模式。

  1. 错误的DDD分层架构

在上图的基础上,原则上我们可以设计四层架构、五层架构等多种多层架构体系。每一层次之间通过接口的方式进行交互,可以严格限制跨层调用,也可以支持部分功能的跨层交互以提供分层的灵活性。下图所示的就是在通用的分层架构基础上所构建的DDD经典分层架构。


暂且不论上图中所展示的分层交互是否合理,我们先来讨论图中所展示的分层组件。本质上,分层架构用于处理组件之间的依赖关系,上图展示了DDD中所包含的4种核心组件,即:

  1. 领域层组件

代表整个DDD应用程序的核心,包含聚合、实体、值对象、领域事件、应用服务、资源库等组件。

  1. 基础设施层组件

这里的基础设施组件范围比较广泛,即可以包括通用的工具类服务,也可以包括数据持久化等具体的技术实现方式。领域层组件中的部分抽象接口(如资源库接口)需要通过基础设施提供的服务得以实现,所以基础设施层组件对领域层组件存在依赖关系。

  1. 应用层组件

应用层组件面向用户接口,是系统对领域模型组件的一种简单封装,通常作为一种门面或网关对外提供统一访问入口,在用户接口和领域模型之间起到衔接作用。同时,因为基础设施组件是对领域模型组件部分抽象接口的具体实现,所以应用组件也会使用基础设施组件来完成业务操作。

  1. 用户接口层组件

用户接口处于系统的顶层,直接面向前端应用,调用应用层组件提供的入口完成用户操作。

基于以上关于DDD中技术组件及其依赖关系的分析,我们明确了上图展示的DDD经典分层架构图实际上存在一定的问题,最主要的问题就是领域层对基础设施层存在依赖,这是不合理的。因为领域层中的资源库接口需要借助于具体的数据访问组件才能得到实现,而数据访问组件属于基础设施层组件,所以是基础设施层依赖于领域层,而不是反其道而行。由此,我们也可以得出一个结论,即设计架构分层的前提是明确系统的核心组件,分层体现的就是对这些核心组件的层次和调用关系的梳理。

  1. 正确的DDD分层架构

那么,我们应该如何正确设计DDD的分层架构呢?为了回答这个问题,我们首先需要梳理架构分层的两个核心问题,即:

  1. 领域模型组件作为核心组件和其他组件之间的依赖关系是怎么样的?
  2. 领域模型组件的抽象接口由谁去实现?

这两个问题的答案决定了架构分层的不同表现风格。而为了更好的回答这两个问题,我们需要引入架构设计过程中的一组设计原则,包括:

(1)依赖性和稳定性原则

组件设计包含一系列原则,其中有三条原则与分层有直接的关系,分别是无环依赖原则、稳定依赖原则和稳定抽象原则。

无环依赖原则(Acyclic Dependencies Principle,ADP)指的是在组件的依赖关系中不能出现环路。稳定依赖原则(Stable Dependencies Principle,SDP)认为被依赖者应该比依赖者更稳定,也就是说如果组件B不如组件A稳定的话,就不应该让组件A依赖组件B。稳定抽象原则(Stable Abstractions Principle,SAP)强调组件的抽象程度应该与其稳定程度保持一致。稳定与抽象是相辅相成的,两者之间的关系示意可以参考下图。


下图 稳定与抽象的关系示例

在上图中,组件X是一个稳定且抽象的组件,因为它被多个组件所依赖。而组件Y则是不稳定的,意味着它也不可能很抽象。那么针对位于同一层级的组件A、B和C而言,它们的抽象和稳定性又应该如何把控呢?我们可以使用单一抽象层次原则(Single Level of Abstraction Principle,SLAP)。良好的分层架构要求一个方法中的所有操作都处于相同的抽象层次,即遵循所谓的单一抽象层次原则。

(2)依赖倒置原则

领域模型组件作为系统的核心理应是抽象且稳定的,也就是说它应该位于系统分层的底端,从而能被其他组件所依赖。用户接口组件直接面向用户,通常是最不稳定的,自然处于系统的顶层。而应用组件处于用户接口组件和领域模型组件之间,这点同样没有异议。那么剩下的就是需要明确基础设施组件的定位,也就是回答领域模型组件的抽象接口由谁去实现这一问题,这就需要进一步引入依赖倒置原则。

依赖倒置原则(Dependency Inversion Principle,DIP)也认为,高层组件不应该依赖于底层组件,两者都应该依赖于抽象;抽象不应该依赖于细节,细节应该依赖于抽象。

我们明确,各种具体的实现技术都不应该包含在领域模型组件中。以数据持久化技术为例,通常我们会以接口的方式抽象数据访问操作,然后通过依赖注入把实现这些数据访问接口的组件注入到领域模型中。这些数据访问的具体实现就可以统一放在基础设施组件中,也就是说基础设施组件实现了领域模型组件中的抽象接口。

基于以上分析,我们可以梳理各个层次之间的关系,从而形成正确的DDD分层架构,如下图所示。


上图的表现形式符合前面各条架构设计原则中的描述,我们通过分层架构管理了DDD中的组件依赖关系。

DDD整洁架构

本质上,所谓的整洁架构也是对DDD四大技术组件进行合理分层的一种架构模式。在架构设计时,整洁架构指导开发人员设计出干净的应用层和领域模型层,确保它们对业务逻辑的专注度,而不掺杂任何具体的技术实现,从而完成领域模型与技术实现之间的完全隔离。

在整洁架构中,一个DDD应用程序可以分为四层,即:

  1. 实体层

实体(Entities)层封装业务规则。请注意它们封装了企业级的、最通用的规则,并且当外部环境发生变化时,这些实体是最稳定的。

  1. 用例层

用例(Use Cases)层则包含了具体的应用逻辑,它实现了所有的用户用例。这些用例使得内层的实体能够依靠实体内定义的业务规则来完成系统的用户需求。

  1. 接口适配器层

接口适配(Interface Adapters)层的目的就是进行数据的转换,将面向用户用例和实体层操作的数据结构转换成为面向数据库、消息通信等外部系统所能接收的数据模型。

  1. 框架与驱动器层

框架和驱动(Frameworks&Drivers)层由各种技术实现工具所组成,常见的包括数据库、Web框架、消息中间件等。我们把这些组件放在整个应用程序的最外层,它们对整个系统的架构不造成任何影响。

基于这种分层方式,整洁架构的整体结构如下图所示。


整洁架构的特性非常明确。层次越靠内的组件依赖的内容越少,位于核心的实体层没有任何依赖。层次越靠内的组件与业务的关系越紧密,因而越不可能形成通用的组件。实体层封装了企业级的业务规则,准确地讲,它应该是一个面向业务的领域模型。而用例层是打通内部业务与外部资源的通道,提供了输出端口与输入端口,但它对外展现其实是应用逻辑,或者说是一个用例。在接口适配层中,我们可以进入网关(Gateway)、控制器(Controller)与表示器(Presenter)等具体的适配器组件,用于打通应用业务逻辑与外层的框架和驱动器,从而实现各种用于访问外部资源的适配机制。

DDD六边形架构

DDD分层架构实际上是一种松散分层架构,位于流程上游的用户接口层和应用层,以及位于流程下游的具备数据访问功能的基础设施层都依赖于领域层,事实上已不存在严格意义上的分层概念。领域驱动设计思想认为应该推平分层架构,不使用严格的分层架构来构建系统,六边形架构(Hexagonal Architecture)也就应运而生。六边形架构促使我们转换视角重新审视一个系统。

六边形架构允许一个应用由用户、程序、自动化测试或批处理脚本驱动,并实现与数据库等外部媒介之间的隔离开发和验证。从设计初衷来讲,六角形架构允许隔离应用程序的核心业务并自动测试其行为,这是该架构在DDD领域中得到应用的核心原因。六边形架构的结构如下图所示,该图来自于软件工程大师Vaughn Vernon。


六边形架构同样表现为是一种分层架构,而且也是三层架构,包括应用程序层、领域层和基础设施层,它们之间的依赖关系如下图所示。


位于上图最上面的是应用程序层,这是DDD应用程序与用户或外部程序之间的交互层,通常包含一些系统交互类的代码,例如用户界面、REST API等。领域层位于上图中的中间位置,隔离应用程序和基础设施,包含所有关注和实现业务逻辑的代码。位于上图最下面的是基础设施层,它包含必要的基础结构类组件,例如与数据库交互的代码或者与其他应用程序的REST API调用代码。

就依赖关系而言,上图所示三层架构中的领域层最为稳定和抽象,所以被应用程序和基础设施层所依赖,而应用程序和基础设施层之间不应该存在任何依赖关系。这样做的好处是把应用程序、业务逻辑和基础架构的关注点分离开发,确保每层组件的约束对其他各层组件的影响较小。

最后,我们来讨论组件边界。在六边形架构中,我们通过引入适配器(Adapter)组件实现与数据库、文件系统、应用程序以及其他各种外部组件之间的集成。

如果你采用的是六边形架构,那么系统应该由内而外围绕领域组件展开,而划分系统的内外部组件成为架构搭建的切入点。可以看到,领域组件位于六边形架构的最内层,应用程序也可以包含业务逻辑,与领域组件构成系统的内部基础架构。而对于外部组件而言,通过各种适配器实现数据持久化、消息通信、各种上下文集成以及用户交互。基于依赖注入和Mock机制,我们可以方便地对适配器组件进行模拟和替换。

DDD架构的映射性

讲到这里,你可能会问,DDD所具备的经典分层架构、整洁架构、六边形架构等多种架构模式之间是否存在一些共性呢?答案是肯定的。事实上,通过分析,我们会发现这些架构的底层逻辑是高度一致性。

我们先来看分层架构和整洁架构,这两种架构模式的对应关系如下图所示。


通过上图的表现形式,我们不难看出,整洁架构和分层架构本质上是一致的,不同的只是具体的分层方式而已。

如果我们把讨论范围扩大到六边形架构,那么可以得到如下图所示的架构映射图。该图清晰展示了不同架构模式所具备的相通性。


我们来对上图进行分析。在上图中,我们可以看到把三种架构模式的组成结构分成了两个部分,即内核层(红色框所包含的部分)和外部层(红色框外部部分)。其中,内核层由应用层和领域层组成,这点对于三种架构模式都是一致的。而对于外部层而言,分层架构和整洁架构包含用户接口和基础设施,而六边形架构则由一系列适配器所组成。基于前面内容的分析,六边形架构中的适配器具备数据持久化、消息通信、各种上下文集成以及用户交互能力,相当于充当了用户接口以及基础设施的功能。

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

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

相关文章

基于Go编写一个人员管理系统案例

代码结构 人员结构体 package entity// 结构体:人 type Person struct {Id intName stringSex stringAge int }// 构造函数 func NewPerson(id int, name, sex string, age int) *Person {return &Person{Id: id,Name: name,Sex: sex,Age: age,} }人员…

【AutoGPT】踩坑帖(follow李鱼皮)

本文写于2024年5月7日 参考视频:AutoGPT傻瓜式使用教程真实体验! 对应文章:炸裂的AutoGPT,帮我做了个网站! 平台:GitPod 云托管服务 原仓库已经改动很大,应使用的Repo为:Auto-GPT-ZH…

人工智能将改变科研?从胰腺癌早筛到新药研发

去年底英国《自然》杂志刊文预测的2024年十大科学进展中,人工智能的进步和ChatGPT人工智能占据前两位。那么,人工智能对于科学而言,它的哪些成果将带来有益的发展?今天我们请知名科普作者张田勘来聊聊这个话题。 (1&am…

C语言 | Leetcode C语言题解之第76题最小覆盖子串

题目&#xff1a; 题解&#xff1a; char* minWindow(char* s, char* t) {int tLen strlen(t);int hash[256] { 0 };for (int i 0; i < tLen; i)hash[t[i]];for (int i 0; i < 256; i) {if (0 hash[i])hash[i] INT_MIN;}int left, right, count, start, minLen, s…

VBA 创建透视表,录制宏,自动化报表

目录 一. 数据准备二. 需求三. 准备好报表模板四. 执行统计操作&#xff0c;录制宏4.1 根据数据源创建透视表4.2 填充数据到报表4.3 结束宏录制 五. 执行录制好的宏&#xff0c;自动化报表 一. 数据准备 ⏹数据源1 姓名学科成绩丁志敏语文91李平平语文81王刚语文64张伊语文50…

城市运行管理服务平台架构

城市运行管理服务平台是一种集成化的信息系统&#xff0c;其根本宗旨在于推动城市的高效运作与精细管理&#xff0c;进而提升广大市民的生活质量&#xff0c;并致力于实现城市的长期、稳定与可持续发展。 一、平台架构 1、核心优势 2、7个应用系统 &#xff08;1&#xff09;…

C++聊天服务器数据库创建

创建数据库chat show databases&#xff1a;展示所有的数据库 create database chat&#xff1a;创建一个数据库chat use chat&#xff1a;使用数据库 创建表User、Friend、AllGroup、GroupUser、OfflineMessage 表User包含&#xff1a;用户id、用户名、用户密码、当前登录…

78.子集

1.题目 子集 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/subsets/ 2.思路 3.C代码实现 class Solution { public:vector<vector<int>> ret;vector<int> path;vector<vector<int>> subsets(vector<int>& …

MySQL: Buffer Pool概念整理

一. 简介 MySQL中的Buffer Pool是InnoDB存储引擎用来缓存表数据和索引的内存区域。这是InnoDB性能优化中最关键的部分之一。通过在内存中缓存这些数据&#xff0c;InnoDB可以极大减少对磁盘I/O的需求&#xff0c;因为从内存中读取数据远比从磁盘读取要快得多。因此&#xff0c…

项目管理在软件工程中的实践方法

软件工程是一个复杂的过程&#xff0c;涉及到需求分析、设计、编码、测试和维护等多个阶段。有效的项目管理对于确保软件项目成功至关重要。以下是结合附件内容&#xff0c;关于项目管理在软件工程中实践的一些方法。 1. 明确项目愿景和目标 在项目启动之初&#xff0c;项目经…

淘宝电商商家ERP订单接口接入指南:对接ERP与淘宝系统的数据桥梁

最近几年&#xff0c;电商发展如火如荼&#xff0c;一方面互联网企业在推互联网 和O2O&#xff0c;同时很多传统企业也在积极互联网&#xff0c;通过各种电商平台拓展销售渠道&#xff0c;有些还同时建有自建的电商平台。这些电商平台通常下单&#xff0c;结算&#xff0c;促销…

Cheetah3D for Mac - 轻松打造专业级3D作品

对于追求专业级3D作品的设计师来说&#xff0c;Cheetah3D for Mac无疑是一款不可多得的工具。 这款软件拥有强大的建模、渲染和动画功能&#xff0c;能够满足您在3D设计方面的各种需求。通过简单的操作&#xff0c;您可以轻松构建出复杂的3D模型&#xff0c;并为其添加逼真的材…

Gitlab自动化测试的配置

1. 代码分支命名规范检测 Setting → Repository → Push rules → Branch name&#xff0c;添加分支命名规范对应的正则表达式。如&#xff1a; ^(Release|Tag|Develop|Feature)_._.|Main$ 表示分支名只能以以下关键字之一开头&#xff1a;Release、Tag、Develop和Feature。 …

使用C++ __builtin_expect优化程序性能后,程序体积不改变原因

结论 使用__builtin_expect优化程序性能&#xff0c;开启-O3的情况下&#xff0c;确实程序的体积可能不改变&#xff0c;但是还是会产生优化效果。 测试代码 不使用__builtin_expect #include <iostream>void fun(int a, int b) {// 不使用__builtin_expectif (a <…

案例|200多套设备实时监测,守护江西彰湖水库安全

中型水库作为水利建设的重要组成部分&#xff0c;在防洪、供水、农业灌溉、改善民生和生态效益等方面都具有重要意义。国务院发布《关于切实加强水库除险加固和运行管护工作的通知》&#xff0c;重点提出要提升信息化管理能力&#xff0c;要加快建设水库雨水情测报、大坝安全监…

【XR806开发板试用】SPI驱动数码管显示

准备工作 安装repo 创建repo安装目录。 mkdir ~/bin下载repo wget https://storage.googleapis.com/git-repo-downloads/repo -P ~/bin/改变执行权限 chmod ax ~/bin/repo设置环境变量&#xff0c;在~/.bashrc文件的最后输入 export PATH~/bin:$PATH和export REPO_URLhttps://…

分布式光纤测温DTS的测温范围是多少?

分布式光纤测温DTS的测温范围不仅仅取决于光缆的感温能力&#xff0c;还受到多种复杂因素的影响。尽管高温光缆可以耐高温&#xff0c;低温光缆可以耐低温&#xff0c;甚至镀金光缆能够耐受高达700摄氏度的极高温度&#xff0c;然而&#xff0c;这些因素并不能完全解释测温范围…

Jmeter性能测试(六)

一、查询数据库进行参数化步骤 1、添加并配置JDBC Connection Configuration 2、添加并配置JDBC Request 3、添加并配置循环控制器组件 4、添加并配置计数器组件(控制循环中的变量取值) 5、通过函数助手生成引用变量名 6、引用变量进行参数化 二、添加配置JDBC Connection Co…

运用分支结构与循环结构写一个猜拳小游戏

下面我们运用平常所学的知识来写一个小游戏&#xff0c;这样能够加强我们学习的趣味性&#xff0c;并且能够更加的巩固我们所学的知识。 游戏代码&#xff1a; 直接放代码&#xff1a;&#xff08;手势可以使用数字来代替&#xff0c;比如0对应石头&#xff0c;1对应剪刀&…

【Linux第四课 - git、gdb】git仓库的使用、dgb代码调试

目录 一 、gitgit、gitee、github的理解Linux中git的使用提交删除 二 、gdb - 调试工具进入gdb版本gdb开始调试123、范围查找 一 、git 在linux中使用 分支管理、多人协作 git、gitee、github的理解 git是版本控制工具&#xff0c;gitee和github是网站 Linux中git的使用 提…