Как исправить эту циклическую зависимость, вызывающую пустой объект - PullRequest
1 голос
/ 19 июня 2019

Я создаю серверную часть приложения, используя узел и экспресс.

Я разделил разные части кода в разных файлах: например, все, что касается доступа к базе данных, находится в файле DBService.js, и если я хочу выполнить какое-либо действие, связанное с моими пользователями, у меня есть файл UserService.js, который выполняет все приложение нуждается в пользователях и использует DBService.js для сохранения пользователей в БД.

Я знаю, что в моем коде есть некоторые циклические зависимости, но до сих пор все работало нормально. Я использую GraphQL практически для всего, но добавляю обычную конечную точку для захвата файла, учитывая его идентификатор.

Мне требуется FileService.js в index.js (точка входа в приложение узла) для обслуживания файла, и эта часть работает хорошо. Проблема в том, что в другом файле (ZoneService.js), где мне также требуется FileService.js, он возвращает пустой объект.

Я точно знаю, что это проблема, потому что, если я уберу требование в файле index.js, проблема исчезнет.

Это пути, которые ведут к круговым зависимостям. '->' означает, что предыдущий Сервис требует следующего.

FileService -> ZoneService -> FileService

FileService -> ZoneService -> FileUploadService -> FileService

Это может выглядеть глупо, но я нуждаюсь в этом, потому что я подумал, что было бы неплохо сохранить определения и графики для каждого типа сущности graphQL в своем собственном файле.

Я попытаюсь объяснить мои рассуждения о первом пути:

  • Я хочу получить файлы из определенной зоны, поэтому эта функция переходит в FileService. Затем я использую ZoneService для получения идентификатора файла с указанным идентификатором зоны, затем получаю пути из БД
  • ZoneService необходим FileService для разрешения поля «файлы» в объекте зоны

Я мог бы просто переместить эту функцию в 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)
    }
  },
}

Ответы [ 2 ]

3 голосов
/ 21 июня 2019

Пара dos и don'ts :

  • Do разбивают вашу схему на более мелкие модули.Для большинства схем имеет смысл разделить определения типов и распознаватели по нескольким файлам, группируя связанные типы и поля Query / Mutation.Определители и определения типов могут быть экспортированы из одного файла, или определения типов могут находиться в файле самостоятельно (возможно, в виде простого текстового файла с расширением .gql или .graphql).(Примечание: Заимствуя терминологию Аполлона, я буду ссылаться на определения и распознаватели связанных типов как модуль ).
  • Не вводить зависимости между этимимодули.Резольвер должен работать независимо друг от друга.Нет необходимости вызывать один распознаватель внутри другого - и, конечно, нет необходимости вызывать один модуль распознавания из другого модуля.Если между модулями есть некоторая общая логика, извлеките ее в отдельную функцию и затем импортируйте ее в оба модуля.
  • Do держите уровень API отдельно от уровня бизнес-логики.Сохраняйте бизнес-логику, содержащуюся в ваших классах модели данных, и не допускайте ваших резольверов в эти классы.Например, ваше приложение должно иметь модель Zone или ZoneService или ZoneRepository, которая содержит такие методы, как getZoneById.Этот файл должен не содержать какие-либо средства распознавания и должен вместо этого быть импортирован вашими модулями схемы.
  • Do использовать контекст для внедрения зависимостей.Любые модели данных, сервисы и т. Д., К которым ваши распознаватели должны иметь доступ, должны вводиться с использованием контекста.Это означает, что вместо непосредственного импорта этих файлов вы будете использовать параметр context для доступа к нужному ресурсу.Это упрощает тестирование и обеспечивает однонаправленный поток зависимостей.

Итак, подведя итог вышесказанному, структура вашего проекта может выглядеть примерно так:

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)
    },
  },
}
0 голосов
/ 19 июня 2019

Лучше избегать циклических зависимостей.
Простой способ сделать это - разделить ваш модуль на меньшие модули.
Как

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 или что-то альтернативное

...