
本文系统梳理六边形架构在 spring boot 3.x(jdk 17+)项目中的推荐包组织方式,明确核心层(core)、适配器层(adapters)的职责边界与典型子包结构,并通过领域模型与持久化实体分离、端口定义位置等关键实践,确保业务逻辑真正独立于框架、数据库与协议。
本文系统梳理六边形架构在 spring boot 3.x(jdk 17+)项目中的推荐包组织方式,明确核心层(core)、适配器层(adapters)的职责边界与典型子包结构,并通过领域模型与持久化实体分离、端口定义位置等关键实践,确保业务逻辑真正独立于框架、数据库与协议。
六边形架构(Hexagonal Architecture),又称端口与适配器(Ports & Adapters),其本质不是画一个六边形图,而是建立一种以业务内核为中心、技术细节为外设的稳定分层契约。在 Spring Boot 3.x(要求 JDK 17+)工程中,若仅按传统 controller-service-repository 扁平分包,极易导致业务逻辑被 Web 协议、JPA 注解、消息中间件等技术细节污染,进而引发测试困难、替换成本高、演进僵化等问题。因此,一套清晰、可扩展、符合 DDD 精神的包结构,是落地该架构的第一步。
✅ 推荐标准包结构(兼顾清晰性与工程可维护性)
我们建议采用 core + adapters 的顶层命名空间,并严格遵循“依赖规则”:外部层(adapters)可依赖 core,但 core 绝不可反向依赖任何外部技术实现。以下是经过生产验证的推荐结构:
com.example.myapp ├── core # 业务内核:完全无 Spring、JPA、HTTP 等任何框架依赖 │ ├── domain # 领域层:纯 Java 业务模型与规则 │ │ ├── entities # 领域实体(如 Order、Customer),含业务方法与不变式校验 │ │ ├── valueobjects # 值对象(如 Money、Address),不可变、基于属性相等 │ │ ├── events # 领域事件(如 OrderPlacedEvent),用于解耦领域内响应 │ │ └── exceptions # 领域异常(如 InsufficientStockException) │ └── application # 应用层:编排领域对象,实现用例(Use Case) │ ├── services # 应用服务(如 OrderApplicationService),协调领域对象与端口 │ └── ports # 【关键】端口接口定义(InPort / OutPort) │ ├── inbound # 入口端口(如 PlaceOrderUseCase) │ └── outbound # 出口端口(如 ProductRepository, NotificationService) ├── adapters # 适配器层:所有技术实现均在此,对 core 透明 │ ├── inbound # 外部请求入口适配器 │ │ ├── rest # REST API(@RestController → 调用 core.application.ports.inbound) │ │ ├── graphql # GraphQL 端点(可选) │ │ └── messaging # 消息监听(如 @KafkaListener → 触发 UseCase) │ └── outbound # 外部能力出口适配器 │ ├── db # 数据库相关实现 │ │ ├── entities # JPA 实体(@Entity),仅用于 ORM 映射 │ │ ├── repositories # Spring Data JPA Repository 接口实现 │ │ └── mappers # 领域对象 ↔ 持久化对象转换器(如 OrderMapper) │ ├── bus # 消息总线(如 Kafka/RabbitMQ 生产者) │ │ ├── dtos # 外发 DTO(非领域对象!) │ │ └── mappers # 领域事件 → 消息 DTO 转换 │ ├── email # 邮件发送适配器(实现 core.application.ports.outbound.NotificationService) │ └── externalapi # 第三方 API 调用适配器(如支付网关 SDK 封装) └── config # Spring 配置类(仅在此处声明 @Bean、@EnableJpaRepositories 等)
? 关键设计原则与示例说明
1. core.domain.entities ≠ adapters.outbound.db.entities
这是解耦成败的核心。前者是纯粹的业务对象,后者是技术映射载体:
// core/domain/entities/Order.java —— 无任何注解,仅业务语义
public class Order {
private final String id;
private final Money totalPrice; // Value Object
private final List<orderitem> items;
public Order(String id, Money totalPrice, List<orderitem> items) {
if (items.isEmpty()) throw new DomainException("Order must have items");
this.id = id;
this.totalPrice = totalPrice;
this.items = List.copyOf(items);
}
public void confirm() { /* 业务规则:状态变更前校验库存 */ }
}</orderitem></orderitem>
// adapters/outbound/db/entities/OrderEntity.java —— 仅此处允许 @Entity
@Entity
@Table(name = "orders")
public class OrderEntity {
@Id private String id;
private BigDecimal totalAmount; // 仅存储字段,无业务逻辑
@ElementCollection private List<string> itemIds;
// 构造器 & JPA 必需的空参构造器
}</string>
✅ 映射由 adapters.outbound.db.mappers.OrderMapper 完成,且 core 中绝不可出现 OrderEntity 类型引用。
2. 端口(Ports)必须定义在 core.application.ports 下
- InPort 是应用层对外暴露的能力契约(如 PlaceOrderUseCase 接口),由 adapters.inbound.rest 实现调用;
- OutPort 是应用层对外部能力的抽象需求(如 ProductRepository 接口),由 adapters.outbound.db.repositories 提供实现。
// core/application/ports/outbound/ProductRepository.java
public interface ProductRepository {
Optional<product> findById(String productId); // 返回领域对象 Product
void save(Product product); // 接收领域对象
}</product>
// adapters/outbound/db/repositories/JpaProductRepository.java
@Repository
public class JpaProductRepository implements ProductRepository {
private final SpringDataProductRepository jpaRepository; // Spring Data JPA 接口
@Override
public Optional<product> findById(String productId) {
return jpaRepository.findById(productId)
.map(ProductEntity::toDomain); // Entity → Domain 转换
}
@Override
public void save(Product product) {
jpaRepository.save(product.toEntity()); // Domain → Entity 转换
}
}</product>
3. 避免常见陷阱
- ❌ 不要将 @RestController 或 @Service 注解放在 core 包下(违反无框架依赖原则);
- ❌ 不要在 core 中引入 org.springframework.*、javax.persistence.*、com.fasterxml.jackson.* 等任何技术包;
- ✅ config 包是唯一允许声明 @Bean 和配置类的地方,用于将 adapters 中的实现注入 core.application 所需的端口;
- ✅ 使用 @Import 或 @ComponentScan(basePackages = "com.example.myapp.adapters") 确保 Spring 扫描到适配器。
? 总结:为什么这套结构值得坚持?
| 维度 | 传统分层架构 | 六边形包结构(本指南) |
|---|---|---|
| 可测试性 | @SpringBootTest 启动全容器才可测 | core 层可纯 JVM 单元测试(零 Spring、零 DB) |
| 可替换性 | 换数据库需改 Service + Mapper | 仅替换 adapters.outbound.db.*,core 不动 |
| 可演进性 | 新增 GraphQL 接口需侵入 Controller | 新增 adapters.inbound.graphql 即可,复用同一 UseCase |
| 心智负担 | 开发者需同时理解业务 + JPA + HTTP | 各层关注点单一,新人可快速聚焦 domain 或 adapter |
六边形架构不是银弹,但它是一面镜子——照出你的业务逻辑是否足够“硬核”。当 Order.confirm() 方法不依赖 @Transactional、不拼写 SQL、不处理 HTTP 状态码时,你才真正拥有了可沉淀、可复用、可长期演进的业务资产。从今天开始,用 core 守住业务底线,让 adapters 自由拥抱变化。











