← 返回首页
🏗️

六边形架构:端口与适配器

📂 architecture ⏱ 5 min 810 words

六边形架构:端口与适配器

六边形架构概述

六边形架构(Hexagonal Architecture),也称为端口与适配器架构(Ports and Adapters),由Alistair Cockburn提出。其核心思想是将应用程序的核心业务逻辑与外部依赖(如数据库、UI、消息队列等)通过端口和适配器进行解耦。

核心概念

端口(Ports)

端口是应用程序与外部世界交互的接口点。端口定义了应用程序需要什么服务以及提供什么服务。

// 入站端口 - 应用程序提供的服务
public interface OrderService {
    Order createOrder(CreateOrderCommand command);
    Order getOrder(Long id);
    void cancelOrder(Long id);
}

// 出站端口 - 应用程序需要的服务
public interface OrderRepository {
    Order save(Order order);
    Optional<Order> findById(Long id);
    void delete(Order order);
}

public interface PaymentGateway {
    PaymentResult processPayment(PaymentRequest request);
    RefundResult processRefund(RefundRequest request);
}

public interface NotificationService {
    void sendOrderConfirmation(Order order);
    void sendOrderCancellation(Order order);
}

适配器(Adapters)

适配器是端口的具体实现,负责将外部系统与端口连接起来。

// 入站适配器 - REST API
@RestController
@RequestMapping("/api/orders")
public class OrderRestAdapter {
    
    private final OrderService orderService;
    private final OrderMapper mapper;
    
    public OrderRestAdapter(OrderService orderService, OrderMapper mapper) {
        this.orderService = orderService;
        this.mapper = mapper;
    }
    
    @PostMapping
    public ResponseEntity<OrderResponse> createOrder(@RequestBody CreateOrderRequest request) {
        CreateOrderCommand command = mapper.toCommand(request);
        Order order = orderService.createOrder(command);
        OrderResponse response = mapper.toResponse(order);
        return ResponseEntity.ok(response);
    }
    
    @GetMapping("/{id}")
    public ResponseEntity<OrderResponse> getOrder(@PathVariable Long id) {
        Order order = orderService.getOrder(id);
        OrderResponse response = mapper.toResponse(order);
        return ResponseEntity.ok(response);
    }
}

// 入站适配器 - 消息队列消费者
@Component
public class OrderMessageAdapter {
    
    private final OrderService orderService;
    private final OrderMapper mapper;
    
    @KafkaListener(topics = "order-commands")
    public void handleOrderCommand(String message) {
        CreateOrderCommand command = mapper.fromJson(message);
        orderService.createOrder(command);
    }
}

// 出站适配器 - JPA实现
@Repository
public class JpaOrderAdapter implements OrderRepository {
    
    private final OrderJpaRepository jpaRepository;
    private final OrderMapper mapper;
    
    @Override
    public Order save(Order order) {
        OrderJpaEntity entity = mapper.toJpaEntity(order);
        OrderJpaEntity savedEntity = jpaRepository.save(entity);
        return mapper.toDomainEntity(savedEntity);
    }
    
    @Override
    public Optional<Order> findById(Long id) {
        return jpaRepository.findById(id)
                .map(mapper::toDomainEntity);
    }
}

// 出站适配器 - 外部支付网关
@Component
public class StripePaymentAdapter implements PaymentGateway {
    
    private final StripeClient stripeClient;
    
    @Override
    public PaymentResult processPayment(PaymentRequest request) {
        try {
            Charge charge = stripeClient.charges().create(
                ChargeCreateParams.builder()
                    .setAmount(request.getAmount().longValue())
                    .setCurrency("usd")
                    .setSource(request.getPaymentMethod())
                    .build()
            );
            return PaymentResult.success(charge.getId());
        } catch (StripeException e) {
            return PaymentResult.failure(e.getMessage());
        }
    }
}

架构图

┌─────────────────────────────────────────────────────────┐
│                    入站适配器                            │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐              │
│  │ REST API │  │ 消息队列  │  │   CLI    │              │
│  └────┬─────┘  └────┬─────┘  └────┬─────┘              │
│       │             │             │                     │
│       ▼             ▼             ▼                     │
│  ┌─────────────────────────────────────────────┐       │
│  │              入站端口 (Ports)                 │       │
│  │         OrderService, CommandHandler        │       │
│  └─────────────────────┬───────────────────────┘       │
│                        │                               │
│  ┌─────────────────────┴───────────────────────┐       │
│  │              核心业务逻辑                    │       │
│  │         领域模型、业务规则、用例              │       │
│  └─────────────────────┬───────────────────────┘       │
│                        │                               │
│  ┌─────────────────────┴───────────────────────┐       │
│  │              出站端口 (Ports)                 │       │
│  │      Repository, PaymentGateway, etc.       │       │
│  └─────────────────────┬───────────────────────┘       │
│       │             │             │                     │
│       ▼             ▼             ▼                     │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐              │
│  │   JPA    │  │ Stripe   │  │  Email   │              │
│  │ Adapter  │  │ Adapter  │  │ Adapter  │              │
│  └──────────┘  └──────────┘  └──────────┘              │
│                    出站适配器                            │
└─────────────────────────────────────────────────────────┘

业务逻辑实现

核心业务逻辑不依赖任何外部框架或技术:

// 领域实体
public class Order {
    private OrderId id;
    private CustomerId customerId;
    private List<OrderItem> items;
    private OrderStatus status;
    private Money totalAmount;
    private LocalDateTime createdAt;
    
    // 业务规则
    public static Order create(CustomerId customerId, List<OrderItem> items) {
        if (items.isEmpty()) {
            throw new DomainException("Order must contain at least one item");
        }
        
        Order order = new Order();
        order.id = OrderId.generate();
        order.customerId = customerId;
        order.items = new ArrayList<>(items);
        order.status = OrderStatus.CREATED;
        order.createdAt = LocalDateTime.now();
        order.recalculateTotal();
        
        return order;
    }
    
    public void cancel() {
        if (status != OrderStatus.CREATED) {
            throw new DomainException("Only created orders can be cancelled");
        }
        this.status = OrderStatus.CANCELLED;
    }
    
    public void confirm() {
        if (status != OrderStatus.CREATED) {
            throw new DomainException("Only created orders can be confirmed");
        }
        this.status = OrderStatus.CONFIRMED;
    }
    
    private void recalculateTotal() {
        this.totalAmount = items.stream()
                .map(OrderItem::getTotalPrice)
                .reduce(Money.ZERO, Money::add);
    }
}

// 用例实现
@Service
public class CreateOrderUseCase {
    
    private final OrderRepository orderRepository;
    private final PaymentGateway paymentGateway;
    private final NotificationService notificationService;
    
    public CreateOrderUseCase(
            OrderRepository orderRepository,
            PaymentGateway paymentGateway,
            NotificationService notificationService) {
        this.orderRepository = orderRepository;
        this.paymentGateway = paymentGateway;
        this.notificationService = notificationService;
    }
    
    @Transactional
    public Order execute(CreateOrderCommand command) {
        // 1. 创建订单
        Order order = Order.create(
            new CustomerId(command.getCustomerId()),
            command.getItems().stream()
                .map(this::toOrderItem)
                .collect(Collectors.toList())
        );
        
        // 2. 处理支付
        PaymentRequest paymentRequest = new PaymentRequest(
            order.getId(),
            order.getTotalAmount(),
            command.getPaymentMethod()
        );
        PaymentResult paymentResult = paymentGateway.processPayment(paymentRequest);
        
        if (!paymentResult.isSuccess()) {
            throw new PaymentException("Payment failed: " + paymentResult.getErrorMessage());
        }
        
        // 3. 确认订单
        order.confirm();
        
        // 4. 保存订单
        Order savedOrder = orderRepository.save(order);
        
        // 5. 发送通知
        notificationService.sendOrderConfirmation(savedOrder);
        
        return savedOrder;
    }
}

测试策略

六边形架构使得测试变得简单,因为业务逻辑与外部依赖完全解耦:

// 单元测试 - 使用内存适配器
class CreateOrderUseCaseTest {
    
    private CreateOrderUseCase useCase;
    private InMemoryOrderRepository orderRepository;
    private InMemoryPaymentGateway paymentGateway;
    private InMemoryNotificationService notificationService;
    
    @BeforeEach
    void setUp() {
        orderRepository = new InMemoryOrderRepository();
        paymentGateway = new InMemoryPaymentGateway();
        notificationService = new InMemoryNotificationService();
        useCase = new CreateOrderUseCase(orderRepository, paymentGateway, notificationService);
    }
    
    @Test
    void shouldCreateOrderSuccessfully() {
        // Given
        CreateOrderCommand command = new CreateOrderCommand(
            1L,
            List.of(new OrderItemCommand(100L, 2)),
            "credit_card"
        );
        paymentGateway.mockSuccess();
        
        // When
        Order order = useCase.execute(command);
        
        // Then
        assertNotNull(order.getId());
        assertEquals(OrderStatus.CONFIRMED, order.getStatus());
        assertEquals(1, order.getItems().size());
        
        // 验证订单已保存
        Optional<Order> savedOrder = orderRepository.findById(order.getId());
        assertTrue(savedOrder.isPresent());
        
        // 验证支付已处理
        assertEquals(1, paymentGateway.getProcessedPayments().size());
        
        // 验证通知已发送
        assertEquals(1, notificationService.getSentNotifications().size());
    }
}

// 集成测试 - 使用真实适配器
@SpringBootTest
class OrderIntegrationTest {
    
    @Autowired
    private TestRestTemplate restTemplate;
    
    @Autowired
    private OrderRepository orderRepository;
    
    @Test
    void shouldCreateOrderViaRestApi() {
        // Given
        CreateOrderRequest request = new CreateOrderRequest(
            1L,
            List.of(new OrderItemRequest(100L, 2)),
            "credit_card"
        );
        
        // When
        ResponseEntity<OrderResponse> response = restTemplate.postForEntity(
            "/api/orders",
            request,
            OrderResponse.class
        );
        
        // Then
        assertEquals(HttpStatus.OK, response.getStatusCode());
        assertNotNull(response.getBody().getId());
        
        // 验证数据库
        Optional<Order> order = orderRepository.findById(response.getBody().getId());
        assertTrue(order.isPresent());
    }
}

六边形架构的优点

  1. 技术无关性:业务逻辑不依赖特定技术栈
  2. 可测试性:可以使用内存适配器进行快速单元测试
  3. 可替换性:可以轻松替换外部依赖(如更换数据库或支付网关)
  4. 多入口支持:可以同时支持REST API、消息队列、CLI等多种入口
  5. 团队协作:不同团队可以并行开发不同的适配器

实施建议

  1. 定义清晰的端口:端口应该只包含业务需要的操作
  2. 保持核心纯净:核心业务逻辑不应包含任何框架注解或技术代码
  3. 使用依赖注入:通过构造函数注入适配器,便于测试和替换
  4. 为每个外部系统创建适配器:不要直接在业务逻辑中调用外部API
  5. 编写测试:使用内存适配器进行单元测试,使用真实适配器进行集成测试