
本文详解ddd四层架构中应用层与领域层的核心分工,重点厘清“跨聚合数据获取”“业务规则归属”等高频争议点,通过代码示例与原则对照,帮助开发者在微服务中正确落地领域驱动设计。
本文详解ddd四层架构中应用层与领域层的核心分工,重点厘清“跨聚合数据获取”“业务规则归属”等高频争议点,通过代码示例与原则对照,帮助开发者在微服务中正确落地领域驱动设计。
在领域驱动设计(DDD)的实践中,一个反复出现的困惑是:当业务流程需要关联多个聚合(如Stock与Category)、并调用外部系统(如FinHub行情接口)时,这类协调性逻辑究竟该放在哪一层? 上述示例中的 StockService 看似“做了很多事”,但它是否违背了DDD的核心原则?答案是否定的——只要严格遵循分层契约,它恰恰是应用层(Application Layer)的典型、合规职责。
✅ 应用层的正当使命:编排,而非决策
DDD官方四层架构(Interface → Application → Domain → Infrastructure)中,应用层唯一且不可替代的职责是“用例编排”(Use Case Orchestration)。它不包含业务规则,但必须协调以下要素:
- 调用多个仓储(Repository)获取不同聚合的实例;
- 调用基础设施层封装的外部适配器(如 FinhubPort);
- 组装DTO或响应对象(如 StockWithExternalData);
- 控制事务边界与领域事件发布。
示例中两处关键操作完全符合这一定位:
// ✅ 合规:应用层协调Category聚合,仅为获取其ID或名称,不参与“分类有效性”等业务判定 final Category category = categoryRepo.findCategoryById(existingStock.getCategoryId()); // ✅ 合规:调用外部行情端口获取实时价格,属技术集成,非领域规则 final BigDecimal externalPrice = finhubPort.getStockPriceByName(existingStock.getName());
这里没有计算折扣、不校验库存阈值、不决定订单状态流转——所有这些业务本质必须留在领域层。应用层只回答一个问题:“为了完成这个用例,我需要哪些数据?从哪里拿?怎么拼起来?”
⚠️ 领域层的铁律:纯业务、零技术、强内聚
领域层(Domain Layer)是DDD的“心脏”,其存在意义是将业务规则从技术细节中彻底剥离。它必须满足:
- 无框架依赖:不引入 Spring、MyBatis、Redis 等任何基础设施类;
- 无跨聚合引用:聚合根(Aggregate Root)只能持有本聚合内实体/值对象的直接引用,对外部聚合仅保留ID(如 stock.getCategoryId());
- 业务规则唯一出口:价格策略、库存扣减条件、状态合法性校验等,必须实现在实体、值对象或领域服务中。
例如,若业务规则要求“股票所属分类必须启用且未过期”,则验证逻辑应位于 Category 实体的 isValidForStock() 方法中,并由领域服务(Domain Service)在需要跨聚合协作时调用——而非在应用层写 if (category == null || !category.isActive())。
? 何时需要领域服务?——复杂业务逻辑的“安全区”
当应用层编排逻辑开始出现多条件分支、嵌套调用、或需复用核心规则时,便是领域服务(Domain Service)的介入时机。例如:
// ❌ 反模式:应用层混入业务规则判断
if (category == null) {
throw new InvalidCategoryException("Category not found");
}
if (!category.isTradable()) { // ❌ 业务规则侵入应用层!
throw new CategoryNotTradableException();
}
// ✅ 正确:将规则封装进领域服务,应用层仅调用
stockDomainService.validateCategoryEligibility(existingStock.getCategoryId());
领域服务属于领域层,但实现可置于基础设施层(遵循依赖倒置),其方法签名应使用领域概念(如 CategoryId、StockId),而非技术类型(如 String、Long)。
? 关键总结:三句口诀守住架构底线
- “外层调内层,内层不识外”:应用层可调用领域层和基础设施层;领域层绝对不可依赖应用层或Infrastructure中的具体实现(如 JdbcTemplate)。
- “规则在Domain,流程在Application”:canPlaceOrder() 在领域层;placeOrder() 的步骤顺序(查库存→创建订单→扣减→发事件)在应用层。
- “ID是桥梁,聚合是孤岛”:Stock 持有 categoryId: String 是合理的;但绝不允许 Stock 直接持有 Category 实体引用——这会破坏聚合边界与一致性。
遵循这些原则,你的 StockService 不仅“allowed”,更是DDD落地的范本。真正的风险不在于做了什么,而在于为什么这么做——当每一次代码落笔前都回归到“这是业务本质,还是技术实现?”的叩问,DDD便不再是教条,而成为守护系统长期健康的工程自觉。











