为了账号安全,请及时绑定邮箱和手机立即绑定

六边形架构:实用指南

域驱动设计(DDD)快速入门所需的一切——

由_DALL·E_生成的

在当今快速发展的环境中,高效地组织代码对于构建可扩展、可维护和可测试的应用程序至关重要。尽管许多开发人员倾向于使用分层架构,这经常会带来紧密耦合、领域渗漏以及测试困难等问题。
六角架构 是一种模式,它提供了业务逻辑和技术细节之间清晰分离的灵活性,带来更大的灵活性。

一些背景信息:
我的Hexagonal架构之旅始于2020,感谢我的同事Alexey,是他让我了解到Hexagonal架构的概念。当时,我们只能找到一些理论文章,并为了模糊的例子,我们花了大量的时间进行实验。在这篇文章中,我想拆解Hexagonal架构,并提供一个使用Java和Spring的实用指南** .

为什么放弃分层架构?

分层架构一直是构建应用程序的默认方式,并且它一直很好地为我们服务。对于许多简单的项目来说,它提供了一种熟悉且逻辑清晰的方式来组织代码,将控制器、服务和存储库等功能区分开。随着应用程序的增长和演变,分层架构可能逐渐成为一个障碍,而不是优势。让我们来看看其中一些常见的问题。

  • 紧耦合:分层架构中的组件常常变得相互依赖,使得在不影响其他层的情况下适应或替换特定层变得困难。这种紧耦合在应用程序需要演进或与新技术集成时降低了灵活性。
  • 测试挑战:业务逻辑往往分散在多个层次上,这使得测试变得复杂,并需要依赖复杂的环境设置。
  • 灵活性降低:更改或更新领域模型,尤其是与特定技术代码绑定时,可能非常昂贵。技术堆栈变化时,更新嵌入技术层的核心领域逻辑变得复杂且容易出错。

当我们启动新的服务时,我们经常会构建一个干净、有条理的包结构,遵循分层设计。然而,尽管我们尽了最大努力,随着时间的流逝,这种结构往往会变得混乱。随着我们添加新功能、响应变化的需求或集成其他系统,严格的界限逐渐变得模糊,导致包结构变得混乱。

向着右方巨大的震动发展!如何应对这种情况?从哪里开始呢?

图1. 从分层架构演化到复杂的包结构
(由 DALL·E 生成)

分层架构本身并不是一种坏的设计或反模式。它是一种熟悉且默认的选择,通常在初期表现得很好。但对于复杂且不断演进的应用程序而言,改变方法可以带来显著的长期优势。

什么是六边形架构设计?

六边形架构并不是一个全新的概念;相反,它是一种转变视角,将领域模型(即我们对现实世界的思维表示)置于中心。通过专注于领域,六边形架构有助于与技术及非技术利益相关者之间的协作,从而使开发与业务需求更易对齐。从根本上说,六边形架构是一种以领域为中心的方法。其主要目标是使应用程序的核心领域独立于诸如API和数据库等技术细节。这种独立性是通过围绕应用程序的三个主要组件进行组织而实现的。

  • 领域:应用程序的核心,包含业务的核心逻辑和模型。领域是唯一真正理解业务并承载业务规则定义的部分。
  • 端口:允许领域与外界通信的接口。端口定义了期望的交互方式,使领域能够保持与特定技术细节的隔离。
  • 适配器:将领域与外部系统(如数据库、API 和用户界面)连接起来的实现。适配器负责处理与这些系统交互的具体细节,并将外部数据格式转换为领域自身的模型格式。

这种结构让业务逻辑与外部系统隔离开来,保持独立。适配器可以轻松更换,这使得更换或添加新技术而不影响核心业务变得更加简单。

图2:抽象的六角形结构

不要尝试将其用于具有简单业务逻辑的小项目!
这样做只会让代码变得更复杂,你将不得不至少使用三个不同的模型,并不断在这几个模型之间进行映射,而不是直接将你的DTO存储到数据库或传递给其他服务。

实施:设计一个酒吧管理系统

图3. 酒吧点单系统示例(使用DALL·E创建)

为了将六边形架构应用到实际中,让我们通过一个酒吧系统的实例来看看如何实现。以下是一些关键功能(或例如用户故事):

  1. 接单:接客户的单并管理。
  2. 找配方:找各种饮料的配方。
  3. 调制饮料:调制客户点的饮料,按照配方来。

在传统的分层架构里,结构可能看起来像这样(在左边),而右边则展示了相同功能在六边形架构中是如何组织的。

图4. 从分层包结构转变为六边形设计

入站端口和适配器: 控制器通过“in 端口”连接到领域,处理来自外部的请求或交互(例如,HTTP 请求)。在这种情形下,订单请求将通过一个 in 端口 进行处理,该端口由 OrderController 使用,并由领域中的 MixologyService 实现。

领域模型:这部分包含了核心的业务逻辑。例如,MixologyService这样的服务会处理调制饮料的逻辑,使用Cocktail模型表示业务逻辑。它还会与外部系统如食谱和订单进行交互。

出端口:“出端口”定义了域与外部系统如何互动。例如,在本例中,RecipeCatalogOutPort 表示用于访问食谱目录的接口,域服务使用该接口来查找饮料配方。

图5. 一个示例映射到六边形架构(Hexagonal Architecture)。

让我们通过代码片段来看看这个结构的实际表现吧!

OrderController 是一个适配器,它将外部世界(HTTP 请求,)与领域模型连接起来。它与 CocktailOrderInPort,一个 输入端口 进行交互,而不关心领域逻辑的具体实现。

@RestController  // 注解@RestController表示该类是一个REST风格的控制器
class OrderController {  // 订单控制器,处理与订单相关的请求

    private CocktailOrderInPort cocktailOrderPort;  // 定义一个私有成员变量,用于调用订单处理接口

    @PostMapping  // 注解@PostMapping表示该方法处理POST请求
    public CocktailDTO sendOrder(@RequestBody String orderRequest){  // 发送订单方法,接收订单请求并处理
        Cocktail cocktail = cocktailOrderPort.heyGiveMeDrink(orderRequest);  // 获取饮料的方法,根据订单请求返回鸡尾酒
        return CocktailDTO.from(cocktail);  // 将Cocktail对象转换为DTO对象,DTO是数据传输对象,用于传输鸡尾酒的信息
    }  
}

在这个设置中,MixologyService 代表领域服务,实现了输入端口(CocktailOrderInPort)。在处理订单时,它使用 RecipeCatalogOutPort 来获取配方信息,将领域逻辑与如数据库实现等技术细节隔离。为了简化起见,酒吧的原料是无限的(假设),并省略了 BarStoragePort。

    @Service  
    public class MixologyService implements CocktailOrderInPort {  

        private final RecipeCatalogOutPort recipeCatalogPort;  
        private final DomainEventObserverPort observer;  

        @Override  
        public Cocktail 嘿,给我来一杯(String order) {  

            var maybeCocktail = recipeCatalogPort.find(order);  

            if (maybeCocktail.isEmpty()) {  
                observer.observe(MixologyEvent.failedToFindRecipe(order));  
                throw new RuntimeException("我们这里没有能调制的配方哦,您要的可能是:" + order);  
            }  
            var cocktail = maybeCocktail.get();  
            observer.observe(MixologyEvent.cocktailMixed(cocktail.name()));  
            return cocktail;  
        }  
    }
六边形架构(Hexagonal Architecture)的关键好处

六边形架构强调以领域为中心,其中核心逻辑受到保护,不受外部因素干扰。这带来了很多好处:

  • 清晰的职责分离:领域模型与特定技术组件保持独立,这简化了领域驱动设计的过程。
  • 增强了灵活性:更换适配器,比如更换数据库或外部 API,都非常简单。
  • 提高了可测试性:端口和适配器通过将领域逻辑与输入输出操作隔离,简化了单元测试。
如何保持协议和领域逻辑的简洁和清晰

保持一致性是确保六边形架构完整性的关键。如果没有定期维护,即使是很结构化的代码库也会随时间逐渐退化,领域规则、接口边界和编码风格可能会变得模糊或漂移。为了防止这种情况的发生,建立严格的编码标准定期代码审查以确保符合架构原则。然而,保持领域与技术细节的隔离需要更多努力,这时ArchUnit就可以派上用场了。

ArchUnit 是一个用于验证架构规则的Java库,允许你定义约束以强化六边形架构(端口和适配器架构)的设计原则。它帮助制定围绕包结构和控制依赖关系的规则,确保:

  • 领域服务和适配器都应无法从其他层被访问,从而使业务逻辑与技术细节保持独立。
  • 端口是领域与适配器之间唯一的接口,确保所有外部通信都通过一个明确定义的边界。
  • 领域模型代表核心业务概念,充当各层之间的“纽带”

图6: 用于六边形架构(Hexagonal 架构)的 ArchUnit 规则

这些规则避免技术细节意外泄露到领域层,从而保持了六边形架构所需的清晰界限。

整合 ArchUnit 可以自动帮助我们强制执行代码中的架构边界,减少对人工检查的需求。将这些测试集成到 CI/CD 流水线中,这样,随着项目的增长,您可以保持架构原则的应用一致性。

结尾:

我们在代码中构建和解释现实世界概念的方法稍作改变,可以增强解决问题的清晰性,并加强与产品相关方的合作。

六边形架构 通过清晰地将业务逻辑与技术实现分离,提供了一种灵活、易于维护且可测试的应用设计方式。虽然它需要仔细的设计和严格的纪律,但从长远来看,尤其是对于复杂应用而言,最初的投入是值得的。

六边形架构ArchUnit依赖注入 模式(使用 Spring)结合,进一步强化了这种做法。ArchUnit 强制执行架构边界,确保一致性,而 Spring 的依赖注入则平滑地连接各个模块,支持模块化、可扩展的设计,让业务逻辑独立于技术细节。

本文中使用到的代码示例可在GitHub上找到。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号

举报

0/150
提交
取消