1. 概述

本文将基于领域驱动设计(DDD)构建一个 Spring 应用,并采用六边形架构(Hexagonal Architecture)来组织代码分层。

这种架构方式最大的好处是——各层高度解耦,替换或测试某一层时几乎不影响其他部分。比如换数据库、换接口协议,甚至把 REST API 改成命令行工具,都无需动核心业务逻辑。

2. 六边形架构简介

六边形架构的核心思想是:将应用围绕领域逻辑设计,隔离外部依赖

  • 中心是“内核”——也就是业务逻辑所在的领域层;
  • 外围是“外部世界”——比如数据库、HTTP 接口、消息队列等;
  • 内外通信通过“端口与适配器”完成:端口(Port)定义契约,适配器(Adapter)实现具体技术细节。

✅ 优势:领域层不依赖任何框架或基础设施,真正做到“可测试、可替换、可独立演进”。

3. 分层原则

我们按照职责将应用划分为三层:

  1. 领域层(Domain):核心业务逻辑,完全独立。
  2. 应用层(Application):协调领域逻辑执行,暴露接口(如 REST)。
  3. 基础设施层(Infrastructure):实现外部依赖,如数据库、配置、第三方服务。

DDD Layers

各层职责说明:

  • 领域层(Inside)

    • ✅ 包含聚合根、实体、值对象、领域服务
    • ✅ 定义仓储接口(Port)
    • ❌ 不依赖 Spring、JPA、JSON 等任何外部技术
  • 应用层(Outside)

    • ✅ 提供用户交互入口(如 REST Controller)
    • ✅ 调用领域服务,编排业务流程
    • ✅ 处理请求/响应的序列化
  • 基础设施层(Outside)

    • ✅ 实现领域层定义的接口(如仓储实现)
    • ✅ 配置 Spring Bean、数据库连接等
    • ✅ 作为适配器桥接内外

⚠️ 关键点:领域层必须保持“纯净”,不能有任何 @Component@Entity 等框架注解。

4. 领域层实现

领域层是整个系统的“心脏”。我们从订单(Order)这个聚合根开始。

4.1 聚合根:Order

public class Order {
    private UUID id;
    private OrderStatus status;
    private List<OrderItem> orderItems;
    private BigDecimal price;

    public Order(UUID id, Product product) {
        this.id = id;
        this.orderItems = new ArrayList<>(Arrays.asList(new OrderItem(product)));
        this.status = OrderStatus.CREATED;
        this.price = product.getPrice();
    }

    public void complete() {
        validateState();
        this.status = OrderStatus.COMPLETED;
    }

    public void addOrder(Product product) {
        validateState();
        validateProduct(product);
        orderItems.add(new OrderItem(product));
        price = price.add(product.getPrice());
    }

    public void removeOrder(UUID productId) {
        validateState();
        final OrderItem orderItem = getOrderItem(productId);
        orderItems.remove(orderItem);
        price = price.subtract(orderItem.getPrice());
    }

    // getters...
    
    private void validateState() {
        if (status == OrderStatus.COMPLETED) {
            throw new IllegalStateException("已完成的订单不可修改");
        }
    }

    private OrderItem getOrderItem(UUID productId) {
        return orderItems.stream()
            .filter(item -> item.getProductId().equals(productId))
            .findFirst()
            .orElseThrow();
    }
}

✅ 设计要点:

  • 构造函数确保订单初始状态为 CREATED
  • complete() 后禁止修改订单项
  • 所有变更都通过方法控制,不暴露 setter
  • OrderItemOrder 自己创建,保证一致性

4.2 订单项:OrderItem

public class OrderItem {
    private UUID productId;
    private BigDecimal price;

    public OrderItem(Product product) {
        this.productId = product.getId();
        this.price = product.getPrice();
    }

    // getters...
}

💡 注意:OrderItem 保存的是下单时的产品快照价格,避免后续价格变动影响订单总额。

4.3 仓储接口(Port)

public interface OrderRepository {
    Optional<Order> findById(UUID id);
    void save(Order order);
}

这个接口定义在领域层,具体实现放在基础设施层。

4.4 领域服务:DomainOrderService

有些逻辑不适合放在聚合根内(比如跨聚合操作),就交给领域服务处理。

public class DomainOrderService implements OrderService {

    private final OrderRepository orderRepository;

    public DomainOrderService(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }

    @Override
    public UUID createOrder(Product product) {
        Order order = new Order(UUID.randomUUID(), product);
        orderRepository.save(order);
        return order.getId();
    }

    @Override
    public void addProduct(UUID id, Product product) {
        Order order = getOrder(id);
        order.addOrder(product);
        orderRepository.save(order);
    }

    @Override
    public void completeOrder(UUID id) {
        Order order = getOrder(id);
        order.complete();
        orderRepository.save(order);
    }

    @Override
    public void deleteProduct(UUID id, UUID productId) {
        Order order = getOrder(id);
        order.removeOrder(productId);
        orderRepository.save(order);
    }

    private Order getOrder(UUID id) {
        return orderRepository
          .findById(id)
          .orElseThrow(() -> new RuntimeException("订单不存在"));
    }
}

⚠️ 注意:这个类不会被注册为 Spring Bean。它属于领域内部逻辑,由基础设施层手动注入依赖。

4.5 单元测试:验证领域逻辑

由于领域层无外部依赖,可以轻松做纯单元测试:

class DomainOrderServiceUnitTest {

    private OrderRepository orderRepository;
    private DomainOrderService tested;

    @BeforeEach
    void setUp() {
        orderRepository = mock(OrderRepository.class);
        tested = new DomainOrderService(orderRepository);
    }

    @Test
    void shouldCreateOrder_thenSaveIt() {
        final Product product = new Product(UUID.randomUUID(), BigDecimal.TEN, "iPhone");

        final UUID id = tested.createOrder(product);

        verify(orderRepository).save(any(Order.class));
        assertNotNull(id);
    }
}

✅ 踩坑提醒:**不要为了“方便”在领域对象上加 @Entity**,否则就破坏了分层边界。

5. 应用层实现

应用层负责对外暴露接口,这里我们使用 Spring MVC 提供 RESTful 接口。

5.1 REST Controller

@RestController
@RequestMapping("/orders")
public class OrderController {

    private final OrderService orderService;

    @Autowired
    public OrderController(OrderService orderService) {
        this.orderService = orderService;
    }

    @PostMapping
    public CreateOrderResponse createOrder(@RequestBody CreateOrderRequest request) {
        UUID id = orderService.createOrder(request.getProduct());
        return new CreateOrderResponse(id);
    }

    @PostMapping("/{id}/products")
    public void addProduct(@PathVariable UUID id, @RequestBody AddProductRequest request) {
        orderService.addProduct(id, request.getProduct());
    }

    @DeleteMapping("/{id}/products")
    public void deleteProduct(@PathVariable UUID id, @RequestParam UUID productId) {
        orderService.deleteProduct(id, productId);
    }

    @PostMapping("/{id}/complete")
    public void completeOrder(@PathVariable UUID id) {
        orderService.completeOrder(id);
    }
}

✅ 核心职责:

  • 将 HTTP 请求映射为领域服务调用
  • 处理参数校验、异常转换
  • 不包含业务逻辑,仅做“编排”

📌 这个 Controller 其实就是一个适配器,把外部 REST 调用适配到内部领域服务。

6. 基础设施层实现

这一层负责所有技术细节的落地。

6.1 Spring Bean 配置

@Configuration
public class BeanConfiguration {

    @Bean
    public OrderService orderService(OrderRepository orderRepository) {
        return new DomainOrderService(orderRepository);
    }
}

在这里我们将 DomainOrderService 注册为 Spring Bean,并注入 OrderRepository 实现。

6.2 MongoDB 配置

@EnableMongoRepositories(basePackageClasses = SpringDataMongoOrderRepository.class)
public class MongoDBConfiguration {
}

使用 basePackageClasses 精准扫描,避免 Spring 扫描整个项目,提升启动速度。

6.3 仓储实现(Adapter)

@Component
public class MongoDbOrderRepository implements OrderRepository {

    private final SpringDataMongoOrderRepository orderRepository;

    @Autowired
    public MongoDbOrderRepository(SpringDataMongoOrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }

    @Override
    public Optional<Order> findById(UUID id) {
        return orderRepository.findById(id);
    }

    @Override
    public void save(Order order) {
        orderRepository.save(order);
    }
}

✅ 这个实现类才是真正的“适配器”,它把领域对象 Order 适配到 MongoDB 的持久化机制。

7. 架构优势与扩展性

7.1 核心优势

  • 关注点分离:每层只做一件事,逻辑清晰
  • 领域独立:业务逻辑不依赖框架,可独立测试和复用
  • 技术可替换:数据库、接口协议可自由切换

7.2 示例:切换数据库为 Cassandra

只需新增一个仓储实现,完全不影响领域层

@Component
public class CassandraDbOrderRepository implements OrderRepository {

    private final SpringDataCassandraOrderRepository orderRepository;

    @Autowired
    public CassandraDbOrderRepository(SpringDataCassandraOrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }

    @Override
    public Optional<Order> findById(UUID id) {
        Optional<OrderEntity> entity = orderRepository.findById(id);
        return entity.map(OrderEntity::toOrder);
    }

    @Override
    public void save(Order order) {
        orderRepository.save(new OrderEntity(order));
    }
}

⚠️ 注意:这里引入了 OrderEntity 作为持久化实体,避免污染领域对象 Order

7.3 示例:替换为命令行应用

甚至可以把 REST 接口换成 CLI 工具,领域层依然不动:

@Component
public class CliOrderController {

    private static final Logger LOG = LoggerFactory.getLogger(CliOrderController.class);

    private final OrderService orderService;

    @Autowired
    public CliOrderController(OrderService orderService) {
        this.orderService = orderService;
    }

    public void createCompleteOrder() {
        LOG.info("<<创建已完成订单>>");
        UUID orderId = createOrder();
        orderService.completeOrder(orderId);
    }

    public void createIncompleteOrder() {
        LOG.info("<<创建未完成订单>>");
        createOrder();
    }

    private UUID createOrder() {
        LOG.info("创建包含两个商品的新订单");
        Product phone = new Product(UUID.randomUUID(), BigDecimal.valueOf(200), "手机");
        Product razor = new Product(UUID.randomUUID(), BigDecimal.valueOf(50), "剃须刀");

        UUID orderId = orderService.createOrder(phone);
        orderService.addProduct(orderId, razor);
        return orderId;
    }
}

💡 这种能力在做数据初始化、脚本任务时特别实用。

8. 总结

本文通过一个订单系统示例,展示了如何结合 DDD 与六边形架构构建高内聚、低耦合的 Spring 应用。

DDD Layers implemented

关键收获:

  • ✅ 领域层是核心,必须保持“纯净”
  • ✅ 应用层是“编排者”,不写业务逻辑
  • ✅ 基础设施层是“适配器”,实现技术细节
  • ✅ 通过 Port & Adapter 模式实现解耦
  • ✅ 可轻松替换数据库、接口形式等外部依赖

🔗 示例代码已上传至 GitHub:https://github.com/yourname/ddd-hexagonal-spring


原始标题:Hexagonal Architecture, DDD, and Spring