Skip to content

六边形架构(Hexagonal Architecture)

概述

六边形架构,也称为端口和适配器架构(Ports and Adapters Architecture),是由Alistair Cockburn在2005年提出的一种架构模式。该架构的核心思想是将应用程序的业务逻辑与外部关注点(如用户界面、数据库、外部服务等)完全分离,通过端口和适配器的方式进行交互。

六边形的形状并非固定,它象征着应用程序可以通过多个端口与外部世界进行交互,每个端口代表一种不同的交互方式。

架构图

基本结构图

                    ┌─────────────────┐
                    │   Web UI        │
                    │   适配器         │
                    └─────────┬───────┘

              ┌───────────────┴───────────────┐
              │           端口              │
              │      (HTTP API)            │
┌─────────────┴─────────────────────────────┴─────────────┐
│                                                         │
│  ┌─────────────────────────────────────────────────┐    │
│  │                                                 │    │
│  │              应用程序核心                        │    │
│  │            (业务逻辑)                           │    │
│  │                                                 │    │
│  └─────────────────────────────────────────────────┘    │
│                                                         │
└─────────────┬─────────────────────────────┬─────────────┘
              │           端口              │
              │      (数据库接口)            │
              └───────────────┬───────────────┘

                    ┌─────────┴───────┐
                    │   数据库        │
                    │   适配器         │
                    └─────────────────┘

完整六边形架构图

                     ┌─────────────┐
                     │   Web UI    │
                     │   适配器     │
                     └──────┬──────┘

                   ┌────────┴────────┐
                   │   HTTP 端口     │
          ┌────────┴────────┬────────┴────────┐
          │                 │                 │
    ┌─────┴─────┐          │                 │          ┌─────────────┐
    │  CLI      │          │                 │          │  消息队列    │
    │  适配器    │          │                 │          │  适配器      │
    └─────┬─────┘          │                 │          └──────┬──────┘
          │                │                 │                 │
    ┌─────┴─────┐          │   应用程序核心   │          ┌──────┴──────┐
    │  CLI      │          │   (业务逻辑)    │          │  消息端口    │
    │  端口      │          │                 │          └─────────────┘
    └───────────┘          │                 │
                           │                 │
          ┌─────────────┐  │                 │  ┌─────────────┐
          │  数据库      │  │                 │  │  外部API     │
          │  适配器      │  │                 │  │  适配器      │
          └──────┬──────┘  │                 │  └──────┬──────┘
                 │         │                 │         │
          ┌──────┴──────┐  └─────────────────┘  ┌──────┴──────┐
          │  数据库端口  │                      │  API端口     │
          └─────────────┘                      └─────────────┘

核心概念

1. 应用程序核心(Application Core)

职责:

  • 包含所有业务逻辑和业务规则
  • 定义领域模型和业务实体
  • 实现用例和业务流程
  • 完全独立于外部技术

特点:

  • 不依赖任何外部框架或技术
  • 可以独立测试
  • 包含系统的核心价值
  • 稳定且变化较少

2. 端口(Ports)

定义: 端口是应用程序核心与外部世界交互的接口定义,它们是抽象的契约,定义了应用程序需要什么以及提供什么。

类型:

主端口(Primary Ports)- 驱动端口

  • 由外部世界调用应用程序
  • 例如:HTTP API、命令行接口、图形用户界面
  • 定义应用程序提供的服务

次端口(Secondary Ports)- 被驱动端口

  • 应用程序调用外部世界
  • 例如:数据库接口、外部服务接口、消息队列接口
  • 定义应用程序需要的服务

3. 适配器(Adapters)

定义: 适配器是端口的具体实现,它们将外部技术转换为应用程序核心能够理解的格式,反之亦然。

类型:

主适配器(Primary Adapters)- 驱动适配器

  • 实现主端口
  • 将外部请求转换为应用程序调用
  • 例如:REST控制器、CLI命令处理器、GUI事件处理器

次适配器(Secondary Adapters)- 被驱动适配器

  • 实现次端口
  • 将应用程序调用转换为外部技术调用
  • 例如:数据库访问层、HTTP客户端、消息发布器

设计原则

1. 依赖倒置原则

外部世界 → 适配器 → 端口 ← 应用程序核心
  • 应用程序核心不依赖外部技术
  • 外部技术通过适配器依赖应用程序核心
  • 所有依赖都指向内部

2. 关注点分离

  • 业务逻辑与技术实现分离
  • 不同的外部关注点通过不同的端口处理
  • 每个适配器专注于特定的技术

3. 可测试性

  • 应用程序核心可以独立测试
  • 可以使用测试替身(Test Doubles)替换适配器
  • 端到端测试通过真实适配器进行

架构层次

1. 外层 - 适配器层

┌─────────────────────────────────────────────────────────┐
│                    适配器层                              │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐     │
│  │  Web适配器  │  │  CLI适配器   │  │  DB适配器    │     │
│  └─────────────┘  └─────────────┘  └─────────────┘     │
└─────────────────────────────────────────────────────────┘

职责:

  • 处理特定技术的细节
  • 数据格式转换
  • 协议适配
  • 错误处理和重试

2. 中层 - 端口层

┌─────────────────────────────────────────────────────────┐
│                     端口层                               │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐     │
│  │  HTTP端口   │  │  CLI端口     │  │  数据端口    │     │
│  └─────────────┘  └─────────────┘  └─────────────┘     │
└─────────────────────────────────────────────────────────┘

职责:

  • 定义交互契约
  • 抽象外部依赖
  • 提供稳定的接口
  • 隔离变化

3. 内层 - 应用程序核心

┌─────────────────────────────────────────────────────────┐
│                 应用程序核心                              │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐     │
│  │  领域模型   │  │  用例服务    │  │  业务规则    │     │
│  └─────────────┘  └─────────────┘  └─────────────┘     │
└─────────────────────────────────────────────────────────┘

职责:

  • 实现业务逻辑
  • 定义领域模型
  • 执行用例
  • 应用业务规则

优势

1. 高度可测试性

单元测试:

  • 应用程序核心可以完全独立测试
  • 不需要启动数据库或外部服务
  • 测试速度快,反馈及时

集成测试:

  • 可以使用测试适配器
  • 模拟各种外部场景
  • 测试端口契约

2. 技术无关性

框架独立:

  • 业务逻辑不绑定特定框架
  • 可以轻松更换技术栈
  • 减少技术债务

数据库独立:

  • 可以更换数据库类型
  • 支持多种存储方案
  • 便于数据迁移

3. 易于维护和扩展

新增功能:

  • 添加新的适配器支持新技术
  • 扩展端口支持新的交互方式
  • 核心逻辑保持稳定

修改现有功能:

  • 变更影响范围明确
  • 接口稳定性好
  • 向后兼容性强

4. 清晰的架构边界

职责明确:

  • 每层职责清晰
  • 依赖关系明确
  • 便于团队协作

代码组织:

  • 结构清晰
  • 易于导航
  • 便于新人理解

劣势

1. 初始复杂性

学习成本:

  • 概念相对复杂
  • 需要理解依赖倒置
  • 设计思维转变

开发成本:

  • 初期开发工作量大
  • 需要定义更多接口
  • 代码量增加

2. 过度工程风险

简单项目:

  • 可能过度设计
  • 增加不必要的复杂性
  • 开发效率降低

小团队:

  • 维护成本高
  • 需要更多架构知识
  • 可能影响交付速度

3. 性能考虑

抽象层开销:

  • 多层调用开销
  • 对象创建成本
  • 可能的性能损失

内存使用:

  • 更多的对象和接口
  • 内存占用增加
  • 垃圾回收压力

适用场景

1. 复杂业务系统

特征:

  • 业务逻辑复杂
  • 需要长期维护
  • 多种外部集成
  • 高质量要求

示例:

  • 金融交易系统
  • 企业资源规划(ERP)
  • 客户关系管理(CRM)

2. 多渠道应用

特征:

  • 支持多种用户界面
  • 多种数据源
  • 不同的集成需求
  • 统一的业务逻辑

示例:

  • 电商平台(Web、移动、API)
  • 银行系统(柜台、ATM、网银、手机银行)
  • 内容管理系统

3. 高测试要求的项目

特征:

  • 质量要求高
  • 需要自动化测试
  • 持续集成/部署
  • 快速反馈

示例:

  • 医疗系统
  • 航空管制系统
  • 金融风控系统

实施策略

1. 识别端口

主端口识别:

  • 分析用户交互方式
  • 确定系统提供的服务
  • 定义用例接口

次端口识别:

  • 分析外部依赖
  • 确定系统需要的服务
  • 定义依赖接口

2. 设计适配器

技术选择:

  • 根据需求选择合适技术
  • 考虑性能和可维护性
  • 评估学习和维护成本

实现策略:

  • 先实现简单适配器
  • 逐步完善功能
  • 保持接口稳定

3. 组织代码结构

包结构示例:

com.example.app/
├── domain/          # 应用程序核心
│   ├── model/       # 领域模型
│   ├── service/     # 业务服务
│   └── port/        # 端口定义
├── adapter/         # 适配器实现
│   ├── web/         # Web适配器
│   ├── persistence/ # 持久化适配器
│   └── messaging/   # 消息适配器
└── config/          # 配置和装配

4. 测试策略

测试金字塔:

        ┌─────────────┐
        │  E2E测试    │  ← 少量,覆盖关键路径
        └─────────────┘
      ┌─────────────────┐
      │   集成测试      │  ← 适中,测试适配器
      └─────────────────┘
    ┌─────────────────────┐
    │     单元测试        │  ← 大量,测试核心逻辑
    └─────────────────────┘

与其他架构的关系

1. 与分层架构的区别

分层架构:

  • 水平分层
  • 上层依赖下层
  • 技术驱动

六边形架构:

  • 内外分离
  • 外层依赖内层
  • 业务驱动

2. 与微服务的结合

单个微服务:

  • 每个微服务采用六边形架构
  • 清晰的服务边界
  • 独立的技术选择

服务间通信:

  • 通过适配器处理服务间调用
  • 统一的通信协议
  • 服务发现和负载均衡

3. 与DDD的结合

领域建模:

  • 应用程序核心采用DDD
  • 清晰的限界上下文
  • 丰富的领域模型

架构对齐:

  • 端口对应领域服务
  • 适配器处理技术细节
  • 保持领域纯净

最佳实践

1. 端口设计

接口设计:

  • 使用领域语言
  • 避免技术泄露
  • 保持接口稳定
  • 考虑向后兼容

粒度控制:

  • 避免过细粒度
  • 减少网络调用
  • 保持原子性
  • 考虑事务边界

2. 适配器实现

错误处理:

  • 统一异常处理
  • 适当的重试机制
  • 降级策略
  • 监控和告警

性能优化:

  • 连接池管理
  • 缓存策略
  • 批量操作
  • 异步处理

3. 测试实践

测试替身:

  • 使用Mock对象
  • 实现测试适配器
  • 数据驱动测试
  • 契约测试

测试环境:

  • 隔离的测试环境
  • 可重复的测试数据
  • 快速的反馈循环
  • 自动化测试流水线

总结

六边形架构通过端口和适配器的设计,实现了业务逻辑与技术实现的完全分离,提供了高度的可测试性和技术无关性。虽然初期实施成本较高,但对于复杂的业务系统和长期维护的项目来说,这种架构模式能够显著提高系统的质量和可维护性。

选择六边形架构时,需要权衡其带来的好处和增加的复杂性,确保团队有足够的能力来实施和维护这种架构。同时,可以考虑与其他架构模式(如DDD、微服务)结合使用,以获得更好的效果。