Skip to content

六边形架构(Hexagonal Architecture)

要理解六边形架构(Hexagonal Architecture),我们需要先回到它的设计初心:解决传统架构中“业务逻辑与技术实现紧耦合”的痛点,让领域模型(核心业务)成为系统的“心脏”,而外部依赖(如数据库、API、UI)则作为“外围设备”,通过抽象接口(端口)适配层与核心连接。

一、六边形架构的核心概念

六边形架构由Alistair Cockburn在2005年提出,别名端口-适配器架构(Ports and Adapters Architecture)。它的核心逻辑可以用三个关键词概括:

1. 核心(Core):领域层(Domain Layer)

六边形的中心是系统的业务核心,包含:

  • 领域模型(Entities):如OrderProduct等业务对象,封装了业务属性和规则(如“订单金额必须大于0”)。
  • 业务逻辑(Use Cases):如“创建订单”“取消订单”等具体业务流程,是领域模型的行为扩展。

核心的唯一原则不依赖任何外部系统或技术框架(如Spring、MySQL、HTTP)。它只关心“业务是什么”,不关心“如何实现”。

2. 端口(Ports):核心与外部的抽象边界

端口是核心与外部系统之间的契约接口,分为两类:

  • 输入端口(Ingress Ports):核心暴露给外部的“入口”,用于触发核心业务逻辑。
    比如:CreateOrderUseCase接口(定义“创建订单”的输入参数和返回值)、CancelOrderUseCase接口。
    外部系统(如UI、API)通过调用输入端口来触发核心逻辑。

  • 输出端口(Egress Ports):核心定义的“依赖抽象”,用于调用外部系统(如数据库、第三方服务)。
    比如:OrderRepository(定义“保存订单”“查询订单”的方法)、InventoryService(定义“验证库存”的方法)。
    核心通过输出端口“声明”自己需要的外部能力,但不关心这些能力的具体实现。

3. 适配器(Adapters):端口与外部系统的“翻译器”

适配器是端口的具体实现,负责将外部系统的“语言”转换为核心能理解的“语言”,反之亦然。它分为两类:

  • 输入适配器(Ingress Adapters):将外部请求转换为核心的输入参数,调用输入端口。
    比如:

    • HTTP适配器(Spring MVC Controller):接收POST /orders请求,将JSON参数转换为CreateOrderRequest对象,调用CreateOrderUseCase
    • CLI适配器:处理命令行输入,触发核心逻辑。
  • 输出适配器(Egress Adapters):实现输出端口,将核心的调用转换为外部系统的操作。
    比如:

    • JDBC适配器:实现OrderRepository,用SQL将Order对象保存到MySQL。
    • REST适配器:实现InventoryService,调用第三方库存服务的GET /inventory/{productId}接口。

二、六边形架构的结构示意

外部系统(Web/CLI/数据库/第三方服务)


适配器层(输入/输出适配器)


端口层(输入/输出端口,抽象接口)


核心层(领域模型+业务逻辑,纯业务)

六边形架构示意图.png

三、六边形架构的关键优势

对比传统的三层架构(UI→业务层→数据层)分层架构,六边形架构的优势更突出:

1. 业务逻辑“绝对纯净”

核心层不依赖任何技术细节(如Spring的@Autowired、MySQL的JDBC),只关注业务规则。即使外部系统(如数据库从MySQL换成PostgreSQL)或技术框架(如从Spring换成Quarkus)变化,核心层无需修改。

2. 易测试性

核心层的测试可以脱离外部依赖

  • 测试CreateOrderUseCase时,只需MockOrderRepositoryInventoryService(输出端口的实现),无需启动数据库或第三方服务。
  • 适配器层的测试只需验证“转换逻辑”(如HTTP参数是否正确转成CreateOrderRequest),无需测试业务逻辑。

3. 灵活性与扩展性

  • 新增外部系统:只需新增适配器,无需修改核心。比如要支持“消息队列触发订单创建”,只需新增MQAdapter(输入适配器),调用CreateOrderUseCase
  • 替换外部系统:只需替换适配器,核心层不变。比如把“库存服务从REST换成gRPC”,只需修改InventoryServiceAdapter的实现,核心的CreateOrderUseCase无需改动。

4. 团队协作效率提升

  • 领域团队(Business Team):专注于核心层的领域模型和业务逻辑,无需关心技术实现。
  • 技术团队(Tech Team):专注于适配器层的技术实现(如HTTP、数据库、消息队列),无需理解复杂的业务规则。

四、六边形架构的实践案例:电商订单系统

我们用一个简化的“创建订单”流程,说明各层的职责:

1. 核心层(Domain Layer)

  • 领域模型Order(订单ID、用户ID、商品列表、金额)、Product(商品ID、名称、价格)。
  • 输入端口CreateOrderUseCase(定义输入CreateOrderRequest,输出OrderResult)。
    java
    public interface CreateOrderUseCase {
        OrderResult createOrder(CreateOrderRequest request);
    }
  • 输入端口实现CreateOrderUseCaseImpl(核心业务逻辑)。
    java
    public class CreateOrderUseCaseImpl implements CreateOrderUseCase {
        // 依赖输出端口(抽象)
        private final OrderRepository orderRepository;
        private final InventoryService inventoryService;
    
        // 构造器注入(依赖倒置)
        public CreateOrderUseCaseImpl(OrderRepository orderRepository, InventoryService inventoryService) {
            this.orderRepository = orderRepository;
            this.inventoryService = inventoryService;
        }
    
        @Override
        public OrderResult createOrder(CreateOrderRequest request) {
            // 1. 验证请求参数(业务规则:商品列表不能为空)
            if (request.getProducts().isEmpty()) {
                throw new IllegalArgumentException("商品列表不能为空");
            }
            // 2. 计算订单总金额(业务规则:金额=商品价格×数量之和)
            BigDecimal totalAmount = calculateTotalAmount(request.getProducts());
            // 3. 验证库存(调用输出端口,不关心具体实现)
            inventoryService.verifyInventory(request.getProducts());
            // 4. 创建Order对象(领域模型)
            Order order = new Order(request.getUserId(), request.getProducts(), totalAmount);
            // 5. 保存订单(调用输出端口)
            orderRepository.save(order);
            // 6. 返回结果
            return new OrderResult(order.getId(), "订单创建成功");
        }
    }
  • 输出端口OrderRepository(保存订单)、InventoryService(验证库存)。
    java
    public interface OrderRepository {
        void save(Order order);
    }
    
    public interface InventoryService {
        void verifyInventory(List<Product> products);
    }

2. 适配器层(Adapters)

  • 输入适配器OrderController(HTTP适配器,接收外部请求)。
    java
    @RestController
    @RequestMapping("/orders")
    public class OrderController {
        // 依赖输入端口(抽象)
        private final CreateOrderUseCase createOrderUseCase;
    
        @Autowired
        public OrderController(CreateOrderUseCase createOrderUseCase) {
            this.createOrderUseCase = createOrderUseCase;
        }
    
        @PostMapping
        public ResponseEntity<OrderResult> createOrder(@RequestBody CreateOrderRequest request) {
            // 转换HTTP请求→核心输入参数,调用输入端口
            OrderResult result = createOrderUseCase.createOrder(request);
            return ResponseEntity.ok(result);
        }
    }
  • 输出适配器JdbcOrderRepository(数据库适配器,实现OrderRepository)、RestInventoryService(第三方服务适配器,实现InventoryService)。
    java
    // JDBC适配器:将核心的save(Order)转换为SQL操作
    @Repository
    public class JdbcOrderRepository implements OrderRepository {
        private final JdbcTemplate jdbcTemplate;
    
        @Autowired
        public JdbcOrderRepository(JdbcTemplate jdbcTemplate) {
            this.jdbcTemplate = jdbcTemplate;
        }
    
        @Override
        public void save(Order order) {
            String sql = "INSERT INTO orders (id, user_id, total_amount) VALUES (?, ?, ?)";
            jdbcTemplate.update(sql, order.getId(), order.getUserId(), order.getTotalAmount());
            // 保存商品列表(省略)
        }
    }
    
    // REST适配器:将核心的verifyInventory转换为第三方API调用
    @Service
    public class RestInventoryService implements InventoryService {
        private final RestTemplate restTemplate;
    
        @Autowired
        public RestInventoryService(RestTemplate restTemplate) {
            this.restTemplate = restTemplate;
        }
    
        @Override
        public void verifyInventory(List<Product> products) {
            // 调用第三方库存服务的REST API
            String url = "https://inventory-service.com/api/inventory/verify";
            ResponseEntity<Void> response = restTemplate.postForEntity(url, products, Void.class);
            if (!response.getStatusCode().is2xxSuccessful()) {
                throw new InventoryNotEnoughException("库存不足");
            }
        }
    }

3. 依赖注入(Dependency Injection)

通过Spring的依赖注入,将适配器注入到核心层:

  • CreateOrderUseCaseImpl依赖OrderRepositoryInventoryService,Spring会自动注入它们的适配器实现(JdbcOrderRepositoryRestInventoryService)。
  • OrderController依赖CreateOrderUseCase,Spring会注入CreateOrderUseCaseImpl

五、六边形架构的实践注意事项

要真正发挥六边形架构的价值,需要避免以下常见错误:

1. 核心层不能有外部依赖

核心层不能引入任何技术框架的代码(如Spring的@Component、Jackson的@JsonProperty),也不能依赖具体的外部系统(如MySQLRedis)。核心层的代码应该是“纯Java/Kotlin”的

2. 端口设计要“基于业务”,而非“基于技术”

  • 输入端口应该对应“业务用例”(如CreateOrderUseCase),而非“技术接口”(如OrderController)。
  • 输出端口应该对应“业务依赖”(如InventoryService),而非“技术实现”(如RestTemplate)。

3. 适配器层要“薄”,不能包含业务逻辑

适配器的职责只有两个:转换格式调用端口。业务逻辑必须放在核心层,比如“验证商品列表不能为空”不能放在OrderController,而要放在CreateOrderUseCaseImpl

4. 避免端口“过细”或“过粗”

  • 过细:每个小功能都定义一个端口,导致接口爆炸(如CreateOrderUseCaseUpdateOrderUseCaseDeleteOrderUseCase可以合并为OrderUseCase吗?不,应该保持用例的单一职责)。
  • 过粗:一个端口包含多个不相关的功能(如OrderRepository包含“保存订单”和“发送消息”,这会违反单一职责)。

六、六边形架构与其他架构的关系

  • 与DDD(领域驱动设计)的关系:六边形架构是DDD的“基础设施模式”,帮助实现DDD的“领域层隔离”。DDD关注“如何建模业务”,六边形架构关注“如何组织代码以保护领域层”。
  • 与Clean Architecture(整洁架构)的关系:六边形架构是Clean Architecture的“前身”,Clean Architecture更强调“分层”(实体→用例→接口适配器→框架),而六边形架构更强调“端口-适配器”的边界。两者的核心思想一致:依赖倒置、业务逻辑中心化

七、总结

六边形架构的本质是**“用抽象隔离变化”**:

  • 核心层(业务)是“稳定的”,不会因为外部系统变化而修改。
  • 端口层(抽象)是“契约”,定义了核心与外部的交互规则。
  • 适配器层(实现)是“可变的”,负责对接具体的外部系统。

对于业务复杂、需要长期维护的系统(如电商、金融、医疗),六边形架构能有效降低系统的耦合度,提高代码的可测试性和扩展性。而对于业务简单、快速迭代的系统(如原型、小工具),六边形架构可能会增加不必要的复杂度,需要权衡使用。

一句话概括六边形架构:“业务在中心,技术在周边,抽象做桥梁”