领域服务
有时,它不见得是件东西。 — Eric Evans
领域中的服务表示一个无状态的操作,它用于实现特定于某个领域的任务。 当某个操作不适合放在聚合(10)和值对象(6)上时,最好的方式便是使用领域服务了。有时我们倾向于使用聚合根上的静态方法来实现这些这些操作,但是在DDD中,这是一种坏味道。
本章路线图
- 学习如何在领域模型中使用领域服务。
- 学习什么是领域服务。
- 学习何时应该使用领域服务。
- 从SaaSOvation项目的两个例子中学习如何对领域服务进行建模。
坏味道?这正是SaaSOvation的开发者们在重构聚合时所遇到的问题。让我们 看看他们是如何应对这些问题的...
在项目的早些时候.项目成员们在Product中维护了一个 Backlogltem实例的集合。这种建模方式使得他们可以计算一个Product的总业务优先级:
public class Product extends ConcurrencySafeEntity {
...
private Set<BacklogItem> backlogItems;
...
public BusinessPriorityTotals businessPriorityTotals() {
...
}
...
}
在当时.这种设计方式是非常完美的,businessPriorityTotals()方法只需要遍历所有 的Backlogltem实例.然后计算出总的业务优先级。并且.这种方式适当地使用了值对象BusinessPriorityTotals。
但是.我却不这么认为。通过对聚合(10)的分析我们知道.这里的Product对象过于庞 大.而Backlogltem本身就应该成为一个聚合。因此.上面的实例方法businessPriorityTotalsU已经不再适用于这种场景。
由于Product不再包含Backlogltem集合.团队成员们的第一反应便是使用一个资源库 BacklogltemRepository来获取所需的Backlogltem实例.这是一种好的做法吗?
事实上.团队中的高级开发者并不建议这么做。一个基本的原则是.我们应该尽量避免 在聚合中使用资源库(12)。那么.将businessPriorityTotalsO方法声明为静态方法.然后将Backlogltem集合作为参数传入,如何呢?这样,我们几乎不用对该方法做多少修改.只需要传入新的参数即可:
public class Product extends ConcurrencySafeEntity {
...
public static BusinessPriorityTotals businessPriorityTotals(
Set<BacklogItem> aBacklogItems) {
...
}
...
}
请思考.Product是创建该静态方法的最佳位置吗?看来要将该方法放在合适的地方并 不是一件易事。由于该方法只使用了每个Backlogltem中的值对象.将该方法放在Backlogltem上似乎更合适。但是.这里计算所得的业务价值却是属于Product的,而不是Backlogltem。进退两难啊!
此时.团队中的高级开发者发话了。他指出.这些问题用一个单一的建模工具就可以解 决.即领域服务(Domain Service)。那么,领域服务是如何工作的呢?
让我们先了解一些背景知识,再回过头来看看这个建模场景,看看 SaaSOvation团队做了什么样的决定。