将服务再次注入到领域对象中

4
我有一个特定的领域,涉及地理数据。我正在使用TypeScript和NodeJS实现这个项目,并拥有以下类:
- Point - 值对象,包含纬度和经度 - Area - 值对象,包含一组点作为形状定义 - Sector - 实体(不持久化,但可变),包含区域和位于其中的一组点
现在,我需要实现一个名为isPointInside(point:Point)的方法,该方法计算提供的点是否适合区域内部。我不想自己实现它,因为有许多库可以帮助我完成。现在我决定使用这个:https://github.com/manuelbieh/Geolib
此外,我正在使用名为inversifyjs的TypeScript IoC框架,它通过注释提供DI。
所以我创建了一个名为“GeoService”的接口,提供负责地理计算的方法,并打算在该领域模型中大量使用它。使用inversify,我提供了实现我的接口的GeoLib适配器,并且我真的很想从我的Area类内部以某种方式使用它。
这是第一个问题,但这并不是我的所有难题 :-)
我还有另一个类,称为SectorGrid,其中包含两维数据结构中的扇区网格(每个扇区在此用例中都是正方形)。 SectorGrid具有addPoint(point:Point)方法。该方法的职责是查找提供的点适合的扇区,如果找不到,则创建一个扇区。现在它不仅需要GeoService来计算扇区初始点应该在哪里(距离网格中心的距离),而且还需要创建Sector-在编写测试时我遇到了问题,因为存在太多的逻辑,我决定向SectorGrid提供某种扇区工厂,不仅为了简化测试,而且为了封装扇区创建逻辑(这相当复杂)。现在需要注入另一个服务,但不知道如何以不会导致问题的方式执行它。

目前,我只是将这两个服务作为这些类的静态属性注入,但我并不为此感到自豪,我正在寻找其他选择。

我知道我的设计可能会过于复杂,我正在寻找一种简化它的方法,但我不想陷入无力的领域模型中。拥有像区域对象这样的东西,却无法在其中放置地理计算,对我来说听起来确切地像贫血模型。

此外,我已经阅读了很多关于向实体注入服务的讨论,但它们都没有令我满意,因为它们要么提供了像“不要这样做”或“只需这样做而不烦恼”这样的结论,要么提供了像领域事件这样完全不适合我情况的解决方案。


为什么不使用函数注入(回调风格)?而且你只提供了症状,没有代码,所以我们不知道你的服务是做什么的,为什么会过于复杂。 - rad
老实说,我认为代码不会比现在更清晰 - 代码太多了(计算扇区位置很复杂),而且我仍在发现我的领域,因此一些类包含太多职责,您可能看不到其中的内容。注入回调函数如何比仅注入以描述方式计算所有事物的对象更好?此外,我不仅需要单个操作,还需要几个操作(方位、距离、点-区域、基于方位和距离的目标点计算等)。 - Mikz
如果所有复杂逻辑都发生在基础设施组件中,那么您实际上根本不需要领域模型。贫血只有在声称实现领域模型时才是不好的,但如果正确使用功能方法,则可以同样有效。如果每个方法调用都需要外部服务,并且聚合唯一要做的就是完全将逻辑委托给该服务,那么您的模型仍然是贫血的,它只是没有假定它是贫血并将其隐藏起来。 - plalx
实际上,基础设施中没有复杂的逻辑。我仅仅依赖它进行地理空间计算。这些计算从数学角度看很复杂,但并没有业务逻辑在其中。至于我提到的工厂 - 它是领域的一部分,但它是封装该实体不负责的复杂逻辑的一种方便方式。 - Mikz
抱歉,我不能再编辑我的评论了,所以我会发另一个。我正在开发的项目是一款增强现实游戏,类似于Ingress或Pokémon Go,相信我,在其中有足够的业务逻辑可以创建一个非常复杂的领域模型。 - Mikz
1个回答

2
处理域对象与服务协作的常见方法是在方法级别注入这些服务,并应用ISP原则确保依赖关系不会超出必要范围。
例如,addPoint(point: Point, geoService: GeoService)。
处理该问题的另一种常见方法是从应用程序服务中解析依赖项并将结果传递到聚合方法中,但当该方法泄漏太多逻辑到应用层时,您可能应该改用方法级别的服务注入。
想象一下,一个Project聚合必须根据其链接的Task聚合以事件一致的方式调整其完成状态和百分比。为此,Project必须找出目前已完成多少个任务。
您可以在应用程序层次解决依赖关系,而不是将TaskRepository/TaskCompletionSummaryProvider传入Project.adjustCompletionState方法。
var project = projectRepository.projectOfId(someProjectId);
var taskCompletionSummary = taskRepository.taskCompletionSummaryOfProject(project.id());
project.adjustCompletionState(taskCompletionSummary);


class Project {
    public void adjustCompletionState(TaskCompletionSummary summary) {
        //The following line could be seen as defensive programming. You could also trust that the application layer is doing it's job correctly. It wouldn't be required at all if a `TaskCompletionSummaryProvider` service would be injected directly instead.
        if (this.id != summary.projectId()) throw new InvalidOperationException('Wrong summary for project');

        if (summary.allCompleted()) this.completionState = ProjectCompletionState.COMPLETED;
        else this.completionState = ProjectCompletionState.inProgress(summary.completionPercentage());
    }
}

谢谢,这是迄今为止最有帮助的答案,但您能否再详细解释一下后面的解决方案? - Mikz
再次感谢,我很讨厌在领域层中没有干净的方法来封装额外的依赖项,即使领域本身就是依赖它们的设计,但我也理解不这样做的原因,所以我想我只能接受并调整我的设计 :) - Mikz
@Mikz 在大多数领域中,领域模型不需要不断使用外部领域服务,因此可能会减少痛苦。 - plalx

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接