关于 common-hexagonal-architecture
为服务和模块设计、重构提供六边形架构指导,通过端口与适配器隔离领域逻辑和基础设施。适用于需要可测试核心、明确依赖边界及外部集成的开发者。
主要功能
- 01划分驱动端口与被驱动端口
- 02将业务逻辑保留在应用核心
- 03通过适配器连接基础设施
- 04使用依赖注入组装组件
- 05按核心与适配器设计测试
- 06检查并维护架构边界
适用场景
- 01设计新服务或重构模块架构
- 02在无基础设施依赖时测试业务逻辑
- 03接入数据库、API 或消息队列
- 04检查并清理分层代码的依赖边界
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ xip-online-applications/ skills --skill common-hexagonal-architecture安装后在 Claude Code 中输入/common-hexagonal-architecture确认已加载。
详细介绍
通用六边形架构
在单个服务边界内应用六边形架构(也称为端口与适配器),使领域逻辑独立于框架、数据库、消息系统和 UI。
适用场景
在以下情况下使用此技能:
- 设计或重构服务或模块的内部架构
- 确保领域/业务逻辑无需依赖基础设施即可测试
- 引入新的外部集成(数据库、API、队列、文件系统)
- 审查代码,确认基础设施相关问题不会渗入核心
- 将分层代码库或「大泥球」代码库重构为边界更清晰的架构
核心概念
六边形(应用核心)
六边形包含所有业务逻辑和领域规则。它对外部世界一无所知——不涉及 HTTP、SQL 或文件 I/O。它仅通过端口进行通信。
端口
端口是一种接口(契约),定义应用核心如何与外部世界交互。
| 端口类型 | 方向 | 用途 |
|---|---|---|
| 驱动端口(主要端口) | 入站 | 定义外部世界可在核心上调用的用例。 |
| 被驱动端口(次要端口) | 出站 | 定义核心需要外部系统提供的内容。 |
- 驱动端口由应用核心(用例/应用服务)实现。
- 被驱动端口由基础设施层中的适配器实现。
适配器
适配器负责在外部世界与端口之间转换。
| 适配器类型 | 连接到 | 示例 |
|---|---|---|
| 驱动适配器(主要适配器) | 驱动端口 | REST 控制器、命令行处理程序、gRPC 服务端、消息使用者 |
| 被驱动适配器(次要适配器) | 被驱动端口 | SQL 仓储、HTTP 客户端、SMTP 邮件发送器、文件存储 |
依赖规则
依赖关系始终向内指向——适配器依赖端口,端口位于核心中,而核心不依赖任何外部内容。
[Driving Adapter] --> [Driving Port (interface)]
|
[Application Core]
|
[Driven Port (interface)] <-- [Driven Adapter]
说明
-
将驱动端口定义为用例接口。
- 每个用例或紧密相关的一组用例对应一个接口。
- 使用领域动作命名:
PlaceOrder、RegisterUser、GenerateReport。 - 只接受和返回领域对象或简单的 DTO,不要使用框架特定类型。
-
在应用服务中实现驱动端口。
- 应用服务位于六边形内部。
- 它们编排领域对象并调用被驱动端口。
- 它们不包含基础设施逻辑(不执行 SQL、不发起 HTTP 调用、不进行文件 I/O)。
-
将被驱动端口定义为仓储/网关接口。
- 每种外部依赖概念对应一个接口:
OrderRepository、PaymentGateway、NotificationSender。 - 方法签名应聚焦于领域需求,而非技术实现(例如,使用
findByCustomerId(id),而不是executeQuery(sql))。 - 被驱动端口接口与领域一起位于应用核心中。
- 每种外部依赖概念对应一个接口:
-
在基础设施中实现被驱动适配器。
- 每个被驱动适配器只实现一个被驱动端口接口。
- 包含所有特定于技术的代码(ORM 调用、HTTP 客户端、SDK 用法)。
- 可以替换而不触及核心(例如,从 PostgreSQL 切换到 DynamoDB)。
-
在边界处实现驱动适配器。
- 驱动适配器接收外部输入(HTTP 请求、CLI 参数、队列消息)。
- 它将输入转换为对驱动端口的调用。
- 它将端口的响应转换回外部响应(JSON、退出代码、确认消息)。
- 驱动适配器不得包含业务逻辑,只能进行映射和委派。
-
通过注入连接依赖项。
- 使用构造函数注入(或组合根/DI 容器)将被驱动适配器实例传入应用服务。
- 组合根是唯一了解具体适配器实现的地方。
- 在测试中,通过相同的端口注入伪造/模拟/内存适配器。
-
文件夹/模块结构。 组织服务,使其结构清晰地体现六边形:
src/ <service-or-module>/ core/ domain/ model/ # Entities, value objects, aggregates event/ # Domain events service/ # Domain services (pure logic) port/ in/ # Driving port interfaces (use cases) out/ # Driven port interfaces (repositories, gateways) application/ service/ # Application services implementing driving ports dto/ # Input/output DTOs for ports adapter/ in/ web/ # REST/GraphQL controllers cli/ # CLI command handlers messaging/ # Message/event consumers out/ persistence/ # Database repositories http/ # External API clients messaging/ # Event publishers, queue producers filesystem/ # File storage adapters config/ # Composition root, DI wiring, framework bootstrap -
采用与六边形架构相匹配的测试策略。
- 单元测试:使用内存/伪造的被驱动适配器测试领域模型和应用服务。不进行真实 I/O。
- 集成测试:使用真实基础设施(数据库、通过 testcontainers 或沙盒接入的外部 API)测试被驱动适配器。
- 验收测试:端到端调用驱动端口(或驱动适配器),使用真实或接近真实的适配器验证完整用例。
- 大多数测试都应针对核心进行单元测试——运行快速、结果确定,且不模拟内部实现。
-
守住边界。
- 核心绝不能导入
adapter/包。 - 适配器只能导入
core/port/(以及用于类型的core/domain/model/)。 - 通过模块边界强制执行这些规则(lint 规则、包可见性、ArchUnit/Deptrac/dependency-cruiser 等架构测试)。
- 核心绝不能导入
-
验证架构。
- 能否通过为所有被驱动端口注入伪造实现来测试任意用例?如果不能,就说明有依赖泄漏。
- 能否在不更改核心的情况下替换被驱动适配器(例如,将 Postgres 替换为内存存储)?如果不能,说明端口耦合过紧。
- 驱动适配器是否包含条件业务逻辑?如果是,就将其移入核心,并通过驱动端口调用。
- 组合根是否是唯一引用具体适配器类的文件?如果不是,说明连接逻辑已泄漏。
评论
支持 Markdown