在DDD(领域驱动设计)项目中,应用服务层(Application Layer)和领域模型层(Domain Layer)的核心区别可以概括为:应用服务层是业务流程的"指挥家/编排者",而领域模型层是业务核心的"发动机/规则制定者"。
应用服务层不包含任何核心业务逻辑,只负责协调和转发;领域模型层则承载了系统所有的业务规则、状态变化和逻辑校验。
以下是两者的详细对比、目录结构区别、适用场景以及完整的代码示例。
⚖️ 核心区别与职责对比
| 维度 | 应用服务层 (Application Layer) | 领域模型层 (Domain Layer) |
|---|---|---|
| 核心定位 | 业务流程的编排者 和协调者 | 业务核心逻辑与规则的承载者 |
| 业务逻辑 | 严禁包含核心业务规则 | 包含所有核心业务规则、策略和完整性约束 |
| 主要职责 | 用例编排、事务控制、权限校验、DTO与领域对象转换、发布领域事件 | 表达业务概念、维护业务状态、执行领域行为(实体/值对象/聚合根)、跨聚合逻辑(领域服务) |
| 依赖关系 | 依赖领域层和基础设施层(调用仓储接口) | 零依赖(不依赖其他任何层,不依赖框架和技术细节) |
| 典型组件 | 应用服务 (Application Service)、命令/查询对象 (Command/Query)、DTO | 聚合根 (Aggregate Root)、实体 (Entity)、值对象 (Value Object)、领域服务 (Domain Service)、仓储接口 (Repository Interface) |
📂 目录结构区别
在标准的DDD四层架构中,这两层的代码目录通常如下划分(以Java/Maven项目为例):
text
com.example.project
├── application/ 【应用服务层】
│ ├── service/ # 应用服务接口与实现(如 OrderApplicationService)
│ ├── dto/ # 数据传输对象(入参Command、出参DTO)
│ └── assembler/ # 负责 DTO 与 领域对象 之间的转换(或叫 Converter)
│
├── domain/ 【领域模型层】
│ ├── model/ # 核心领域模型
│ │ ├── Order.java # 聚合根 (Aggregate Root)
│ │ ├── OrderItem.java # 实体 (Entity)
│ │ └── vo/ # 值对象 (Value Object,如 Address, Money)
│ ├── service/ # 领域服务 (处理跨聚合的复杂业务逻辑)
│ ├── event/ # 领域事件 (Domain Event)
│ └── repository/ # 仓储接口 (Repository Interface,只定义接口,不含实现)
│
└── infrastructure/ 【基础设施层】
└── repository/ # 仓储接口的具体实现 (如 OrderRepositoryImpl)
🎯 场景选择与功能归属
在实际开发中,判断一段逻辑该写在哪一层,可以遵循以下原则:
- 适合放在【领域模型层】的场景:
- 单一对象的行为与规则: 比如"订单金额计算"、"优惠券是否可用校验"、"订单状态从'待支付'流转到'已支付'"。这些逻辑应该封装在聚合根或实体的方法中(充血模型)。
- 跨多个聚合的业务规则: 比如"转账业务"(涉及两个账户聚合的余额扣减与增加),或者"下单时校验用户信用额度并扣减库存"。这种不属于单一实体的逻辑,应放在领域服务中。
- 业务概念的封装: 比如金额(Money)、收货地址(Address)等不可变对象及其自带的校验规则,应作为值对象。
- 适合放在【应用服务层】的场景:
- 业务流程的串联(用例): 比如"用户下单"这个动作,需要依次执行:
参数转换 -> 调用领域服务校验 -> 调用聚合根创建订单 -> 调用仓储保存 -> 发布订单创建事件。应用服务只负责把这些步骤串起来。 - 技术层面的横切关注点: 比如开启和提交数据库事务(
@Transactional)、当前登录用户的权限校验、记录操作日志等。 - 与外部世界的交互适配: 接收前端传来的DTO,将其转换为领域层能理解的领域对象或命令(Command),或者将领域对象转换为返回给前端的DTO。
- 业务流程的串联(用例): 比如"用户下单"这个动作,需要依次执行:
💻 完整代码示例参考
以一个电商系统的 "创建订单" 为例,对比传统贫血模型与DDD富血模型的写法。
1. 领域模型层(Domain Layer)实现
这里体现了业务规则的封装。Order 聚合根负责保证自身数据的一致性,Money 值对象负责金额的运算规则。
java
// 值对象:金额 (封装了金额的运算规则,不可变)
public class Money {
private final BigDecimal amount;
private final String currency;
public Money add(Money other) {
if (!this.currency.equals(other.currency)) {
throw new IllegalArgumentException("货币单位不一致");
}
return new Money(this.amount.add(other.amount), this.currency);
}
// ... 省略构造器与getter
}
// 聚合根:订单 (封装了订单创建的核心业务规则)
public class Order {
private OrderId id;
private List<OrderItem> items;
private Money totalAmount;
private OrderStatus status;
// 私有构造,通过工厂方法创建,确保创建出来的订单一定是合法的
private Order(OrderId id, List<OrderItem> items) {
this.id = id;
this.items = items;
this.status = OrderStatus.PENDING;
// 业务规则:创建时自动计算总金额
this.totalAmount = calculateTotalAmount(items);
}
// 工厂方法:创建订单
public static Order create(List<OrderItem> items) {
if (items == null || items.isEmpty()) {
throw new IllegalArgumentException("订单项不能为空");
}
// 业务规则:校验库存(这里假设订单项内部封装了库存校验逻辑)
items.forEach(OrderItem::checkStock);
return new Order(new OrderId(UUID.randomUUID().toString()), items);
}
private Money calculateTotalAmount(List<OrderItem> items) {
return items.stream()
.map(OrderItem::getSubtotal)
.reduce(new Money(BigDecimal.ZERO, "CNY"), Money::add);
}
}
// 领域服务接口(定义在领域层)
public interface OrderRepository {
void save(Order order);
}
2. 应用服务层(Application Layer)实现
这里体现了流程的编排。它不包含任何 if (amount > 100) 这样的业务判断,只是指挥领域对象去工作。
java
@Service
public class OrderApplicationService {
@Autowired
private OrderRepository orderRepository; // 注入领域层定义的仓储接口
// 事务控制通常放在应用服务层
@Transactional
public String createOrder(CreateOrderCommand command) {
// 1. 参数转换与基础适配 (DTO -> 领域对象)
List<OrderItem> items = command.getItems().stream()
.map(item -> new OrderItem(item.getProductId(), item.getQuantity(), item.getPrice()))
.collect(Collectors.toList());
// 2. 委托领域层执行核心业务逻辑 (调用聚合根的工厂方法)
// 此时,库存校验、金额计算等业务规则都在 Order.create 内部自动完成
Order order = Order.create(items);
// 3. 持久化 (调用仓储接口,实际实现由基础设施层提供)
orderRepository.save(order);
// 4. 发布领域事件 (通知其他系统,如积分服务、物流服务)
// domainEventPublisher.publish(new OrderCreatedEvent(order.getId()));
return order.getId().getValue();
}
}
总结:
当你需要修改业务规则(比如"满100减20"或"新用户才能用券")时,你只需要去修改领域模型层 的 Order 或 Coupon 实体;而当你需要调整业务流程(比如下单后增加一个短信通知步骤)时,你只需要修改应用服务层的编排逻辑。两者各司其职,保证了系统的可维护性和扩展性。