再次将服务注入域对象

时间:2016-10-31 16:57:33

标签: node.js typescript dependency-injection domain-driven-design

我有一个针对地理数据运营的特定域名。我在TypeScript和NodeJS中实现这个项目,并有以下类:

  • - 包含纬度和经度的值对象
  • 区域 - 包含一组点作为形状定义的值对象
  • 扇区 - 实体(它不是持久的,但它是可变的) - 包含其中的区域和一组点

现在我需要实现一个名为isPointInside(point: Point)的方法来计算提供的点是否适合区域内部。我不想自己实现它,因为有些库会为我做这件事。现在我决定使用那个:https://github.com/manuelbieh/Geolib

此外,我正在使用名为inversifyjs的TypeScript的IoC框架,没什么特别的 - 通过注释提供DI。

所以我创建了一个名为“GeoService”的接口,它提供了对地理计算有效的方法,我打算在该域模型中大量使用它。使用inversify我提供了实现我的接口的GeoLib适配器,我真的想在我的Area类中以某种方式使用它。

这是第一个问题,但这不是我斗争的结束: - )。

我有另一个类,名为 SectorGrid ,它包含二维数据结构中的扇区网格(每个扇区是此用例中的正方形)。 SectorGrid有一个方法addPoint(point: Point)。该方法的责任是找到一个提供点拟合的扇区,如果找不到,则创建它。现在它不仅需要GeoService来计算扇区的初始点应该是什么(距离网格中心的距离),而且还需要创建扇区 - 在我编写测试时我遇到了问题,逻辑太多了我我们决定为SectorGrid提供某种扇区工厂,不仅简化了测试,而且还封装了扇区创建逻辑(这非常复杂)。所以现在注入另一种服务,不知道如何以一种不会引起我问题的方式来做。

现在我只是将这两个服务注入这些类的静态属性,但这并不是我引以为傲的,我正在寻找其他选项。

我知道我的设计可能过于复杂,我正在寻找一种简化它的方法,但我不想在贫血领域模型领域结束。拥有像Area对象这样的东西并且不能将地理计算放在其中听起来就像是我的贫血模型。

此外,我已经阅读了很多关于向实体注入服务的讨论,但他们都没有满足我,因为他们要么得出结论,如“不要这样做”或“只做这个而且不要打扰”,或提供类似域事件的解决方案,完全不符合我的情况。

1 个答案:

答案 0 :(得分:2)

处理域对象需要与服务协作的情况的常用方法是在方法级别注入这些服务,并应用ISP原则以确保依赖性不会超出所需的范围。

E.g。 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());
    }
}