1、简介
- 目标:基于 SpringBoot 演示 DDD 四层架构落地;不是框架,是代码组织方式。
- 示例业务:简易订单限界上下文,完成下单。
- 技术栈:SpringBoot + MyBatis‑Plus(基础设施层实现仓储)。
- 重要提醒:简单 CRUD 业务没必要硬套 DDD;DDD 适合复杂业务。
2、四层架构与 SpringBoot 映射
| DDD 分层 | 职责 | SpringBoot 对应位置 | 依赖规则 |
|---|---|---|---|
| 接口层 (Presentation) | http 接口,接收入参,返回 DTO,基础参数校验 | controller | 只调用应用层 |
| 应用层 (Application) | 流程编排、事务、发布事件,无业务规则 | application | 调用领域层、仓储接口;不依赖数据库 |
| 领域层 (Domain) | 核心:实体、值对象、聚合根、领域服务、领域事件、仓储接口 | domain | 不依赖 Spring、不依赖数据库、不依赖 MyBatis |
| 基础设施层 (Infrastructure) | 仓储实现、MyBatis、MQ、第三方调用、防腐层 | infrastructure | 实现领域层定义的 Repository 接口;依赖数据库组件 |
关键约束:
- 领域层不能引用任何数据库、mybatis 相关类,只定义仓储接口。
- 仓储接口定义在 domain,实现在 infrastructure。
- 业务规则全部写在领域对象 / 领域服务,不能写在 Controller、Application。
3. SpringBoot 包结构(完整可直接复制)
com.example.dddorder
├── controller # 接口层 Presentation
│ └── OrderController.java
├── application # 应用层 Application
│ ├── dto # 入参出参DTO
│ │ ├── CreateOrderCommand.java
│ │ └── OrderDTO.java
│ └── OrderApplicationService.java
├── domain # 领域层 Domain(核心!无技术依赖)
│ ├── event # 领域事件
│ │ └── OrderCreatedDomainEvent.java
│ ├── model # 领域模型:聚合、实体、值对象
│ │ ├── aggregate
│ │ │ └── Order.java # 聚合根 Order
│ │ ├── entity
│ │ │ └── OrderItem.java # 子实体(订单行,属于Order聚合内部)
│ │ └── vo
│ │ ├── AddressVO.java # 值对象:收货地址
│ │ ├── MoneyVO.java # 值对象:金额
│ │ └── OrderStatusVO.java # 值对象:订单状态
│ ├── repository # 仓储【接口】,只定义,不实现
│ │ └── OrderRepository.java
│ └── service # 领域服务,承载业务规则
│ └── OrderDomainService.java
└── infrastructure # 基础设施层 Infrastructure
├── config # spring配置
├── persistence # 持久化实现
│ ├── do # 数据库DO对象,和表一一对应
│ │ ├── OrderDO.java
│ │ └── OrderItemDO.java
│ ├── mapper # Mybatis Mapper
│ │ ├── OrderMapper.java
│ │ └── OrderItemMapper.java
│ └── impl # 仓储接口实现类
│ └── OrderRepositoryImpl.java
└── event # 领域事件发送实现(MQ)
└── DomainEventPublisherImpl.java
重要区分三种对象:
- Domain Model(domain 下):业务模型,DDD 模型,不感知数据库。
- DO(infrastructure/persistence/do):数据库表映射对象。
- DTO(application/dto):对外接口出入参。 ❗禁止:领域实体直接作为返回 DTO,禁止直接返回 DO 给前端。
4、分层代码示例
4.1 领域层(Domain)
4.1.1 值对象 MoneyVO.java(不可变)
值对象:全部 final,无 id,通过属性判等。
package com.example.dddorder.domain.model.vo;
import java.math.BigDecimal;
// 值对象:金额,不可变
public class MoneyVO {
private final BigDecimal amount;
private final String currency;
public MoneyVO(BigDecimal amount, String currency) {
this.amount = amount;
this.currency = currency;
}
// 领域行为:金额相加
public MoneyVO add(MoneyVO other) {
return new MoneyVO(this.amount.add(other.amount), this.currency);
}
// getter,不要setter!值对象禁止修改
public BigDecimal getAmount() { return amount; }
public String getCurrency() { return currency; }
}
4.1.2 领域事件 OrderCreatedDomainEvent.java
package com.example.dddorder.domain.event;
import java.time.LocalDateTime;
// 领域事件:订单已创建,代表已经发生的业务事实
public class OrderCreatedDomainEvent {
private final Long orderId;
private final Long userId;
private final LocalDateTime createTime;
public OrderCreatedDomainEvent(Long orderId, Long userId, LocalDateTime createTime) {
this.orderId = orderId;
this.userId = userId;
this.createTime = createTime;
}
// getter
public Long getOrderId() { return orderId; }
public Long getUserId() { return userId; }
public LocalDateTime getCreateTime() { return createTime; }
}
4.1.3 聚合根 Order.java
Order 是聚合根;OrderItem 是内部子实体,外部不能直接访问 OrderItem。
package com.example.dddorder.domain.model.aggregate;
import com.example.dddorder.domain.model.entity.OrderItem;
import com.example.dddorder.domain.model.vo.AddressVO;
import com.example.dddorder.domain.model.vo.MoneyVO;
import com.example.dddorder.domain.model.vo.OrderStatusVO;
import com.example.dddorder.domain.event.OrderCreatedDomainEvent;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
/**
* 聚合根:Order
* 聚合边界:Order + OrderItem
* 外部只能操作Order,不能直接操作OrderItem
*/
public class Order {
// 唯一标识:实体ID
private Long id;
private Long userId;
private OrderStatusVO status;
private AddressVO address;
private MoneyVO totalAmount;
private LocalDateTime createTime;
// 聚合内部子实体,外部不能直接访问
private List<OrderItem> items = new ArrayList<>();
// 领域事件:本聚合产生的事件,暂存此处,应用层负责发布
private List<Object> domainEvents = new ArrayList<>();
/**
* 领域行为:创建订单(业务规则写在这里,不是写在service)
*/
public static Order create(Long userId, AddressVO address, List<OrderItem> items){
Order order = new Order();
order.userId = userId;
order.address = address;
order.status = OrderStatusVO.CREATED;
order.items.addAll(items);
order.createTime = LocalDateTime.now();
// 计算总金额(领域内业务逻辑)
MoneyVO total = new MoneyVO(BigDecimal.ZERO,"CNY");
for (OrderItem item : items) {
total = total.add(item.getSubTotal());
}
order.totalAmount = total;
// 记录领域事件
order.domainEvents.add(new OrderCreatedDomainEvent(order.id, userId, order.createTime));
return order;
}
// 取出并清空领域事件
public List<Object> popDomainEvents(){
List<Object> events = new ArrayList<>(this.domainEvents);
this.domainEvents.clear();
return events;
}
// getter,内部业务方法
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public Long getUserId() { return userId; }
public OrderStatusVO getStatus() { return status; }
public MoneyVO getTotalAmount() { return totalAmount; }
public List<OrderItem> getItems() { return items; }
}
4.1.4 仓储接口 OrderRepository.java(domain 层,只定义)
domain 层只定义接口,不引入 mybatis。
package com.example.dddorder.domain.repository;
import com.example.dddorder.domain.model.aggregate.Order;
/**
* 仓储接口:只操作聚合根Order
*/
public interface OrderRepository {
// 保存聚合根,内部会保存Order+OrderItem
Order save(Order order);
Order findById(Long orderId);
}
4.1.5 领域服务 OrderDomainService.java
当业务逻辑不属于单个聚合,多个对象协作时使用领域服务;无状态。
package com.example.dddorder.domain.service;
import com.example.dddorder.domain.model.aggregate.Order;
import com.example.dddorder.domain.model.entity.OrderItem;
import com.example.dddorder.domain.model.vo.AddressVO;
import java.util.List;
/**
* 领域服务:存放不属于Order聚合本身的业务规则
* 本示例模拟下单前校验商品合法性
*/
public class OrderDomainService {
// 领域业务规则校验
public void validateOrderCreate(Long userId, List<OrderItem> items, AddressVO address){
if(items == null || items.isEmpty()){
throw new IllegalArgumentException("订单项不能为空");
}
if(address == null){
throw new IllegalArgumentException("收货地址不能为空");
}
// 可以写更多业务规则:价格校验、商品限购等
}
public Order createOrder(Long userId, List<OrderItem> items, AddressVO address){
validateOrderCreate(userId, items, address);
return Order.create(userId, address, items);
}
}
4.2 应用层 Application
职责:流程编排、事务控制、调用领域、发布事件;不写业务规则。
package com.example.dddorder.application;
import com.example.dddorder.application.dto.CreateOrderCommand;
import com.example.dddorder.domain.event.OrderCreatedDomainEvent;
import com.example.dddorder.domain.model.aggregate.Order;
import com.example.dddorder.domain.model.entity.OrderItem;
import com.example.dddorder.domain.model.vo.AddressVO;
import com.example.dddorder.domain.repository.OrderRepository;
import com.example.dddorder.domain.service.OrderDomainService;
import com.example.dddorder.infrastructure.event.DomainEventPublisherImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
public class OrderApplicationService {
private final OrderDomainService orderDomainService;
private final OrderRepository orderRepository;
private final DomainEventPublisherImpl eventPublisher;
public OrderApplicationService(OrderDomainService orderDomainService,
OrderRepository orderRepository,
DomainEventPublisherImpl eventPublisher) {
this.orderDomainService = orderDomainService;
this.orderRepository = orderRepository;
this.eventPublisher = eventPublisher;
}
/**
* 应用服务方法:只编排流程,没有业务规则
*/
@Transactional(rollbackFor = Exception.class)
public Long createOrder(CreateOrderCommand command){
// 1.组装参数为领域对象
AddressVO address = new AddressVO(command.getReceiver(), command.getPhone(), command.getAddressDetail());
List<OrderItem> itemList = command.getItems();
// 2.调用领域服务得到聚合根
Order order = orderDomainService.createOrder(command.getUserId(), itemList, address);
// 3.仓储保存聚合根
Order savedOrder = orderRepository.save(order);
// 4.取出领域事件,发布(如发送MQ,通知库存、支付服务)
List<Object> events = savedOrder.popDomainEvents();
for (Object event : events) {
if(event instanceof OrderCreatedDomainEvent){
eventPublisher.publish((OrderCreatedDomainEvent) event);
}
}
return savedOrder.getId();
}
}
4.3 接口层 Controller
package com.example.dddorder.controller;
import com.example.dddorder.application.OrderApplicationService;
import com.example.dddorder.application.dto.CreateOrderCommand;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/order")
public class OrderController {
private final OrderApplicationService orderApplicationService;
public OrderController(OrderApplicationService orderApplicationService) {
this.orderApplicationService = orderApplicationService;
}
@PostMapping("/create")
public Long create(@RequestBody CreateOrderCommand command){
return orderApplicationService.createOrder(command);
}
}
4.4 基础设施层:仓储实现 OrderRepositoryImpl
-
实现 domain 定义的 Repository 接口;做【领域模型 ↔ DO】转换;调用 Mapper。
-
领域模型和数据库 DO 必须互相转换,不能直接用领域对象操作数据库。
package com.example.dddorder.infrastructure.persistence.impl;
import com.example.dddorder.domain.model.aggregate.Order;
import com.example.dddorder.domain.model.entity.OrderItem;
import com.example.dddorder.domain.repository.OrderRepository;
import com.example.dddorder.infrastructure.persistence.do.OrderDO;
import com.example.dddorder.infrastructure.persistence.do.OrderItemDO;
import com.example.dddorder.infrastructure.persistence.mapper.OrderItemMapper;
import com.example.dddorder.infrastructure.persistence.mapper.OrderMapper;
import org.springframework.stereotype.Repository;import java.util.List;
import java.util.stream.Collectors;@Repository
public class OrderRepositoryImpl implements OrderRepository {private final OrderMapper orderMapper; private final OrderItemMapper orderItemMapper; public OrderRepositoryImpl(OrderMapper orderMapper, OrderItemMapper orderItemMapper) { this.orderMapper = orderMapper; this.orderItemMapper = orderItemMapper; } @Override public Order save(Order order) { // 1.领域模型转DO OrderDO orderDO = toOrderDO(order); if(order.getId() == null){ orderMapper.insert(orderDO); order.setId(orderDO.getId()); }else { orderMapper.updateById(orderDO); } // 保存子实体OrderItem List<OrderItemDO> itemDos = order.getItems().stream() .map(this::toOrderItemDO) .peek(i -> i.setOrderId(order.getId())) .collect(Collectors.toList()); // 先删旧,再批量插入 orderItemMapper.deleteByOrderId(order.getId()); for (OrderItemDO itemDO : itemDos) { orderItemMapper.insert(itemDO); } return order; } @Override public Order findById(Long orderId) { OrderDO orderDO = orderMapper.selectById(orderId); List<OrderItemDO> itemDOS = orderItemMapper.selectByOrderId(orderId); // DO转领域聚合Order return toOrderAggregate(orderDO, itemDOS); } // ------------------- 对象转换器 ------------------- private OrderDO toOrderDO(Order order){ // 领域模型 → DO OrderDO orderDO = new OrderDO(); orderDO.setId(order.getId()); orderDO.setUserId(order.getUserId()); orderDO.setStatus(order.getStatus().getCode()); orderDO.setTotalAmount(order.getTotalAmount().getAmount()); return orderDO; } private OrderItemDO toOrderItemDO(OrderItem item){ OrderItemDO itemDO = new OrderItemDO(); itemDO.setProductId(item.getProductId()); itemDO.setQuantity(item.getQuantity()); itemDO.setSubAmount(item.getSubTotal().getAmount()); return itemDO; } private Order toOrderAggregate(OrderDO orderDO, List<OrderItemDO> itemDOS){ // DO还原聚合根Order,省略实现 return null; }}
5. 完整调用链路
Controller(接口层)
→ OrderApplicationService(应用层:事务、编排)
→ OrderDomainService(领域服务:执行业务规则校验)
→ Order聚合根.create() (领域模型内部执行业务逻辑,生成领域事件)
→ OrderRepository.save() 【调用接口】
→ OrderRepositoryImpl(基础设施实现,DO转换,Mybatis保存Order+OrderItem)
→ popDomainEvents(),发布OrderCreatedDomainEvent事件
6. 关键落地注意事项(SpringBoot DDD 常见坑)
- ❌ 不要把领域层注入 Spring Bean:聚合根、实体、值对象是 POJO,不要加 @Service/@Component。只有领域服务、应用服务、仓储实现是 Spring Bean。
- ❌ 禁止在领域模型中直接操作数据库;领域层完全不依赖 mybatis。
- ❌ 禁止外部直接操作聚合内部子实体(本案例不能直接修改 OrderItem,必须通过 Order 聚合根)。
- ❌ 不要把 DO 直接返回给 Controller;不要把领域实体直接作为 DTO 返回。三层对象:DTO <-> DomainModel <-> DO,需要互相转换。
- ❌ 应用层不要写 if‑else 业务规则,业务规则下沉到领域对象 / 领域服务。
- ✅ 领域事件:领域只生产事件对象;事件发布交给应用层,领域层不感知 MQ。
- ✅ 聚合 = 事务边界;一个 Repository.save 操作,一个聚合一次持久化。
7. 什么时候不要上 DDD
- 简单后台 CRUD 系统:直接 MVC 即可,DDD 会增加大量样板代码。
- 业务需求经常变动,没有稳定业务领域。
DDD 收益来自复杂业务;代价是样板代码变多。
参考文档: