您当前的位置:首页 > 电脑百科 > 程序开发 > 架构

DDD 必备架构--六边形架构

时间:2023-11-09 15:03:48  来源:今日头条  作者:后端技术分享

架构是研究“分”和“合”的艺术,通过“分离关注点”将系统拆分为多个部分,然后在“原则和规则”的约束下对组件进行装配,形成高内聚的构件;再根据需求对多个构件进行关联,形成低耦合的连接,最终构建“高内聚低耦合”的软件系统。

DDD 必备架构--六边形架构

 

image

为了有效应对软件复杂性,通常会对其进行分类,然后对症下药逐个击破。

1. 软件系统复杂性

面对一个软件需求,我们经常会将其分为两类:

  1. 功能性需求。就是产品提出的众多业务功能,例如:用户登录、查询数据、添加订单等;
  2. 非功能性需求。指系统在实现功能时必须满足的技术指标,最常见的包括性能、可靠性、安全性、可维护性、易用性等,例如:系统的响应时间、并发访问量、容错能力、数据安全性、可扩展性等。

其实,这两类需求整好与软件系统的两类“复杂性”一一对应:

  1. 业务复杂性,指系统中业务逻辑和业务规则的复杂程度。业务复杂性主要来自于业务的规模、结构、变化性等,这个与软件所在领域有极大关系;
  2. 技术复杂性,指系统中所用技术的复杂程度。技术复杂性主要来自于所使用的数据库、网络协议、中间件、应用框架等,这与软件架构和基础设施关系巨大;

面对不同的需求和复杂性,其设计目标和应对方式也完全不同。

1.1. 业务复杂性

首先,看下业务复杂性:

DDD 必备架构--六边形架构

 

image

对于业务需求,我们追求的是系统的复用性和扩展性。

  1. 复用性。指组件或模块能够在不同的场景下被重复使用。高复用性设计能够大幅度减少开发和维护的成本,提高开发效率。组件的粒度越小复用性越高,功能结构越清晰,逻辑调整越便利,与之相反的是代码重复率,重复代码是系统腐化的重要标志;
  2. 扩展性。能够在不改变现有系统结构的情况下,方便地添加新的功能或修改现有功能。具有高扩展性的系统能够满足未来需求的变化,降低系统的维护成本。

导致业务代码变化的原因有很多,比如:

  1. 线上bug。线上bug很少但对业务系统的伤害巨大,发现bug后为了快速修复问题,往往选择短平快的方式而非最佳方案,这些“补丁”就像系统中的“飞线”在代码中穿梭,成为超出三界的定时炸弹,一不小心就会给你意外的“惊喜”;
  2. 新功能需求。这是代码膨胀的主要推动力,开发人员将产品提出的需求翻译成代码,不停的“塞入”到代码仓库,导致仓库快速膨胀,很快你将面对几十万行代码并在之上进行新的开发;
  3. 创新性业务。意味着新建系统、新建仓库、新建服务,看起来一切非常良好,可以一次性甩掉多年的历史包袱,但公司整个系统变得越来越复杂,甚至没人能说出服务间的调用关系;

这些变更都会导致业务代码越来越多、逻辑越来越复杂,最终变得难以维护。

为了更好的应对这些变化,常见的手段包括:

  1. DDD, 构建于“领域模型”基础之上,使用面向对象的各种语言特性,实现逻辑的封装、复用;将业务概念和实现组件结合在一起,避免相互转化,从而降低沟通成本;
  2. 重构,随着业务的变化,对原有代码结构进行优化,在新结构上以扩展的方式完成新功能的添加,不断地对代码结构进行调优
  3. TDD,重构的重要保障,在优化代码结构的同时,保障不会破坏原有的业务逻辑

业务复杂性先简单介绍到这,接下来看下技术复杂性:

1.2. 技术复杂性

DDD 必备架构--六边形架构

 

image

对于非功能需求,我们追求的是系统的高性能和高可用。

  1. 高性能。在同等资源下,要么让系统运行尽可能快,要么让系统吞吐尽可能大
  2. 高可用。尽量保障系统 7 * 24h 不间断的提供服务,避免由于服务中断导致公司损失

导致技术复杂性激增的原因有很多,比如:

  1. 用户和并发量。随着用户和并发量的激增,系统承受的压力将越来越大,一个小小的卡点便能造成巨大的损失
  2. 系统数据量。随着系统数据量不断积累,当单表数据量超过 亿 级,不管是访问速度还是异常恢复都将面临巨大挑战
  3. 机器规模。当机器规模超过一定的阈值,硬件问题将频繁爆发,基本每天都会出现硬件故障,最终成为高可用的阻力

这些问题并不能通过简单的增加代码来解决,往往需要引入新技术或使用新方案:

  1. 新技术。当数据库表数据量达到“亿”级出现明显的性能瓶颈时,可以应用 分库分表 或 TiDB 来解决
  2. 新方案。当数据库读压力巨大,可以调整技术方案,使用数据库读写分离、增加缓存等方案解决

通过对比,是否有一种感觉:“业务”与“技术” 差距巨大,可以说是天壤之别。所以需要一种架构,能够对 “业务” 和 “技术” 进行隔离,降低两者的相互影响,使得:

  1. 技术调整不影响业务模型
  2. 业务调整不能依赖技术

六边形架构,便可以解决这个问题。

2. 六边形架构

六边形架构出自《实现领域驱动设计》一书,与“洋葱架构” 非常相似,都能很好的实现 “业务” 与 “技术” 的分离。

在 “Command侧”(详见CQRS架构) DDD 仍旧是最佳解决方案,所以在此使用 “六边形架构” 作为顶级架构。

六边形架构如下:

DDD 必备架构--六边形架构

 

image

该架构由内外两个六边形组成,这也是其名称来源:

  1. 内六边形属于业务域,用于应对业务复杂性。
  2. 外六边形属于技术域,用于应对技术复杂性。
  3. 其中内六边形是整个系统的核心,外六边形依赖于内六边形,而内六边形不依赖于外六边形
  4. 内外两个六边形存在清晰的边界,两者相互独立,互不影响,独自演进

2.1.【业务】内六边形

内六边形聚焦于业务逻辑,使用良好的设计应对业务的复杂性;通过提升系统的复用性和扩展性,来应对未来的变化。

2.1.1. DDD

DDD 是解决复杂业务的一把利器,将业务需求和代码实现相结合,通过对业务领域的深入理解,构建出可复用、可维护、可扩展的领域模型。

DDD 分为战略和战术两部分,在此重点阐述战术部分。战术模型主要包括:

  1. 实体(Entity):具有唯一标识的领域对象,具有丰富的属性和行为,表示需要持续跟踪的领域概念,比如 用户、订单、地址等;
  2. 值对象(Value Object):没有唯一标识的领域对象,具有属性和行为,一般用于表示某一个领域概念,比如 金额、邮箱、手机号等;
  3. 聚合根(Aggregate Root):一组由实体和值对象组成的高内聚对象集合,由一个根实体来管理它们,我们也称之为聚合根,比如 订单+订单项便组成了一个聚合,其中订单为聚合根;
  4. 工厂(Factory):创建领域对象的一种机制,也是设计模式在 DDD 中的落地,它隐藏了对象创建细节,并保障创建对象的有效性,主要解决复杂对象初始化问题;
  5. 存储库(Repository):提供对领域对象的持久化和检索功能,将领域对象从数据存储细节中分离出来,完成领域对象和存储引擎的解耦;
  6. 领域服务(Service):处理领域对象之间的交互,没有自己的状态,封装了一些业务逻辑,主要用于需要多个领域对象相互协作才能完成的业务场景,比如银行转账;
  7. 领域事件(Event):指在领域内发生的、有意义的、需要被捕捉的事件,主要完成服务内的流程解耦和服务间的系统解耦;

这些领域对象相互协作,共同承载复杂的业务场景,大幅提升了业务的复用性和扩展性。

2.1.2. TDD

也称为测试驱动开发,它的基本思想是在编写代码之前先编写测试,然后根据测试来编写代码,最后再运行测试。可以帮助开发人员更快地发现并修复代码中的bug,还可以让开发人员更加关注代码的设计,使代码更加健壮、可扩展和易维护。

业务模型与基础设施的解耦将为你的 TDD 带来众多好处:

  1. 提高测试的速度:使用 Mock 技术可以避免测试过程中涉及到缓慢的网络或数据库操作,从而提高测试速度,加快迭代周期;
  2. 提高测试可靠性:使用 Mock数据可以降低测试失败或不可靠的情况,避免由于数据变更所造成的测试不稳定问题;
  3. 更好的测试隔离:业务逻辑和基础设施可以并行开发,避免两者相互干扰而产生不确定行为;
  4. 有利于重构:业务逻辑和外部依赖分离,在重构代码时,可以聚焦于业务逻辑而不必担心外部依赖的稳定性和正确性;

2.1.3. 两顶帽子

两顶帽子,是落地重构的重要开发模式,是一种工作习惯,或者说是一种高效的工作流程。

两顶帽子法,将软件变更落地过程分为两个阶段:

  1. 优化结构阶段。在不改变软件行为的前提下,对软件结构进行优化,使其更具扩展性。比如:
    1. 【重构】抽取公共逻辑到方法、类,以便更好的被复用;
    2. 【设计模式】抽取模板方法,对核心逻辑进行统一;
    3. 【架构模式】抽取功能微内核,确定插件签名和功能;
  2. 添加功能阶段。在调整后的具备更好的扩展性的代码基础上完成功能调整或者增加新功能,如:
    1. 新功能复用已有组件能力,避免重复开发;
    2. 以扩展点的方式增加新功能,提升开发效率;

2.2.【技术】外六边形

外六边形聚焦于技术,与公司基础设施密切相关,也受所用框架的各种约束。其核心是发挥框架和中间件的优势,更好的为业务提供服务。

根据应用场景的不同,我们将外六边形的组件分成两类:

  1. 输入适配器。将来自外部的数据转换为系统可以使用的格式;
  2. 输出适配器。将系统内部的数据转换为外部可以使用的格式

他们是系统与外部的“翻译官”,将外部请求转换为系统可以理解的指令,并将系统响应转换为外部世界可以理解的格式,这种松耦合可以使系统更加灵活更具扩展性。

2.2.1. 输入适配器

负责将外部系统的请求转换为应用服务能够处理的格式,通常包括Web 请求、RPC调用、消息队列、定时任务等,是将外部请求转换为内部指令的桥梁。

常见的输入适配器主要包括:

  1. Web 请求。处理 HTTP 请求,对请求进行转换、验证,将其转换为应用服务所需格式,调用接口完成业务逻辑,最后将处理结果转化为所需格式进行返回。常见的框架有 Spring MVC、Struct2等;
  2. RPC 调用。处理远程调用请求,将请求转换为应用程序所需格式,调用应用服务接口完成业务逻辑,最后返回处理结果;在 Spring Cloud 技术栈下,与 Web 请求高度类似,但仍旧具有自己的特点,需要与 Web 请求进行区分处理。常见的 RPC 框架有 Spring Cloud、gRPC、Thrift、Dubbo等;
  3. 消息队列。主要指的是消息队列的消费端,从消息队列中读取消息,将信息转换为应用程序所需格式,调用应用服务接口完成业务逻辑。常见的有 RocketMQ、Kafka、RabbitMQ等;
  4. 定时任务。由定时器周期性触发,调用应用服务的业务方法,完成某种后台任务。常见的有Quartz、XXL-job、Spring Task 等;

2.2.2. 输出适配器

负责将应用程序输出结果转换为外部系统能够理解的格式,通常包括数据库、RPC调用、缓存、搜索、消息队列、文件系统等,是将内部响应转换为外部响应的桥梁。

常见的输出适配器主要包括:

  1. 数据库。将领域模型中的模型数据保存到数据库进行持久化存储,常用的框架包括 MyBatis、Jpa、Hibernate等,中间件主要是 MySQL
  2. 缓存。模型数据发生变更后,对缓存数据进行清理或更新,常见框架包括本地缓存 Guava、Caffeine、EhCache,分布式缓存有 redis、Memcache、TAIr等;
  3. 搜索。为应对多维度查询,系统会引入搜索引擎组件,在模型数据发生变更后,需要将变更同步到搜索引擎,常见的有Elasticsearch、Solr、Sphinx等;
  4. 消息队列。这里主要指的是消息队列的发送端,当业务操作完成后,系统会向外发布领域事件,以将变更通知到下游系统,常见的有 RocketMQ、Kafka、RabbitMQ等;
  5. RPC调用。这里主要指的是 RPC 的调用端,当业务模型对其他领域服务存在依赖时,需要通过 RPC 进行系统通信,常见的 RPC 框架有 Spring Cloud、gRPC、Thrift、Dubbo等;
  6. 文件系统。这里简单理解为系统的日志输出即可,常见的有Log4j、Logback、SLF4J、JUL等;

这么多纷繁复杂的框架、中间件从另一个层面也验证了,外六边形是以技术作为驱动的,其核心就是:如何更好的使用这些技术工具,发挥每个框架的强项。

3. 小结

任意一个业务系统都会面对两类需求:

  1. 来自业务的功能性需求;
  2. 来自技术的非功能性需求;

两类需求背后的驱动力(复杂性)完全不同:

  1. 功能性需求的驱动力在于业务自身的复杂性和多变性;
  2. 非功能性需求的驱动力在于架构的变更和技术的更迭;

为了更好的应对这两类变化,需要在架构上进行隔离,避免相互影响带来更多的复杂性,然后逐个击破。这就是六边形架构最擅长的领域:

  1. 内六边形。聚焦于业务解决功能性需求,常用的手段有 DDD、TDD、重构;
  2. 外六边形。聚焦于技术解决非功能性需求,需要使用好 输入适配器 和 输出适配器;


Tags:DDD   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
DDD 与 CQRS 才是黄金组合
在日常工作中,你是否也遇到过下面几种情况: 使用一个已有接口进行业务开发,上线后出现严重的性能问题,被老板当众质疑:“你为什么不使用缓存接口,这个接口全部走数据库,这怎么能扛...【详细内容】
2024-03-27  Search: DDD  点击:(11)  评论:(0)  加入收藏
DDD死党:单引擎查询利器
基于索引的单表查询,是 MySQL 正确打开方式!基于 QueryObject 的声明式查询,是简单查询的正确使用方式!1、应用场景单表查询在业务开发中占比最大,是所有 CRUD Boy 的入门必备,所...【详细内容】
2023-12-19  Search: DDD  点击:(121)  评论:(0)  加入收藏
DDD死党:内存Join——将复用和扩展用到极致
1. 为什么"内存Join"是个无法绕过的话题首先,我们先简单解释下,什么是“内存Join”。相信大家对关系数据库的 join 语句肯定不陌生,其作用就是通过关联关系从多个表中查询数据,...【详细内容】
2023-12-14  Search: DDD  点击:(212)  评论:(0)  加入收藏
我们聊聊DDD、SOA、微服务和微内核
DDD、SOA、微服务和微内核,看到经常有人把这几个概念拿出来一起讲。事实上,DDD和其他三个不是一个维度的东西。DDD其实特别好理解,DDD就是领域来驱动设计嘛,是一种设计思想。很...【详细内容】
2023-12-08  Search: DDD  点击:(232)  评论:(0)  加入收藏
DDD架构下的防御式编程:5大关卡共同保障业务数据的有效性
一般情况下,在流程达到存储引擎前,所有的验证规则必须全部通过,尽量不要使用存储引擎作为兜底方案。但有一种情况极为特殊,也就只有存储引擎能够优雅的完成,那就是唯一键保护。1....【详细内容】
2023-12-03  Search: DDD  点击:(141)  评论:(0)  加入收藏
DDD四层微服务架构
一、微服务搭建思路大家看到的这张架构图并不是空穴来潮,它是通过不断演变出来的,我们要从DDD四层架构、微服务架构两个维度去融合理解。这里的DDD四层架构适用于单个服务的工...【详细内容】
2023-11-24  Search: DDD  点击:(216)  评论:(0)  加入收藏
DDD 必备架构--六边形架构
架构是研究“分”和“合”的艺术,通过“分离关注点”将系统拆分为多个部分,然后在“原则和规则”的约束下对组件进行装配,形成高内聚的构件;再根据需求对多个构件进行关联,形成低...【详细内容】
2023-11-09  Search: DDD  点击:(374)  评论:(0)  加入收藏
DDD 与 CQRS 才是黄金组合,你觉得呢?
“数据密集型系统”越来越多的应用程序有着各种严格而广泛的要求,单个工具不足以满足所有的数据处理和存储需求。取而代之的是,总体工作被拆分成一系列能被单个工具高效完成的...【详细内容】
2023-11-08  Search: DDD  点击:(287)  评论:(0)  加入收藏
DDD与微服务集成的第一战役:客户端重试&服务端幂等
当一个接口从简单的内部调用升级为远程方法调用(RPC)会面临很多问题,比如: 本地事务失效。在内部调用时,多个方法通常在同一事务中执行,可以使用本地数据库事务来确保数据的一致性...【详细内容】
2023-10-30  Search: DDD  点击:(380)  评论:(0)  加入收藏
去哪儿网架构演进之路:微服务的尽头原来是DDD……
一、架构设计理念与技术1.架构演变路径图片 单体(又称巨石系统):所有业务融合于一体。在项目早期,公司一般会选择单体以降低运营等各方面成本。 服务化:随着业务飞速发展和流量增...【详细内容】
2023-10-11  Search: DDD  点击:(265)  评论:(0)  加入收藏
▌简易百科推荐
对于微服务架构监控应该遵守的原则
随着软件交付方式的变革,微服务架构的兴起使得软件开发变得更加快速和灵活。在这种情况下,监控系统成为了微服务控制系统的核心组成部分。随着软件的复杂性不断增加,了解系统的...【详细内容】
2024-04-03  步步运维步步坑    Tags:架构   点击:(5)  评论:(0)  加入收藏
大模型应用的 10 种架构模式
作者 | 曹洪伟在塑造新领域的过程中,我们往往依赖于一些经过实践验证的策略、方法和模式。这种观念对于软件工程领域的专业人士来说,已经司空见惯,设计模式已成为程序员们的重...【详细内容】
2024-03-27    InfoQ  Tags:架构模式   点击:(13)  评论:(0)  加入收藏
哈啰云原生架构落地实践
一、弹性伸缩技术实践1.全网容器化后一线研发的使用问题全网容器化后一线研发会面临一系列使用问题,包括时机、容量、效率和成本问题,弹性伸缩是云原生容器化后的必然技术选择...【详细内容】
2024-03-27  哈啰技术  微信公众号  Tags:架构   点击:(10)  评论:(0)  加入收藏
DDD 与 CQRS 才是黄金组合
在日常工作中,你是否也遇到过下面几种情况: 使用一个已有接口进行业务开发,上线后出现严重的性能问题,被老板当众质疑:“你为什么不使用缓存接口,这个接口全部走数据库,这怎么能扛...【详细内容】
2024-03-27  dbaplus社群    Tags:DDD   点击:(11)  评论:(0)  加入收藏
高并发架构设计(三大利器:缓存、限流和降级)
软件系统有三个追求:高性能、高并发、高可用,俗称三高。本篇讨论高并发,从高并发是什么到高并发应对的策略、缓存、限流、降级等。引言1.高并发背景互联网行业迅速发展,用户量剧...【详细内容】
2024-03-13    阿里云开发者  Tags:高并发   点击:(6)  评论:(0)  加入收藏
如何判断架构设计的优劣?
架构设计的基本准则是非常重要的,它们指导着我们如何构建可靠、可维护、可测试的系统。下面是这些准则的转换表达方式:简单即美(KISS):KISS原则的核心思想是保持简单。在设计系统...【详细内容】
2024-02-20  二进制跳动  微信公众号  Tags:架构设计   点击:(36)  评论:(0)  加入收藏
详解基于SpringBoot的WebSocket应用开发
在现代Web应用中,实时交互和数据推送的需求日益增长。WebSocket协议作为一种全双工通信协议,允许服务端与客户端之间建立持久性的连接,实现实时、双向的数据传输,极大地提升了用...【详细内容】
2024-01-30  ijunfu  今日头条  Tags:SpringBoot   点击:(9)  评论:(0)  加入收藏
PHP+Go 开发仿简书,实战高并发高可用微服务架构
来百度APP畅享高清图片//下栽のke:chaoxingit.com/2105/PHP和Go语言结合,可以开发出高效且稳定的仿简书应用。在实现高并发和高可用微服务架构时,我们可以采用一些关键技术。首...【详细内容】
2024-01-14  547蓝色星球    Tags:架构   点击:(115)  评论:(0)  加入收藏
GraalVM与Spring Boot 3.0:加速应用性能的完美融合
在2023年,SpringBoot3.0的发布标志着Spring框架对GraalVM的全面支持,这一支持是对Spring技术栈的重要补充。GraalVM是一个高性能的多语言虚拟机,它提供了Ahead-of-Time(AOT)编...【详细内容】
2024-01-11    王建立  Tags:Spring Boot   点击:(124)  评论:(0)  加入收藏
Spring Boot虚拟线程的性能还不如Webflux?
早上看到一篇关于Spring Boot虚拟线程和Webflux性能对比的文章,觉得还不错。内容较长,抓重点给大家介绍一下这篇文章的核心内容,方便大家快速阅读。测试场景作者采用了一个尽可...【详细内容】
2024-01-10  互联网架构小马哥    Tags:Spring Boot   点击:(115)  评论:(0)  加入收藏
站内最新
站内热门
站内头条