DDD 领域服务与应用服务
什么时候服务需要放到领域层,又什么时候需要放到应用层?
#synthesis
#growing
#architecture
#ddd
[!info] related notes
DDD 领域服务与应用服务
领域服务表达不自然属于某个实体或值对象的业务规则;应用服务编排一个用例的执行。不能仅凭“注册、登录、销户”这些动作名称,或是否写数据库,决定整段流程属于哪一层。
判断职责
| 问题 | 常见归属 |
|---|---|
| 单个聚合能否进行某次状态变化 | 聚合自身的领域行为 |
| 一条业务规则无法自然归属实体/值对象 | 领域服务或领域策略 |
| 加载模型、调用规则、提交事务、协调外部能力 | 应用服务 |
| 数据库访问、密码哈希、邮件传输的具体实现 | 基础设施适配器 |
应用服务可以执行用例级权限与流程控制,但不应复制领域不变量。领域服务可以依赖必要的抽象接口,不表示应直接耦合 ORM、Redis 或邮件 SDK。跨多个聚合不是自动建立一个“大领域服务”的理由,还要明确事务与一致性边界。
注册、登录、登出与销户
- 注册:应用服务组织输入、哈希和持久化;身份创建、邀请码规则等按模型归属实体或领域策略。邮箱是否已存在的查询只是预检查,并发唯一性还需数据库约束及冲突映射。
- 登录:应用服务协调凭证验证与会话签发;锁定条件等规则可在领域模型中表达,哈希比对由适配器实现。不能因为它重要就把完整用例都称为领域服务。
- 登出:可能包含会话撤销、令牌失效与在线状态变化。写了登出时间不自动意味着领域服务;是否有领域规则取决于业务模型。仅删除客户端 Token 不证明服务端会话已撤销。
- 销户:应用层编排资格检查、身份状态转换与跨上下文协作。欠款、未完成订单等规则由其所属上下文提供可靠结果,不能让账号领域模型任意直读其他模块内部表。
注册用例如何协作
应用入口认证/校验请求
→ 创建领域身份,执行注册规则
→ 通过适配器计算密码哈希
→ 在事务中保存身份与必要 outbox 事件
→ 唯一约束冲突映射为可处理的注册冲突
→ 提交后返回注册结果
→ 后台可靠发送欢迎邮件,失败按任务协议恢复
这只是职责示意,顺序按实际业务前置条件调整。密码应哈希而非可逆加密;示例不构成完整认证方案。欢迎邮件若不决定注册是否成立,不应在身份已提交后因邮件异常把请求伪装成“注册未发生”,也不能靠普通 fire-and-forget 保证邮件必达。
原笔记中的 libs/modules/auth/domain/services 与 commands/register.handler.ts 是一种目录示例,不是 DDD 强制布局。文件名和 service 后缀不能代替职责判断。
易混淆点
- 领域服务不等于所有核心业务代码:实体和值对象也承载规则。
- 应用服务不等于 HTTP Controller:同一用例可由 HTTP、CLI 或作业入口调用。
- 持久化在哪里调用与事务由谁控制需要明确约定,没有“写数据库就属于领域层”的规则。
- 跨上下文不等于跨网络;模型边界与部署边界分别设计。