我正在使用node和express构建应用程序的后端。
我在不同的文件中分离了代码的不同部分:例如,与访问数据库有关的所有内容都在文件DBService.js中,并且如果我想执行与用户相关的任何操作,我可以使用UserService.js文件该应用需要用户,并使用DBService.js将用户保存在数据库中。
我知道我的代码中确实有一些循环依赖项,但到目前为止一切正常。我正在使用GraphQL进行几乎所有操作,但是我添加了一个普通终结点以根据ID来抓取文件。
我确实需要index.js(指向节点应用程序的入口)中的FileService.js来提供文件,并且这一部分工作良好。问题是在另一个我也需要FileService.js的文件(ZoneService.js)中,它返回一个空对象。
我知道这是问题所在,因为如果我删除index.js文件中的require,问题就会消失。
这些是导致循环依赖的路径。 “->”表示上一个服务需要下一个。
FileService -> ZoneService -> FileService
FileService -> ZoneService -> FileUploadService -> FileService
这看起来很愚蠢,但是我需要这样做,因为我认为将graphQL类型定义和每个实体的解析器保留在自己的文件中是一个好举动。
我将尝试解释我的第一个路径的原因:
我可以将该函数移至ZoneService并从那里获取文件,但是会破坏我分离关注点的所有逻辑。
我想知道的是解决此问题的最佳方法,这样它就不会再次发生,并且该如何避免。
我会发布一些代码,但我不确定该怎么办,如果您认为有必要让我知道。
谢谢!
编辑-这是一些代码:
FileService.js
//Import services to use in resolvers
const EditService = require("./EditService.js")
const ZoneService = require("./ZoneService.js")
//Resolvers
const resolvers = {
Query: {
getFileById: (parent, {_id}) => {
return getFileById(_id)
},
getFilesById: (parent, {ids}) => {
return getFilesById(ids)
},
getFilesByZoneId: (parent, {_id}) => {
return getFilesByZoneId(_id)
},
},
File: {
editHistory: file => {
return EditService.getEditsById(file.editHistory)
},
fileName: file => {
return file.path.split('\\').pop().split('/').pop();
},
zone: file => {
return ZoneService.getZoneById(file.zone)
}
}
}
ZoneService.js
//Import services to use in resolvers
const UserService = require("./UserService.js")
const FileService = require("./FileService.js")
const EditService = require("./EditService.js")
const ErrorService = require("./ErrorService.js")
const FileUploadService = require("./FileUploadService.js")
//Resolvers
const resolvers = {
Query: {
getZone: (parent, {_id, label}) => {
return _id ? getZoneById(_id) : getZoneByLabel(label)
},
getZones: () => {
return getZones()
},
},
Zone: {
author: zone => {
return UserService.getUserById(zone.author)
},
files: zone => {
if(zone.files && zone.files.length > 0) return FileService.getFilesById(zone.files)
else return []
},
editHistory: zone => {
return EditService.getEditsById(zone.editHistory)
}
},
Mutation: {
createZone: async (parent, input, { loggedUser }) => {
return insertZone(input, loggedUser)
},
editZone: async (parent, input, { loggedUser }) => {
return editZone(input, loggedUser)
},
removeZone: async (parent, input, { loggedUser }) => {
return removeZone(input, loggedUser)
}
},
}
答案 0 :(得分:3)
几个要做和不要:
.gql
或.graphql
的纯文本文件)。 (注意:借用Apollo的术语,我将把相关的类型定义和解析器称为 module )。Zone
模型,或者包含诸如ZoneService
之类的方法的ZoneRepository
或getZoneById
。该文件应不包含任何解析器,而应由您的模式模块导入。因此,综上所述,您的项目结构可能看起来像这样:
services/
zone-service.js
file-service.js
schema/
files/
typeDefs.gql
resolvers.js
zones/
typeDefs.gql
resolvers.js
您可以通过以下方式初始化服务器:
const FileService = require(...)
const ZoneService = require(...)
const server = new ApolloServer({
typeDefs,
resolvers,
context: () => ({
services: {
FileService,
ZoneService,
}
})
})
这意味着您的解析器文件不需要导入任何内容,而您的解析器将看起来像这样:
module.exports = {
Query: {
getFileById: (parent, {_id}, {services: {FileService}}) => {
return FileService.getFileById(_id)
},
getFilesById: (parent, {ids}, {services: {FileService}}) => {
return FileService.getFilesById(ids)
},
getFilesByZoneId: (parent, {_id}, {services: {FileService}}) => {
return FileService.getFilesByZoneId(_id)
},
},
}
答案 1 :(得分:0)
为了更好,您应该避免循环依赖。
一种简单的方法是将模块分成较小的模块。
为
FileService -> CommonService
ZoneService -> CommonService
或
FileServicePartDependsOnZoneService -> ZoneService
ZoneService -> FileServicePartNotDependsOnZoneService
或
FileService -> ZoneServicePartNotDependsOnFileService
ZoneServicePartDependsOnFileService -> FileService
请注意这是示例。您应该将模块命名为有意义的,并且比我的示例短。
另一种方法是将它们合并在一起。 (但这可能不是一个好主意)
如果无法避免循环依赖。您也可以在需要时使用require
模块来代替import
。
例如:
//FileService.js
let ZoneService
function doSomeThing() {
if(!ZoneService) {
ZoneService = require("./ZoneService.js").default
//or ZoneService = require("./ZoneService.js")
}
//using ZoneService
}
对于可重用,请定义函数getZoneService
或其他替代方法