六边形架构模式:端口与适配器、核心领域与外部适配器
六边形架构,也叫端口与适配器模式。我第一次接触它是在一个遗留系统重构项目中。那个系统耦合太严重了,改一个数据库字段,整个业务层都要跟着改。后来我引入了六边形架构,才真正把核心业务从技术细节中解放出来。
说白了,六边形架构的核心思想就一句话:让业务逻辑成为系统的中心,技术实现只是外围的插件。你想想看,业务逻辑才是你系统的灵魂,数据库、消息队列、Web框架这些,都是可以替换的。
架构的核心三要素
六边形架构由三个核心部分组成。我习惯把它们想象成一个洋葱:
- 核心领域(Core Domain):最内层,纯业务逻辑,不依赖任何外部框架
- 端口(Ports):定义业务接口,是核心领域与外界沟通的契约
- 适配器(Adapters):实现端口,连接外部世界(数据库、Web、消息队列等)
这里有个关键点:依赖方向永远是从外向内。适配器依赖端口,端口依赖核心领域。核心领域对适配器一无所知。
核心原则:业务代码中不要出现任何import语句指向外部框架。如果你在业务类里看到了@Autowired、@Entity、@RequestMapping,那说明架构已经跑偏了。
SVG架构图:六边形架构的核心逻辑
端口:核心领域与外界沟通的契约
端口就是接口。我把它分为两类:
- 入站端口(Inbound Port):外部如何调用我的业务?比如用户下单、查询订单
- 出站端口(Outbound Port):我的业务需要外部提供什么?比如保存订单、发送通知
来看一个订单系统的例子。这是入站端口:
// 入站端口:定义业务能力
public interface OrderService {
Order createOrder(CreateOrderCommand command);
Order findOrderById(String orderId);
void cancelOrder(String orderId);
}
这是出站端口:
// 出站端口:定义持久化契约
public interface OrderRepository {
void save(Order order);
Optional<Order> findById(String orderId);
void deleteById(String orderId);
}
注意看,这两个接口里没有任何Spring注解,没有JPA依赖。它们就是纯粹的Java接口。这就是核心领域该有的样子。
核心领域:纯业务逻辑
核心领域里只有实体、值对象和领域服务。它们不关心数据怎么存,也不关心请求从哪里来。
// 核心领域实体
public class Order {
private String orderId;
private OrderStatus status;
private List<OrderItem> items;
private Money totalAmount;
public Order(String orderId, List<OrderItem> items) {
this.orderId = orderId;
this.items = items;
this.status = OrderStatus.CREATED;
this.totalAmount = calculateTotal();
}
public void cancel() {
if (this.status == OrderStatus.SHIPPED) {
throw new IllegalStateException("已发货订单不能取消");
}
this.status = OrderStatus.CANCELLED;
}
private Money calculateTotal() {
return items.stream()
.map(OrderItem::getSubtotal)
.reduce(Money.ZERO, Money::add);
}
}
这个Order类里,没有@Entity,没有@Table,没有@Column。它就是一个纯粹的领域对象。我在项目中见过太多把@Entity注解直接写在领域模型上的做法,结果业务逻辑和ORM绑定死了,想换数据库都难。
我的习惯:核心领域的包名就叫domain,里面只放纯Java类。所有框架注解都放在适配器层。这样即使将来从Spring Boot换成Quarkus,核心代码一行都不用改。
适配器:连接外部世界的桥梁
适配器分两种:入站适配器和出站适配器。
入站适配器接收外部请求,调用入站端口。比如REST控制器:
@RestController
@RequestMapping("/api/orders")
public class OrderController {
private final OrderService orderService; // 入站端口
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
@PostMapping
public ResponseEntity<OrderResponse> createOrder(@RequestBody CreateOrderRequest request) {
CreateOrderCommand command = new CreateOrderCommand(request.getItems());
Order order = orderService.createOrder(command);
return ResponseEntity.ok(OrderResponse.from(order));
}
}
出站适配器实现出站端口,与外部系统交互。比如JPA仓库:
@Repository
public class JpaOrderRepository implements OrderRepository {
private final SpringDataJpaOrderRepository jpaRepository;
public JpaOrderRepository(SpringDataJpaOrderRepository jpaRepository) {
this.jpaRepository = jpaRepository;
}
@Override
public void save(Order order) {
OrderEntity entity = OrderEntity.from(order);
jpaRepository.save(entity);
}
@Override
public Optional<Order> findById(String orderId) {
return jpaRepository.findById(orderId)
.map(OrderEntity::toDomain);
}
}
看到没有?OrderRepository接口在核心领域里定义,JpaOrderRepository在适配器层实现。核心领域根本不知道JPA的存在。
我曾经踩过的坑:在核心领域里直接使用@Transactional注解。后来想换成R2DBC响应式数据库,发现事务注解散落在各个业务方法里,改起来痛不欲生。正确的做法是:事务控制放在适配器层,核心领域只关心业务规则。
Spring Boot实践:完整项目结构
我建议的项目包结构是这样的:
com.example.ordersystem
├── domain # 核心领域
│ ├── model # 实体、值对象
│ │ ├── Order.java
│ │ ├── OrderItem.java
│ │ └── OrderStatus.java
│ ├── port # 端口定义
│ │ ├── inbound
│ │ │ └── OrderService.java
│ │ └── outbound
│ │ └── OrderRepository.java
│ └── service # 领域服务
│ └── OrderDomainService.java
├── adapter # 适配器层
│ ├── inbound # 入站适配器
│ │ ├── rest
│ │ │ ├── OrderController.java
│ │ │ └── dto
│ │ │ ├── CreateOrderRequest.java
│ │ │ └── OrderResponse.java
│ │ └── messaging
│ │ └── OrderMessageConsumer.java
│ └── outbound # 出站适配器
│ ├── persistence
│ │ ├── JpaOrderRepository.java
│ │ └── entity
│ │ └── OrderEntity.java
│ └── messaging
│ └── OrderMessageProducer.java
└── Application.java # Spring Boot启动类
这个结构的好处是:依赖关系一目了然。domain层不依赖adapter层,adapter层依赖domain层。你可以在IDE里看到,domain包下的import语句全是Java标准库,没有Spring、没有JPA。
六边形架构的优势
| 维度 | 传统分层架构 | 六边形架构 |
|---|---|---|
| 依赖方向 | 上层依赖下层,层层耦合 | 外部依赖内部,核心独立 |
| 测试难度 | 需要启动Spring容器 | 核心逻辑纯单元测试,无需框架 |
| 技术替换 | 改数据库要改业务层 | 只替换适配器,核心不变 |
| 代码复用 | 业务逻辑与框架绑定 | 核心逻辑可跨框架复用 |
我记得有一次,客户要求把MySQL换成MongoDB。在传统架构下,这几乎要重写整个持久层。但在六边形架构下,我只需要新增一个MongoOrderRepository实现OrderRepository接口,然后改一下配置类注入。核心领域代码一行没动。嗯,这就是架构设计带来的价值。
总结一下:六边形架构不是银弹,但它能帮你把业务逻辑和技术实现解耦。如果你正在做一个业务复杂度高、技术栈可能变化的系统,我强烈建议你试试。从一个小模块开始,慢慢你就会发现它的好处。