关于 ddd-domain-service
用于判断领域行为何时应由无状态领域服务承担,而非实体或其他层。它区分领域操作、应用层编排与基础设施工作。
主要功能
- 01领域服务的适用条件
- 02区分三种服务层
- 03领域操作命名建议
- 04状态与职责范围判断
- 05常见服务反模式
适用场景
- 01协调跨聚合操作
- 02承载无自然实体归属的计算
- 03区分用例编排与领域逻辑
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ yonatankarp/ software-design-skills --skill ddd-domain-service安装后在 Claude Code 中输入/ddd-domain-service确认已加载。
详细介绍
一句话概述
领域服务是领域层中的无状态操作,无法自然地归属于某个单独的实体或值对象——通常是因为它需要协调多个聚合,或涉及不以某个对象为核心的领域计算。
何时使用此技能
- 一个内聚的领域操作涉及多个聚合(例如,在两个
Account聚合之间转账)。 - 某项计算涉及领域类型,但不适合作为其中任何一个类型的方法(例如,已知汇率时进行货币兑换)。
- 将行为强行放到某个参与实体上,会导致该实体对其他实体“了解过多”。
何时不使用此技能
- 逻辑可以清晰地归属于单个实体——将其放在那里。
- 工作属于应用层编排(处理用例、开启事务、调用存储库)——这是应用服务,而非领域服务。
- 工作属于基础设施(HTTP 调用、数据库访问、发送消息)——这是基础设施服务。
核心内容
DDD 代码库中通常会共存三种服务。请将它们区分开来:
领域服务。 位于领域层。只操作领域类型。无状态。用通用语言中的动词来表达——TransferService.transfer(fromAccount, toAccount, amount),而不是 AccountUtils.handleTransfer(...)。
应用服务。 编排用例。开启事务、调用存储库、调用领域服务、分发事件。无状态。位于应用层。
基础设施服务。 与外部世界交互——发送 HTTP 请求、写入数据库、向队列发布消息。位于基础设施层。
领域服务应根据其在领域中的作用命名。TransferService 和 ExchangeRateConverter 是不错的名称。TransferManager 和 TransferHelper 则是不良信号——它们用技术性噪声掩盖了动词。
判断准则
- 优先采用实体行为。只有当操作涉及多个聚合或没有自然归属时,才考虑使用领域服务。
- 如果领域服务有状态,它几乎肯定被错误归类了——应将其改为领域实体、值对象,或把状态移入某个参与聚合。
- 如果领域服务扩展到几十个方法,它很可能是伪装的“事务脚本”——拆分它,或将行为放回实体中。
反模式
- 每个类都添加
*Service后缀——领域会变成一组针对数据 DTO 的事务脚本(参见ddd-anti-patterns中的贫血领域模型)。 - 在领域服务中放入基础设施(数据库、HTTP)——这会使领域与部署问题耦合。
- 领域服务不通过实体的方法就直接修改传入的实体——这会绕过不变量。
- 持有缓存、会话或其他进程状态的有状态“服务”。
另请参阅
ddd-entity— 应优先将行为放在这里。ddd-aggregate— 领域服务通常会跨聚合进行协调。ddd-anti-patterns— 贫血模型和服务膨胀的不良信号。
参考资料
- Evans,Domain-Driven Design(2003),第 5 章(以软件表达的模型)。
评论
支持 Markdown