领域中的服务表示一个无状态的操作,它用于实现特定于某个领域的任务。 当某个操作不适合放在聚合和值对象上时,最好的方式便是使用领域服务。有时我们倾向于使用聚合根上的静态方法来实现这些这些操作,但是在 DDD中,这是一种坏味道。
早期在Product中维护了一个Backlogitem实例的集合。这种建模方式可以计算一个Product的总业务优先级:
当时这种设计方式非常完美,businessPriorityTotals只需遍历所有 Backlogitem实例,计算出总业务优先级。并且,这种方式适当地使用了值对象 BusinessPriorityTotals。
但我不这么认为,通过对聚合的分析我们知道,这里的Product对象过于庞大,而Backlogitem本身就应该成为一个聚合。因此,上面的实例方法businessPriorityTotals已不再适用于这种场景。
由于Product不再包含Backlogitem集合,团队成员们的第一反应便是使用一个资源库 BacklogltemRepository来获取所需的Backlogitem实例,这是一种好的做法吗?
事实上,团队中的高级开发者并不建议这么做。一个基本原则:应尽量避免在聚合中使用资源库。那么,将businessPriorityTotals()方法声明为静态方法,然后将 Backlogitem集合作为参数传入,如何?
这样,几乎不用对该方法做多少修改,只需传入新参数:
Product是创建该静态方法的最佳位置吗?
看来要将该方法放在合适的地方并非易事。由于该方法只使用了每个Backlogitem中的值对象,将该方法放在Backlogitem似乎更合适。但这里计算所得的业务价值却属于Product而非Backlogitem,进退维谷!
团队中的高级开发者发话了。他指出:这些问题用一个单一的建模工具即可解决,即领域服务(Domain Service)。
那领域服务是如何工作的?
什么是领域服务
什么不是领域服务?
听到“服务”,自然想到一个远程客户端与某复杂业务系统交互,该场景基本描述了SOA中的一个服务。有多种技术和方法可以实现SOA服务,最终这些服务强调的都是系统层面的
远程过程调用(RPC)或
消息中间件(MQ)
这些技术使得我们可通过服务与分布在不同地方的系统进行业务交互。
以上这些都不是领域服务。
不要将领域服务与应用服务混淆:
应用服务并不会处理业务逻辑
但领域服务恰恰是处理业务逻辑。应用服务是领域模型很自然的客户,也是领域服务的客户。
虽然领域服务中有“服务”这个词,但它并不意味着需要远程的、重量级的事务操作。
何时应该使用领域服务
领域服务到底是什么?当领域中的某个操作或转换过程不是实体或值对象的职责时,此时便应该将该操作放在一个单独的接口,即领域服务。
请确保该领域服务和通用语言是一致的;并且保证它是无状态的。
通常领域模型主要关注特定于某个领域的业务。同样,领域服务也具有相似特点。由于领域服务有可能在单个原子操作中处理多个领域对象,这将增加领域服务的复杂性。
有时,当与另一个限界上下文交互时,领域服务的确需要进行远程操作,但此时重点并非将领域服务作为一个服务提供方,而是将其作为RPC的客户端。
那么何时一个操作不属于实体或值对象?即何时可使用领域服务:
执行一个显著的业务操作过程
对领域对象进行转换
以多个领域对象作为输入进行计算,产生一个值对象结果
计算过程应该具有“显著的业务操作过程”。这也是领域服务很常见的应用场景,它可能需要多个聚合作为输入。
当一个方法不便放在实体或值对象,使用领域服务便是最佳的解决方案。
确定需要领域服务?
请不要过于倾向将一个领域概念建模成领域服务,只有在有必要时才这么做。领域服务不是“银弹”。
过度使用领域服务将导致贫血领域模型,即所有业务逻辑都位于领域服务中,而非实体和值对象。