本文系统解析ddd四层架构中应用层与领域层的核心职责划分,明确业务规则、流程编排、跨聚合协作等关键逻辑的归属原则,并结合真实代码示例说明何时该用应用服务、何时必须下沉至领域服务。
本文系统解析ddd四层架构中应用层与领域层的核心职责划分,明确业务规则、流程编排、跨聚合协作等关键逻辑的归属原则,并结合真实代码示例说明何时该用应用服务、何时必须下沉至领域服务。
在领域驱动设计(DDD)落地实践中,一个高频困惑是:“校验外部聚合是否存在”“根据名称查询并获取ID”“组装跨数据源的结果”这类逻辑,究竟该放在应用层还是领域层? 上述StockService示例中的categoryRepo.findCategoryByName()和categoryRepo.findCategoryById()调用,正是典型场景——它既非纯技术实现(如SQL执行),也非原子业务规则(如“库存不能为负”),而是介于两者之间的协调行为。要准确定位其归属,必须回归DDD分层架构的底层契约。
一、铁律:依赖方向不可逆,业务重心不可偏移
DDD官方四层架构(Interface → Application → Domain → Infrastructure)的核心约束只有一条:外层可依赖内层,内层绝不可感知外层。这意味着:
- 领域层(Domain)必须是“纯Java”:无Spring注解、无MyBatis、无HTTP客户端、无Redis调用;
- 所有业务规则(如“商品价格=基础价×折扣系数”“订单状态只能从待支付流转至已支付”)必须100%实现在领域层;
- 应用层(Application)是“指挥官”,不是“决策者”:它负责事务边界、流程顺序、跨聚合调用、事件发布,但不包含任何业务计算或判定逻辑。
因此,findCategoryByName()这类操作本身不违反原则——只要它仅用于获取必要上下文以支撑后续领域操作,且不参与业务规则判定,就属于应用层合理职责。
二、应用层的合法动作清单(附代码对照)
以下行为在应用层中完全合规,且是其存在价值的体现:
| 场景 | 是否允许 | 说明 | 对应示例代码 |
|---|---|---|---|
| 查询其他聚合根以获取ID或基础信息 | ✅ 允许 | 为构建当前聚合所需上下文,不涉及业务规则判断 | categoryRepo.findCategoryByName(...) |
| 调用外部系统(如FinHub)获取实时数据 | ✅ 允许 | 属于基础设施能力封装,应用层协调调用时机 | finhubPort.getStockPriceByName(...) |
| 组合多个仓储结果并封装DTO返回 | ✅ 允许 | 接口层适配职责的前置环节,无业务含义 | new StockWithExternalData(...) |
| 执行数据库事务控制(@Transactional) | ✅ 允许 | 保障用例级数据一致性 | (隐式由Spring管理) |
// ✅ 合规的应用层代码(精简版)
@Override
public String addStock(NewStockCommand cmd) {
// 1. 查询分类——仅为获取ID,不校验业务有效性(如"分类是否启用"属领域规则)
Category category = categoryRepo.findCategoryByName(cmd.getCategoryName());
String categoryId = Optional.ofNullable(category).map(Category::getUuid).orElse(null);
// 2. 创建领域对象——此时才触发领域层校验(如Stock构造函数强制categoryId非空)
Stock stock = new Stock(cmd.getName(), cmd.getValue(), categoryId);
// 3. 持久化——调用Infrastructure层实现
stockRepo.save(stock);
return stock.getUuid();
}
⚠️ 注意:若“分类必须启用才能关联股票”是业务规则,则category.isEnabled()检查必须移入领域层(例如在Stock构造函数或StockFactory中),应用层仅传递category对象,不作判断。
三、何时需要领域服务?——复杂业务逻辑的“安全出口”
当应用层出现以下信号时,表明逻辑已超出编排范畴,需升维至领域层:
- 出现嵌套条件分支(如if (category.isPremium()) { ... } else if (user.hasVip()) { ... });
- 需要基于多个聚合的状态做联合决策(如“只有当分类为A且用户等级≥3时,才允许创建高价股票”);
- 涉及跨聚合的业务一致性规则(如“同一分类下股票总数不能超过100个”)。
此时应定义领域服务(Domain Service),它:
- 位于domain包下,接口与实现均不依赖基础设施;
- 接收实体/值对象为参数,返回值对象或抛出领域异常;
- 可协调多个聚合根,但所有判断依据必须来自领域模型内部状态。
// ✅ 领域服务示例(domain/service/StockCreationPolicy.java)
public interface StockCreationPolicy {
void validate(NewStockCommand cmd, Category category, User user); // 业务规则在此
}
// ✅ 应用层调用方式(解耦业务决策与流程)
@Override
public String addStock(NewStockCommand cmd) {
Category category = categoryRepo.findCategoryByName(cmd.getCategoryName());
User user = userRepo.findById(cmd.getUserId()); // 假设存在
// 委托给领域服务做业务判定
creationPolicy.validate(cmd, category, user); // 若不满足规则,抛出DomainException
Stock stock = new Stock(cmd.getName(), cmd.getValue(), category.getUuid());
stockRepo.save(stock);
return stock.getUuid();
}
四、总结:三层判断法,快速定位逻辑归属
面对任意一段业务代码,用以下问题逐层判断:
是否直接表达业务规则?
→ 是 → 必须在领域层(实体方法、领域服务、值对象工厂);
→ 否 → 进入下一步。是否涉及多个聚合根/外部系统的协调?
→ 是 → 属于应用层(流程编排、事务控制、DTO组装);
→ 否 → 进入下一步。是否纯粹技术实现(DB/Cache/MQ/HTTP)?
→ 是 → 归入基础设施层(Repository实现、FeignClient、RedisTemplate封装);
→ 否 → 重新审视是否遗漏了领域概念(可能需建模新值对象或实体)。
遵循此框架,即可避免“应用层臃肿”与“领域层贫血”的双重陷阱,让微服务真正具备业务可演进性与技术可替换性——这正是DDD在2026年依然被Google、阿里、腾讯等头部厂商作为微服务治理基石的根本原因。











