Узел, требующий модуля до его раскрытия, вызывает неопределенность

В конечном итоге я пытаюсь потребовать var server = require('../../index.js'); в моем файле foo-dao.js, чтобы я мог получить доступ к подключаемому серверу hapi без необходимости передавать его через объект hapi request от контроллера к dao.

Проблему можно увидеть в комментариях к моему методу foo-dao.js при попытке потребовать index.js в верхней части файла.

Я считаю, что проблема в том, что в моем index.js требуется папка ./modules, для которой требуется ./modules/foo/foo-routes.js, для которой требуется ./modules/foo/foo-ctrl.js, для которой требуется ./modules/foo/foo-dao.js.

Вот упрощенное изображение требований

./modules/index.js -> ./modules/foo/foo-routes.js -> ./modules/foo/foo-ctrl.js -> ./modules/foo/foo-dao.js -> ./ modules/index.js ‹-- ПРОБЛЕМА ЗДЕСЬ, ПОТОМУ ЧТО МОЯ СЕРВЕРНАЯ ПЕРЕМЕННАЯ В INDEX.JS ЕЩЕ НЕ БЫЛА ОТКРЫТА.

/хапи/index.js

/**
 * Hapi.js server.
 *
 * @type {exports}
 */
var Hapi = require('hapi');
var modules = require('./modules');

// Instantiate the server
var server = new Hapi.Server('0.0.0.0', 4445, {cors: true, debug: {request: ['error']}});

...

/**
 * Add all the modules within the modules folder
 */
for(var route in modules) {
  server.route(modules[route]);
}

/**
 * Expose the server's methods when used as a require statement
 *
 * @type {exports.server}
 */
module.exports = server;

/хапи/модули/foo/foo-routes.js

var Joi = require('Joi');
var fooController = require('./foo-ctrl');

module.exports = function() {
  return [
    {
      method: 'GET',
      path: '/api/foo',
      config: {
        handler: fooController.foo//,
      }
    }
  ]
}();

/хапи/модули/foo/foo-ctrl.js

var fooDao = require('./foo-dao');

module.exports = function() {

  return {

    foo: function foo(req, reply) {

      fooDao.findFoo(function(err, data) {

        if (err) {
          return reply(Boom.badImplementation(err));
        }

        reply(data);
      });
    }
  }
}();

/хапи/модули/foo/foo-dao.js

var server = require('../../index.js');  //  WHEN I REQUIRE THE FILE HERE, IT'S UNDEFINED, PROBABLY BECAUSE THE server OBJECT HAS NOT BEEN EXPOSED YET

console.log('server = ');
console.log(server);

module.exports = function() {

  return {

    findFoo: function findFoo(callback) {
      var server = require('../../index.js');  //  WHEN I REQUIRE THE FILE HERE, IT'S ACTUALLY DEFINED, PROBABLY BECAUSE THE server OBJECT HAS BEEN EXPOSED BY THIS POINT. I DON'T WANT TO HAVE TO REQUIRE INDEX.JS IN EVERY SINGLE FUNCTION THOUGH. HOW CAN I CIRCUMVENT THIS PROBLEM?
      ... get data and return it in the callback
    }
  }
}();

person Catfish    schedule 21.08.2014    source источник
comment
Хорошо, так сделай его не круглым.   -  person Ry-♦    schedule 21.08.2014
comment
@minitech Гений! Почему я не подумал об этом? О, подождите, я определил проблему, и я прошу помощи в том, как не сделать ее круговой.   -  person Catfish    schedule 21.08.2014
comment
Что ж, либо каждая часть одного компонента требует для функционирования всех частей другого, что невозможно, либо вы можете разбить его на несколько модулей, либо потребовать более конкретных модулей, чем индекс, если вы уже выполнили первый шаг.   -  person Ry-♦    schedule 22.08.2014
comment
не совсем понимаю..   -  person Catfish    schedule 22.08.2014


Ответы (1)


В конечном итоге я пытаюсь потребовать var server = require('../../index.js'); в моем файле foo-dao.js, чтобы я мог получить доступ к подключаемому серверу hapi без необходимости передавать его через объект запроса hapi от контроллера к dao.

Это явный признак плохого дизайна, от которого система CommonJS пытается вас спасти. Не берите подмодуль и сообщайте ему о более высокой связанной родительской модели. Это делает подмодуль непригодным для повторного использования и сильно связывает его с содержащим приложением, что противоположно тому, что вы хотите. Глубокие, повторно используемые модули должны получать то, что им нужно, через параметры и объекты запросов, а не за счет абстракций приложений более высокого уровня.

Вот что вы хотите сделать в своем foo-dao.js, по крайней мере, в качестве первого шага к решению вашей проблемы с циклическим требованием. Но даже помимо этого, я думаю, вам нужны более масштабные изменения в структуре вашего кода, чтобы сделать его чистым.

module.exports = function makeDao(server) {

  return {
    findFoo: function findFoo(callback) {
      //now via closure you have permanent access to the server,
      //without being coupled so tightly to it's exact filesystem location
    }
  }
};

Чтобы загрузить это, вам нужно передать экземпляр сервера сверху вниз, что в вашем случае modules.

var server = new Hapi.Server('0.0.0.0', 4445, {cors: true, debug: {request: ['error']}});
var modules = require('./modules')(server);

Это упростит тестирование кода вашего модуля (внедрение зависимостей) и повторное использование.

Я подробно расскажу о своем общем подходе в моем репозитории express_code_structure. В частности, обратите внимание, как appinstance передается маршрутам. Детали того, как это работает, меняются с помощью express4, и я уверен, что hapi.js отличается, но общие идеи, вероятно, все еще применимы.

person Peter Lyons    schedule 21.08.2014
comment
Я не знал, что в такой оператор require можно передавать переменные. Как бы я структурировал свой код по-другому, чтобы сделать его более чистым? Это было ОЧЕНЬ чисто, пока мне не понадобился доступ к серверу в моем методе dao, и единственная причина, по которой мне это нужно, - это доступ к плагину hapijs (который в конечном итоге я даже не могу оставить его в качестве плагина, поскольку он вызывает столько боли и реструктуризации) - person Catfish; 22.08.2014
comment
Я не. Посмотрите внимательно. Модуль экспортирует функцию. Внешний код требует модуля (который является функцией), вызывает эту функцию с некоторой конфигурацией, а затем использует значение, возвращенное в приложении. Этот шаблон очень распространен, и почти все промежуточное ПО (по аналогии с плагинами hapi) использует его. Например, если вы хотите ограничить размер загрузки, вы можете сделать var limitMiddleware = require('limiter')({size: 100}); - person Peter Lyons; 22.08.2014
comment
Мне трудно дать вам конкретный совет по макету FS и организации кода для вашей конкретной проблемы, но, хотя есть законное использование require('../, обычно это запах кода, указывающий на то, что вы еще не правильно разделили задачи. - person Peter Lyons; 22.08.2014
comment
Интересно. Что ж, мне очень интересно узнать об архитектуре плагина Hapi. Я знаю, как бы я сделал это без использования плагина Hapi, и это не создало бы циклической зависимости, но я просто пытался использовать архитектуру плагина, и это может быть тот случай, когда это просто не имеет смысла. - person Catfish; 22.08.2014
comment
Думаю, я не соглашусь с вами в том, что require('../ обычно — это запах кода. Все зависит от того, что вы хотите сделать. Например, у меня есть файл констант в корне моего проекта. Мне нужно использовать ../ для ссылки на этот файл. У меня также есть созданная мной библиотека, которая находится в моей папке root/lib. Если я работаю в root/controllers/module/someFile.js, я также должен использовать ../../lib. - person Catfish; 22.08.2014
comment
Справедливо. Это ваше мнение. Я думаю о модулях как об отделах в продуктовом магазине. Они должны быть автономными и иметь возможность преобразования из подкаталога в независимый модуль npm без серьезной операции. Я знаю, что TJ Holowaychuk выразил ... как запах кода, хотя я не могу найти цитату с помощью быстрого поиска в Интернете. Ничто из того, что вы сказали выше, не кажется мне ужасным, просто используйте его с умом. Я предпочитаю передавать вещи сверху, а не тянуться за ними. - person Peter Lyons; 22.08.2014
comment
У вас есть проект на github, который иллюстрирует ваш пример, на который я мог бы посмотреть? - person Catfish; 22.08.2014