DDD-017:六边形架构(Hexagonal Architecture) DDD-017:六边形架构(Hexagonal Architecture)17.1 六边形架构概述17.1.1 什么是六边形架构?【原理】六边形架构(Hexagonal Architecture),又称端口与适配器架构(Ports and Adapters Architecture),由 Alistair Cockburn 于 2005 年提出。其核心思想是将应用程序分为内部和外部,内部是核心业务逻辑,外部是技术实现细节,两者通过端口和适配器进行交互。六边形架构的核心原则:内外分离:核心业务逻辑独立于技术实现依赖倒置:外部依赖内部,而非内部依赖外部端口定义:内部定义交互接口(端口)适配器实现:外部实现具体技术细节(适配器)17.1.2 架构示意图┌──────────────────────────────────────────────────┐ │ 外部世界 │ │ Web UI / REST API / CLI / MQ / External Services │ └───────────────┬──────────────────┬───────────────┘ │ │ ┌───────────────┼──────────────────┼───────────────┐ │ ▼ ▼ │ │ ┌─────────────────┐ ┌─────────────────┐ │ │ │ Rest Adapter │ │ MQ Adapter │ │ │ │ (入站适配器) │ │ (出站适配器) │ │ │ └────────┬────────┘ └────────┬────────┘ │ │ │ │ │ ┌──────────┴─────────────┴────────────────────┴──────────────┴──────────┐ │ 六边形边界(应用边界) │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ │ 应用程序核心 │ │ │ │ │ │ │ │ ┌─────────────────┐ ┌─────────────────┐ │ │ │ │ │ 入站端口 │ │ 出站端口 │ │ │ │ │ │ (Driving Port) │ │ (Driven Port) │ │ │ │ │ │ OrderUseCase │ │ OrderRepository │ │ │ │ │ └────────┬────────┘ └────────┬────────┘ │ │ │ │ │ │ │ │ │ │ │ ┌──────────────────┐ │ │ │ │ │ └─────▶│ 应用服务 │◀─┘ │ │ │ │ │ OrderApplication │ │ │ │ │ │ Service │ │ │ │ │ └────────┬─────────┘ │ │ │ │ │ │ │ │ │ ┌────────▼─────────┐ │ │ │ │ │ 领域模型 │ │ │ │ │ │ Order Aggregate │ │ │ │ │ └──────────────────┘ │ │ │ │ │ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ │ └───────────────────────────────────────────────────────────────────────────┘ │ │ ┌───────────────┼────────────────────┼───────────────┐ │ ▼ ▼ │ │ ┌─────────────────┐ ┌─────────────────┐ │ │ │ JPA Adapter │ │ Redis Adapter │ │ │ │ (出站适配器) │ │ (出站适配器) │ │ │ └────────┬────────┘ └────────┬────────┘ │ │ │ │ │ └─────────────┬┴────────────────────┴┬─────────────┘ │ │ ┌─────────────▼──────────────────────▼─────────────┐ │ 基础设施 │ │ MySQL Database / Redis / Kafka │ └──────────────────────────────────────────────────┘17.1.3 核心概念概念说明示例端口(Port)定义应用程序与外界的交互接口OrderRepository、OrderUseCase适配器(Adapter)实现端口,连接具体技术JPA Repository、REST Controller入站端口(Driving Port)定义外部如何调用应用OrderUseCase 接口出站端口(Driven Port)定义应用如何调用外部OrderRepository 接口入站适配器(Driving Adapter)实现入站端口调用REST Controller出站适配器(Driven Adapter)实现出站端口接口JPA Repository17.2 端口与适配器详解17.2.1 入站端口(Driving Port)【原理】入站端口定义了外部世界如何驱动应用程序执行业务用例。它是应用程序暴露给外部的服务接口。入站端口特点:定义在应用层或领域层以用例为中心命名(如 CreateOrder、CancelOrder)表达业务意图,不暴露技术细节通常由 Application Service 实现【代码示例】// ========== 入站端口定义 ==========packagecom.example.order.application.port.in;/** * 订单用例接口(入站端口) * 定义外部可以执行的业务操作 */publicinterfaceOrderUseCase{/** * 创建订单 */OrderIdcreateOrder(CreateOrderCommandcommand);/** * 支付订单 */voidpayOrder(PayOrderCommandcommand);/** * 取消订单 */voidcancelOrder(CancelOrderCommandcommand);/** * 查询订单详情 */OrderDetailQueryResultqueryOrderDetail(OrderIdorderId);}// ========== 命令对象 ==========@Data@BuilderpublicclassCreateOrderCommand{privateUserIduserId;privateListOrderItemDataitems;privateCouponCodecouponCode;privateShippingAddressshippingAddress;@Data@BuilderpublicstaticclassOrderItemData{privateProductIdproductId;privateQuantityquantity;}}@Data@BuilderpublicclassPayOrderCommand{privateOrderIdorderId;privatePaymentMethodpaymentMethod;privateMoneyamount;}@Data@BuilderpublicclassCancelOrderCommand{privateOrderIdorderId;privateStringreason;}// ========== 查询结果 ==========@Data@BuilderpublicclassOrderDetailQueryResult{privateOrderIdorderId;privateOrderStatusstatus;privateMoneytotalAmount;privateLocalDateTimecreateTime;privateListOrderItemResultitems;@Data@BuilderpublicstaticclassOrderItemResult{privateProductIdproductId;privateStringproductName;privateMoneyprice;privateQuantityquantity;privateMoneysubtotal;}}17.2.2 出站端口(Driven Port)【原理】出站端口定义了应用程序需要被驱动的外部依赖。它是应用程序对基础设施的需求声明。出站端口特点:定义在领域层或应用层以能力命名(如 Repository、EventPublisher)只定义接口,不关心实现由基础设施层的适配器实现【代码示例】// ========== 出站端口定义 ==========packagecom.example.order.application.port.out;/** * 订单仓储接口(出站端口) * 定义数据持久化能力 */publicinterfaceOrderRepository{/** * 保存订单 */Ordersave(Orderorder);/** * 根据ID查询订单 */OptionalOrderfindById(OrderIdorderId);/** * 根据用户ID查询订单列表 */ListOrderfindByUserId(UserIduserId);/** * 删除订单 */voiddelete(Orderorder);}/** * 事件发布接口(出站端口) * 定义事件发布能力 */publicinterfaceEventPublisher{/** * 发布领域事件 */voidpublish(ListDomainEventevents);/** * 发布单个事件 */defaultvoidpublish(DomainEventevent){publish(List.of(event));}}/** * 库存服务接口(出站端口) * 定义库存扣减能力 */publicinterfaceInventoryService{/** * 预留库存 */booleanreserveStock(ProductIdproductId,Quantityquantity);/** * 释放库存 */voidreleaseStock(ProductIdproductId,Quantityquantity);/** * 确认扣减库存 */voidconfirmDeduction(ProductIdproductId,Quantityquantity);}/** * 支付服务接口(出站端口) * 定义支付处理能力 */publicinterfacePaymentService{/** * 发起支付 */PaymentResultpay(PaymentRequestrequest);/** * 查询支付状态 */PaymentStatusqueryStatus(PaymentIdpaymentId);/** * 退款 */RefundResultrefund(RefundRequestrequest);}17.2.3 入站适配器(Driving Adapter)【原理】入站适配器实现对外部请求的接收,并将其转换为对入站端口的调用。入站适配器特点:处理特定技术协议(HTTP、MQ、CLI等)负责请求解析、参数验证、异常处理调用入站端口执行业务将结果转换为响应格式【代码示例】// ========== 入站适配器 - REST Controller ==========packagecom.example.order.adapter.in.web;@RestController@RequestMapping("/api/orders")@RequiredArgsConstructorpublicclassOrderController{privatefinalOrderUseCaseorderUseCase;// 注入入站端口@PostMappingpublicResponseEntityOrderResponsecreateOrder(@RequestBody@ValidCreateOrderRequestrequest){// 1. 将请求DTO转换为命令对象CreateOrderCommandcommand=toCommand(request);// 2. 调用入站端口OrderIdorderId=orderUseCase.createOrder(command);// 3. 返回响应returnResponseEntity.status(HttpStatus.CREATED).body(newOrderResponse(orderId.getValue()));}@PostMapping("/{orderId}/payment")publicResponseEntityVoidpayOrder(@PathVariableLongorderId,@RequestBody@ValidPayOrderRequestrequest){PayOrderCommandcommand=PayOrderCommand.builder().orderId(OrderId.of(orderId)).paymentMethod(request.getPaymentMethod()).amount(Money.of(request.getAmount())).build();orderUseCase.payOrder(command);returnResponseEntity.ok().build();}@PostMapping("/{orderId}/cancellation")publicResponseEntityVoidcancelOrder(@PathVariableLongorderId,@RequestBody@ValidCancelOrderRequestrequest){CancelOrderCommandcommand=CancelOrderCommand.builder().orderId(OrderId.of(orderId)).reason(request.getReason()).build();orderUseCase.cancelOrder(command);returnResponseEntity.ok().build();}@GetMapping("/{orderId}")publicResponseEntityOrderDetailResponsegetOrder(@PathVariableLongorderId){OrderDetailQueryResultresult=orderUseCase.queryOrderDetail(OrderId.of(orderId));returnResponseEntity.ok(toResponse(result));}// ========== 转换方法 ==========privateCreateOrderCommandtoCommand(CreateOrderRequestrequest){returnCreateOrderCommand.builder().userId(UserId.of(request.getUserId())).items(request.getItems().stream().map(item-CreateOrderCommand.OrderItemData.builder().productId(ProductId.of(item.getProductId())).quantity(Quantity.of(item.getQuantity())).build()).collect(Collectors.toList())).couponCode(request.getCouponCode()!=null?CouponCode.of(request.getCouponCode()):null).shippingAddress(toAddress(request.getShippingAddress())).build();}}// ========== 入站适配器 - 消息消费者 ==========packagecom.example.order.adapter.in.messaging;@Component@RequiredArgsConstructorpublicclassOrderEventListener{privatefinalOrderUseCaseorderUseCase;@KafkaListener(topics="payment-completed",groupId="order-service")publicvoidonPaymentCompleted(PaymentCompletedEventevent){// 将消息事件转换为命令PayOrderCommandcommand=PayOrderCommand.builder().orderId(OrderId.of(event.getOrderId()