正确避免循环依赖-NestJS

时间:2020-05-27 23:49:06

标签: typescript design-patterns dependency-injection graphql nestjs

说我有一个StudentService,其方法可以向学生添加课程,一个LessonService,其方法可以将学生添加课程。在我的课程和学生解决者中,我都希望能够更新本课程<--->学生关系。因此,在我的LessonResolver中,我有以下类似之处:

  async assignStudentsToLesson(
    @Args('assignStudentsToLessonInput')
    assignStudentsToLesson: AssignStudentsToLessonInput,
  ) {
    const { lessonId, studentIds } = assignStudentsToLesson;
    await this.studentService.assignLessonToStudents(lessonId, studentIds); **** A.1 ****
    return this.lessonService.assignStudentsToLesson(lessonId, studentIds); **** A.2 ****
  }

和我的StudentResolver

中的相反

上面的 A.1 A.2 之间的区别在于StudentService可以访问StudentRepository和{{1 }}可以访问LessonService-我认为这是牢牢分开的关注点。

但是,似乎LessonRepository必须导入StudentModule,而LessonModule必须导入LessonModule似乎是一种反模式。使用StudentModule方法可以解决此问题,但是在NestJS Documentation中提到如果可能,应避免使用此模式:

尽管应尽可能避免循环依赖, 不能总是这样做。 (这是其中一种吗?)

在使用DI时,这似乎应该是一个普遍的问题,但是我正在努力寻求一个明确的答案,即可以消除这种情况的可用选项,或者如果我偶然发现了不可避免的情况

最终目的是让我能够编写以下两个GraphQL查询:

forwardRef

1 个答案:

答案 0 :(得分:2)

最简单的方法可能是完全删除依赖关系,而是引入依赖于其他两个模块的第三个模块。 在您的情况下,您可以将两个解析器合并到一个单独的StudentLessonResolver中,该单独ResolverModule位于自己的模块中,例如async assign({ lessonId, studentIds }: AssignStudentsToLessonInput) { await this.studentService.assignLessonToStudents(lessonId, studentIds); return this.lessonService.assignStudentsToLesson(lessonId, studentIds); }

StudentModule

因此LessonModuleResolverModule现在是完全独立的,而 type AssignCallback = (assignStudentsToLesson: AssignStudentsToLessonInput) => Promise<void>; class LessonResolver { // and similar for StudentResolver private assignCallbacks: AssignCallback[] = []; // ... dependencies, constructor etc. onAssign(callback: AssignCallback) { assignCallbacks.push(callback); } async assignStudentsToLesson( @Args('assignStudentsToLessonInput') assignStudentsToLesson: AssignStudentsToLessonInput, ) { const { lessonId, studentIds } = assignStudentsToLesson; await this.lessonService.assignStudentsToLesson(lessonId, studentIds); **** A.2 **** for (const cb of assignCallbacks) { await cb(assignStudentsToLesson); } } } // In another module this.lessonResolver.onAssign(({ lessonId, studentIds }) => { this.studentService.assignLessonToStudents(lessonId, studentIds); }); this.studentResolver.onAssign(({ lessonId, studentIds }) => { this.lessonService.assignStudentsToLesson(lessonId, studentIds); }); 依赖于两者。不再循环了:)

如果由于某种原因需要两个解析器并使它们彼此更新,则可以使用事件或回调来发布更改。 然后,您将再次引入第三个模块,该模块侦听这些事件并更新另一个模块。

StudentModule

同样,您中断了周期,因为LessonModuleSubject<AssignStudentsToLessonInput>彼此都不了解,而您注册的回调保证了调用任何一个解析器都会导致两个服务都被更新。

如果您使用的是RxJS之类的反应式库,而不是手动管理回调,则应该使用LessonRepository来供解析器发布,并使用新引入的模块进行订阅。

更新

正如OP所建议的,还有其他选择,例如将两个存储库都注入两个服务中。但是,如果每个模块都包含存储库和服务,即如果您从LessonService导入LessonModulesetOffscreenPageLimit,则此方法将不起作用,因为您仍然对模块具有循环依赖性水平。 但是,如果学生与课程之间确实存在紧密的联系,那么您也可以将两个模块合并为一个模块,这样就不会有问题。

一个类似的选择是将第一个解决方案的单个解析器更改为直接使用存储库的服务。这是否是一个好选择,取决于管理商店的复杂性。从长远来看,您最好通过这项服务。

我在单个解析器/服务解决方案中看到的一个优势是,它提供了用于分配学生上课的单一解决方案,而在事件解决方案中,studentService.assignLessonToStudents和lessonService.assignStudentsToLesson实际上可以做完全相同的事情,因此不清楚应该使用哪个。