1. 概述
本文将基于领域驱动设计(DDD)构建一个 Spring 应用,并采用六边形架构(Hexagonal Architecture)来组织代码分层。
这种架构方式最大的好处是——各层高度解耦,替换或测试某一层时几乎不影响其他部分。比如换数据库、换接口协议,甚至把 REST API 改成命令行工具,都无需动核心业务逻辑。
2. 六边形架构简介
六边形架构的核心思想是:将应用围绕领域逻辑设计,隔离外部依赖。
- 中心是“内核”——也就是业务逻辑所在的领域层;
- 外围是“外部世界”——比如数据库、HTTP 接口、消息队列等;
- 内外通信通过“端口与适配器”完成:端口(Port)定义契约,适配器(Adapter)实现具体技术细节。
✅ 优势:领域层不依赖任何框架或基础设施,真正做到“可测试、可替换、可独立演进”。
3. 分层原则
我们按照职责将应用划分为三层:
- 领域层(Domain):核心业务逻辑,完全独立。
- 应用层(Application):协调领域逻辑执行,暴露接口(如 REST)。
- 基础设施层(Infrastructure):实现外部依赖,如数据库、配置、第三方服务。
各层职责说明:
领域层(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
OrderItem
由Order
自己创建,保证一致性
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 应用。
关键收获:
- ✅ 领域层是核心,必须保持“纯净”
- ✅ 应用层是“编排者”,不写业务逻辑
- ✅ 基础设施层是“适配器”,实现技术细节
- ✅ 通过 Port & Adapter 模式实现解耦
- ✅ 可轻松替换数据库、接口形式等外部依赖
🔗 示例代码已上传至 GitHub:https://github.com/yourname/ddd-hexagonal-spring