| name | ddd-architecture-hexagonal |
| description | Comprehensive guidance for Hexagonal Architecture (Ports & Adapters) — Alistair Cockburn's hexagonal architecture with domain core, port definitions, adapter implementations, and full Java/Spring Boot implementation steps. Covers primary/driving adapters, secondary/driven adapters, use case ports, repository ports, and dependency injection configuration. Use when user asks about hexagonal architecture, 六边形架构, ports and adapters, 端口适配器, Alistair Cockburn, or needs DDD with clean separation. |
| license | Apache-2.0 |
DDD Architecture — Hexagonal (Ports & Adapters)
Alistair Cockburn (2005) — 业务逻辑通过端口(Ports)隔离,外部系统通过适配器(Adapters)接入。适用受众: 后端架构师/技术负责人(团队 > 5 人)、DDD 实践者。定制机制: 提供技术栈/入口类型/团队规模三个配置维度。加载本技能时,建议先阅读 Workflow 和 When to Use 确认匹配度。
"允许应用被用户、程序、自动化测试或批处理脚本平等驱动,并在脱离最终运行设备和数据库的情况下开发和测试。" — Alistair Cockburn
Workflow
Step 1: 主适配器协议转换 — REST Controller/CLI 接收请求,完成参数校验与协议转换
Step 2: 入站端口调用 — 适配器调用 UseCase 接口,触发应用层用例编排
Step 3: 应用服务编排 — Application Service 编排用例,调用领域模型执行业务逻辑
Step 4: 领域模型执行 — 聚合根/实体执行业务逻辑,输出结果或领域事件
Step 5: 出站端口持久化 — 应用服务通过 Repository 接口保存领域状态
Step 6: 次适配器技术实现 — Adapter 执行 DB/MQ/外部 API 的技术实现
定义验证法: 脱离数据库和 HTTP 即可跑通全部领域层单元测试 → 边界正确。
Rules
- 依赖规则: 依赖方向必须从外到内——Adapter → Application → Domain。Domain 层零外部依赖。
- 端口粒度规则: 每个端口职责单一,包含 10+ 方法的端口需立即拆分。
- 异常转换规则: 技术异常(SQLException、TimeoutException)必须在 Adapter 内部捕获并转换为领域异常,不得向上泄漏。
When to Use
✅ 适合场景
- 多入口系统(REST + CLI + MQ + gRPC):多个外部系统同时驱动同一业务逻辑
- 基础设施频繁变更(换 DB/MQ/缓存):只需换 Adapter 实现,Domain 零修改
- 极致可测试性:Mock 端口即可测试全部领域逻辑,不依赖数据库
- 微服务架构标准化:团队 > 5 人,需要各服务一致的架构约定
- 团队熟悉抽象设计:能合理设计端口粒度,避免过度抽象
Boundary
⚠️ 需要条件
- 团队理解抽象设计:端口粒度需要领域知识和抽象能力,否则可能定义不当
- 项目规模适中:小项目用 Layered 更简单,六边形增加间接层成本
- 需要 DDD 领域模型配合:Port 接口定义需要领域建模前置知识
- 良好的 DI 容器支持:Spring/Guice 等框架可简化适配器装配
❌ 不适用(附替代方案)
- 单一 REST API + 简单 CRUD → 用
ddd-architecture-layered
- 中文企业 MyBatis 生态 → 用
ddd-architecture-cola
- 需 UseCase + Entity 严格分离 → 用
ddd-architecture-clean
- 快速原型 / MVP 阶段 → 直接用传统三层(不引入六边形架构的开销)
核心原理
三大抽象
Driving Side (REST/CLI/gRPC/Test) → Domain Core ← Driven Side (PostgreSQL/RabbitMQ/Redis/Stripe)
| 概念 | 说明 | 代码体现 |
|---|
| Port(端口) | 领域层定义的接口 | interface OrderRepository |
| Primary Adapter(主适配器) | 外部如何驱动系统 | REST Controller、CLI Command、gRPC Service |
| Secondary Adapter(次适配器) | 系统如何驱动外部 | JPA RepositoryImpl、Kafka Producer、StripeClient |
Strong Port vs Weak Port
interface OrderRepository { List<Order> findByQuery(String sql, Map<String, Object> params); }
interface OrderRepository {
Optional<Order> findById(OrderId id);
List<Order> findByCustomerId(CustomerId customerId);
void save(Order order);
}
目录结构
{project}/
├── {project}-domain/ # 领域核心 + 端口定义(零框架依赖)
│ └── port/{inbound, outbound}/ # ★ 端口接口
├── {project}-application/ # 应用层(UseCase 实现)
├── {project}-adapter/ # 适配器层
│ ├── inbound/ # ★ 主适配器(REST/CLI/gRPC/MQ)
│ └── outbound/ # ★ 次适配器(Persistence/Messaging/External)
└── {project}-configuration/ # 配置层(DI 装配)
开发规范
层职责
| 层 | 依赖 | 允许做的事 | 禁止做的事 |
|---|
| Domain | 无 | 实体行为、值对象、领域事件、端口定义 | import 框架注解、SQL、HTTP |
| Application | Domain | 用例编排、事务管理、端口调用 | if/else 业务判断、直接操作 DB |
| Adapter | Application | 协议转换、参数校验、异常映射 | 包含业务逻辑、直接操作 Domain 内部状态 |
| Configuration | 全部 | DI 装配、Profile 配置 | 包含业务代码 |
代码规范
public interface CreateOrderUseCase { OrderCreatedResult execute(CreateOrderCommand command); }
public interface OrderRepository { void save(Order order); Optional<Order> findById(OrderId id); }
@ApplicationService
public class CreateOrderService implements CreateOrderUseCase {
private final OrderRepository orderRepository;
@Override @Transactional
public OrderCreatedResult execute(CreateOrderCommand command) {
Order order = Order.create(command.getCustomerId());
orderRepository.save(order);
return OrderCreatedResult.from(order);
}
}
@RestController
public class OrderController {
@PostMapping("/orders")
public ResponseEntity<CreateOrderResponse> create(@RequestBody @Valid CreateOrderRequest req) {
var result = createOrderUseCase.execute(req.toCommand());
return ResponseEntity.status(201).body(CreateOrderResponse.from(result));
}
}
@Repository
public class PostgresOrderRepository implements OrderRepository {
@Override public void save(Order order) { jpaRepo.save(mapper.toPO(order)); }
}
落地步骤
Phase 1: 定义端口(1-2 天)→ 入站端口(UseCase) → 出站端口(Repository/Gateway)
Phase 2: 领域模型(2-3 天)→ 聚合根/实体/值对象 → 领域服务 → 领域事件
Phase 3: 应用服务(1-2 天)→ UseCase 实现(注入端口,编排调用)
Phase 4: 适配器(2-4 天) → 主适配器(REST/gRPC/CLI) → 次适配器(DB/MQ/External)
Phase 5: DI 装配 + 测试(1-2 天)→ DI 配置 → 端口 Mock 测试 → 适配器集成测试
Security & Stability
- 本技能为纯文档型架构指南,不收集、不处理、不上传用户数据。
- 所有代码模板均为教学参考。替换外部服务 URL、密钥和凭证为环境变量配置。
- Port/Adapter 隔离确保领域逻辑不直接依赖 HTTP/DB/MQ 库——Adapter 层处理所有 I/O。
- 实现 Secondary Adapter 时,始终设置连接/读取超时,对关键路径实现断路器模式。
Gotchas(8 条常见陷阱)
- 端口定义过宽: 每个端口只做一件事。包含 10+ 方法的端口视为"上帝端口",立即拆分。
- 主适配器包含业务逻辑: Controller 只能做参数转换和调用 UseCase,不能包含 if/else 业务判断。
- 次适配器忘记异常转换: 技术异常(SQLException、TimeoutException)必须在适配器内部转换为领域异常。
- 领域层框架依赖: Domain 模块不能 import Spring/JPA/MyBatis 注解。
- 端口命名不一致: 入站端口用动作命名(
CreateOrderUseCase),出站端口用资源命名(OrderRepository)。
- 事务放在适配器层: 事务由应用层控制,适配器不负责事务管理。
- 过度抽象: 不为"未来可能会换"提前创建端口。等真正需要替换时再抽取端口。
- 用户需求不清晰时先给假设: 用户没说技术栈/入口类型/团队规模时,先给假设版本再补充提问。
FAQ(8 条常见问题)
Q1: 六边形和整洁架构有什么区别? A: 六边形由 Cockburn 提出(2005),核心是端口/适配器;整洁由 Uncle Bob 提出(2012),强调 UseCase 和 Entity 分离。六边形更关注"对称性"(主/次适配器),整洁更关注"依赖规则"。
Q2: 端口应该放在 Domain 层还是 Application 层? A: 推荐放在 Domain 层。Domain 是业务核心,端口是业务对外部依赖的抽象。
Q3: 如何避免端口过度抽象? A: YAGNI 原则:只为当前确定的变更需求定义端口。
Q4: 一个 UseCase 接口一个方法还是多个方法? A: 推荐一个接口一个方法(接口隔离原则)。但高度相关的方法(如 OrderRepository 的 save/findById)可放同一接口。
Q5: Controller 层的 DTO 和 Domain 层的 Entity 能否共用? A: 不能。DTO 是适配器层的数据载体,Entity 是领域层的业务模型,需要 Mapper 转换。
Q6: 六边形架构如何处理查询? A: 查询也通过端口进行。使用 QueryOrderUseCase 返回只读 DTO,复杂查询可用 CQRS。
Q7: 如何验证六边形边界是否正确? A: 能否不启动数据库和 HTTP 就跑通 Domain 层全部单元测试?能 → 正确。不能 → 有泄漏。
Q8: 团队不熟悉六边形架构怎么办? A: 逐步引入。先做好 Domain 层纯净度和端口定义,再逐步抽取适配器。
References
Primary Sources
导航
💡 六边形 = 端口 + 适配器。验证法:不启动数据库和 HTTP,只跑 CLI/单元测试就能执行业务逻辑 → 边界正确。