跳到主要内容

六边形架构

六边形架构(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

延伸阅读