六边形架构
六边形架构(Hexagonal Architecture),也称为端口与适配器架构(Ports and Adapters Architecture),是一种旨在创建松耦合、可测试的应用程序的架构模式。它将应用程序的核心逻辑与外部世界隔离,使业务逻辑独立于框架、UI 和数据库。
什么是六边形架构?
六边形架构由 Alistair Cockburn 提出,其核心思想是将应用程序分为内部(领域)和外部(基础设施)两部分,通过端口(Ports)和适配器(Adapters)进行通信。
外部世界
┌─────────────┐
│ Web UI │
└──────┬──────┘
│
┌─────────────┐ ┌────┴────┐ ┌─────────────┐
│ 外部系统 │ │ │ │ 数据库 │
└──────┬──────┘ │ 适配器 │ └──────┬──────┘
│ │ │ │
│ └────┬────┘ │
│ │ │
│ ┌────┴────┐ │
└─────────>│ 端口 │<─────────┘
│ │
└────┬────┘
│
┌────────────┼────────────┐
│ │ │
│ ┌──────┴──────┐ │
│ │ │ │
│ │ 领域逻辑 │ │
│ │ (应用核心) │ │
│ │ │ │
│ └─────────────┘ │
│ │
└────── 六边形内部 ────────┘
核心概念
1. 领域(Domain)- 六边形内部
领域包含应用程序的核心业务逻辑,不依赖于任何外部框架或技术。
// 领域实体 - 纯业务逻辑,无外部依赖
public class Order {
private OrderId id;
private CustomerId customerId;
private List<OrderItem> items;
private OrderStatus status;
private Money totalAmount;
private Order(CustomerId customerId, List<OrderItem> items) {
this.id = OrderId.generate();
this.customerId = customerId;
this.items = new ArrayList<>(items);
this.status = OrderStatus.PENDING;
this.totalAmount = calculateTotal();
}
public static Order create(CustomerId customerId, List<OrderItem> items) {
validateItems(items);
return new Order(customerId, items);
}
public void confirm() {
if (status != OrderStatus.PENDING) {
throw new IllegalStateException("Only pending orders can be confirmed");
}
this.status = OrderStatus.CONFIRMED;
}
public void ship(TrackingNumber trackingNumber) {
if (status != OrderStatus.CONFIRMED) {
throw new IllegalStateException("Only confirmed orders can be shipped");
}
this.status = OrderStatus.SHIPPED;
// 领域事件
registerEvent(new OrderShippedEvent(id, trackingNumber, LocalDateTime.now()));
}
private Money calculateTotal() {
return items.stream()
.map(OrderItem::getSubtotal)
.reduce(Money.ZERO, Money::add);
}
// Getters...
}
// 值对象
public class Money {
private final BigDecimal amount;
private final Currency currency;
public static final Money ZERO = new Money(BigDecimal.ZERO, Currency.getInstance("CNY"));
public Money(BigDecimal amount, Currency currency) {
this.amount = amount;
this.currency = currency;
}
public Money add(Money other) {
if (!this.currency.equals(other.currency)) {
throw new IllegalArgumentException("Cannot add different currencies");
}
return new Money(this.amount.add(other.amount), this.currency);
}
// Getters...
}
2. 端口(Ports)
端口定义了应用程序与外部世界交互的接口,分为入站端口(驱动端口)和出站端口(被驱动端口)。
// 入站端口 - 应用程序提供的服务接口
public interface OrderService {
Order createOrder(CreateOrderCommand command);
void confirmOrder(OrderId orderId);
void shipOrder(OrderId orderId, TrackingNumber trackingNumber);
Optional<Order> findOrder(OrderId orderId);
}
// 出站端口 - 应用程序依赖的外部服务接口
public interface OrderRepository {
Order save(Order order);
Optional<Order> findById(OrderId id);
List<Order> findByCustomerId(CustomerId customerId);
}
public interface PaymentService {
PaymentResult processPayment(OrderId orderId, Money amount);
}
public interface NotificationService {
void sendOrderConfirmation(CustomerId customerId, OrderId orderId);
void sendShippingNotification(CustomerId customerId, OrderId orderId, TrackingNumber trackingNumber);
}
3. 适配器(Adapters)
适配器实现端口接口,将外部技术与应用程序核心连接。
// 入站适配器 - REST API 控制器
@RestController
@RequestMapping("/api/orders")
public class OrderController {
private final OrderService orderService; // 入站端口
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
@PostMapping
public ResponseEntity<OrderResponse> createOrder(@RequestBody @Valid CreateOrderRequest request) {
CreateOrderCommand command = toCommand(request);
Order order = orderService.createOrder(command);
return ResponseEntity.status(HttpStatus.CREATED).body(toResponse(order));
}
@PostMapping("/{orderId}/confirm")
public ResponseEntity<Void> confirmOrder(@PathVariable String orderId) {
orderService.confirmOrder(new OrderId(orderId));
return ResponseEntity.ok().build();
}
}
// 出站适配器 - JPA 仓库实现
@Repository
public class JpaOrderRepository implements OrderRepository {
private final SpringDataOrderRepository jpaRepository;
private final OrderMapper mapper;
public JpaOrderRepository(SpringDataOrderRepository jpaRepository, OrderMapper mapper) {
this.jpaRepository = jpaRepository;
this.mapper = mapper;
}
@Override
public Order save(Order order) {
OrderEntity entity = mapper.toEntity(order);
OrderEntity saved = jpaRepository.save(entity);
return mapper.toDomain(saved);
}
@Override
public Optional<Order> findById(OrderId id) {
return jpaRepository.findById(id.getValue())
.map(mapper::toDomain);
}
@Override
public List<Order> findByCustomerId(CustomerId customerId) {
return jpaRepository.findByCustomerId(customerId.getValue())
.stream()
.map(mapper::toDomain)
.collect(Collectors.toList());
}
}
// 出站适配器 - 支付服务客户端
@Component
public class HttpPaymentService implements PaymentService {
private final PaymentClient paymentClient;
public HttpPaymentService(PaymentClient paymentClient) {
this.paymentClient = paymentClient;
}
@Override
public PaymentResult processPayment(OrderId orderId, Money amount) {
PaymentRequest request = new PaymentRequest(
orderId.getValue(),
amount.getAmount(),
amount.getCurrency().getCurrencyCode()
);
try {
PaymentResponse response = paymentClient.charge(request);
return new PaymentResult(response.isSuccess(), response.getTransactionId());
} catch (PaymentException e) {
return new PaymentResult(false, null);
}
}
}
代码组织结构
src/
├── domain/ # 领域层(六边形内部)
│ ├── model/ # 领域模型
│ │ ├── Order.java
│ │ ├── OrderItem.java
│ │ ├── Customer.java
│ │ └── Money.java
│ ├── service/ # 领域服务
│ │ └── OrderDomainService.java
│ ├── event/ # 领域事件
│ │ └── OrderShippedEvent.java
│ └── exception/ # 领域异常
│ └── DomainException.java
├── application/ # 应用层
│ ├── port/ # 端口定义
│ │ ├── in/ # 入站端口
│ │ │ └── OrderService.java
│ │ └── out/ # 出站端口
│ │ ├── OrderRepository.java
│ │ ├── PaymentService.java
│ │ └── NotificationService.java
│ └── service/ # 应用服务实现
│ └── OrderServiceImpl.java
├── adapter/ # 适配器层(六边形外部)
│ ├── in/ # 入站适配器
│ │ ├── web/ # Web 适配器
│ │ │ ├── OrderController.java
│ │ │ └── dto/
│ │ └── messaging/ # 消息适配器
│ │ └── OrderEventListener.java
│ └── out/ # 出站适配器
│ ├── persistence/ # 持久化适配器
│ │ ├── JpaOrderRepository.java
│ │ └── entity/
│ ├── payment/ # 支付服务适配器
│ │ └── HttpPaymentService.java
│ └── notification/ # 通知服务适配器
│ └── EmailNotificationService.java
└── config/ # 配置
└── ApplicationConfig.java
六边形架构的优势
| 优势 | 说明 |
|---|---|
| 可测试性 | 领域逻辑不依赖外部框架,易于单元测试 |
| 技术无关 | 可以轻松替换数据库、框架或外部服务 |
| 关注点分离 | 业务逻辑与技术细节清晰分离 |
| 灵活性 | 支持多种入站/出站适配器同时存在 |
| 可维护性 | 清晰的边界使代码更易于理解和维护 |
测试策略
// 单元测试 - 只测试领域逻辑,无需 Spring 上下文
class OrderTest {
@Test
void shouldCalculateTotalCorrectly() {
// Given
CustomerId customerId = new CustomerId("C001");
List<OrderItem> items = Arrays.asList(
new OrderItem("P001", "Product 1", 2, new Money(new BigDecimal("100"), Currency.getInstance("CNY"))),
new OrderItem("P002", "Product 2", 1, new Money(new BigDecimal("200"), Currency.getInstance("CNY")))
);
// When
Order order = Order.create(customerId, items);
// Then
assertEquals(new BigDecimal("400"), order.getTotalAmount().getAmount());
}
@Test
void shouldNotShipPendingOrder() {
// Given
Order order = createPendingOrder();
// When/Then
assertThrows(IllegalStateException.class, () -> {
order.ship(new TrackingNumber("TRACK123"));
});
}
}
// 集成测试 - 使用内存适配器
@SpringBootTest
class OrderServiceIntegrationTest {
@Autowired
private OrderService orderService;
@MockBean
private PaymentService paymentService;
@Test
void shouldCreateAndConfirmOrder() {
// Given
CreateOrderCommand command = new CreateOrderCommand(
new CustomerId("C001"),
Arrays.asList(new OrderItemCommand("P001", 2))
);
when(paymentService.processPayment(any(), any()))
.thenReturn(new PaymentResult(true, "TXN123"));
// When
Order order = orderService.createOrder(command);
orderService.confirmOrder(order.getId());
// Then
Order confirmed = orderService.findOrder(order.getId()).orElseThrow();
assertEquals(OrderStatus.CONFIRMED, confirmed.getStatus());
}
}
六边形架构 vs 分层架构
| 特性 | 分层架构 | 六边形架构 |
|---|---|---|
| 依赖方向 | 上层依赖下层 | 都依赖领域 |
| 外部交互 | 通过底层 | 通过端口 |
| 可测试性 | 需要集成测试 | 易于单元测试 |
| 技术替换 | 较困难 | 容易 |
| 复杂度 | 较低 | 较高 |
适用场景
- 复杂业务逻辑:领域逻辑复杂,需要清晰边界
- 长期维护:系统需要长期演进
- 多接口支持:需要支持 Web、CLI、消息等多种接口
- 技术栈可能变化:未来可能更换技术栈
总结
六边形架构通过端口和适配器模式,将应用程序核心与外部世界隔离,提供了优秀的可测试性和灵活性。虽然增加了一定的复杂度,但对于复杂业务系统来说,这种投资是值得的。
"六边形架构让领域逻辑成为一等公民,技术细节成为可替换的实现。" —— Alistair Cockburn