Хорошо, это было давно, и это популярный вопрос, поэтому я пошел дальше и создал репозиторий github для скаффолдинга с JavaScript-кодом и длинным README о том, как мне нравится структурировать приложение express.js среднего размера.
focusaurus / express_code_structure - это репозиторий с последним кодом для этого. Запросы на извлечение приветствуются.
Вот снимок README, так как stackoverflow не любит ответы просто по ссылке. Я сделаю некоторые обновления, так как это новый проект, который я буду продолжать обновлять, но в конечном итоге репозиторий github станет актуальным местом для получения этой информации.
Экспресс структура кода
Этот проект является примером того, как организовать веб-приложение express.js среднего размера.
Текущий, по крайней мере, экспресс v4.14 декабря 2016 года
Насколько велико ваше приложение?
Веб-приложения не являются одинаковыми, и, на мой взгляд, не существует единой структуры кода, которая должна применяться ко всем приложениям express.js.
Если ваше приложение маленькое, вам не нужна такая глубокая структура каталогов, как показано здесь. Просто будьте проще и вставьте несколько файлов .js
в корень вашего хранилища, и все готово. Вуаля.
Если ваше приложение огромно, в какой-то момент вам нужно разбить его на отдельные пакеты npm. В целом подход node.js предпочтителен для множества небольших пакетов, по крайней мере, для библиотек, и вы должны создать свое приложение, используя несколько пакетов npm, поскольку это начинает иметь смысл и оправдывает накладные расходы. Таким образом, по мере роста вашего приложения и того, что некоторая часть кода становится многократно используемой за пределами вашего приложения или становится чистой подсистемой, переместите его в собственный репозиторий git и превратите в отдельный пакет npm.
Итак, цель этого проекта - проиллюстрировать работоспособную структуру для приложения среднего размера.
Какова ваша общая архитектура
Существует много подходов к созданию веб-приложения, например
- MVC на стороне сервера в стиле Ruby on Rails
- Стиль одностраничного приложения в стиле MongoDB / Express / Angular / Node (MEAN)
- Базовый веб-сайт с некоторыми формами
- Модели / Операции / Представления / Стиль событий а-ля MVC мертв, пора ПЕРЕМЕЩАТЬ на
- и многие другие как текущие, так и исторические
Каждый из них хорошо вписывается в другую структуру каталогов. Для целей этого примера это просто строительные леса, а не полностью работающее приложение, но я предполагаю следующие ключевые моменты архитектуры:
- На сайте есть несколько традиционных статических страниц / шаблонов
- "Сайт" часть сайта разработана как одностраничный стиль приложения
- Приложение предоставляет браузеру API в стиле REST / JSON
- Приложение моделирует простой бизнес-домен, в данном случае это приложение автосалона
А как насчет Ruby on Rails?
На протяжении всего этого проекта будет темой, что многие идеи, воплощенные в Ruby on Rails и принятых «Соглашении о конфигурации» решениях, хотя они широко приняты и используются, на самом деле не очень полезны, а иногда являются противоположностью что рекомендует этот репозиторий.
Мое главное здесь заключается в том, что существуют основополагающие принципы организации кода, и на основе этих принципов соглашения Ruby on Rails имеют смысл (в основном) для сообщества Ruby on Rails. Тем не менее, просто бездумно придерживаясь этих соглашений, теряет смысл. Как только вы освоите основные принципы, ВСЕ ваши проекты будут хорошо организованы и понятны: сценарии оболочки, игры, мобильные приложения, корпоративные проекты, даже ваш домашний каталог.
Для сообщества Rails они хотят иметь возможность иметь одного разработчика Rails, переключающегося с одного приложения на другое, и быть знакомыми с ним каждый раз. Это имеет большой смысл, если у вас 37 сигналов или Pivotal Labs, и имеет свои преимущества. В мире серверного JavaScript общий идеал - это просто куда больше дикого запада, и у нас действительно нет проблем с этим. Вот так мы катимся. Мы к этому привыкли. Даже в express.js это близкий родственник Синатры, а не Rails, и получение соглашений от Rails обычно ничего не помогает. Я бы даже сказал Принципы по соглашению по конфигурации .
Основные принципы и мотивы
- Быть умственно управляемым
- Мозг может иметь дело только с небольшим количеством связанных вещей и думать о них одновременно. Вот почему мы используем каталоги. Это помогает нам справляться со сложностью, сосредотачиваясь на небольших порциях.
- Будьте подходящего размера
- Не создавайте «Каталоги Mansion», где есть только один файл, один на три каталога вниз. Вы можете увидеть это в Ansible Best Practices , который вынуждает небольшие проекты создавать более 10 каталогов для хранения более 10 файлов, когда 1 каталог с 3 файлами будет гораздо более подходящим. Вы не едете на работу на автобусе (если вы не водитель автобуса, но даже если вы ездите на автобусе, AT не работает), поэтому не создавайте структуры файловой системы, которые не оправданы фактическими файлами внутри них. .
- Будьте модульными, но прагматичными
- В целом сообщество узлов предпочитает небольшие модули. Все, что может быть чисто отделено от вашего приложения целиком, должно быть извлечено в модуль для внутреннего использования или публично опубликовано на npm. Тем не менее, для приложений среднего размера, которые являются здесь областью, накладные расходы могут добавить утомление вашему рабочему процессу без соответствующей стоимости. Так что для того времени, когда у вас есть некоторый код, который выровнен, но не достаточен для оправдания совершенно отдельного модуля npm, просто считайте его « proto-module », ожидая, что когда он пересекает некоторый порог размера, это будет извлечено.
- Некоторые люди, такие как @ hij1nx , даже включают каталог
app/node_modules
и имеют файлы package.json
в каталогах proto-module , чтобы упростить этот переход и выступить в качестве напоминания.
- Легко найти код
- Учитывая возможность сборки или исправления ошибки, наша цель состоит в том, чтобы у разработчика не было проблем с поиском соответствующих исходных файлов.
- Имена значимы и точны
- код ошибки полностью удален, не оставлен в файле-сироте или просто закомментирован
- Будьте удобны для поиска
- весь исходный код первого лица находится в каталоге
app
, поэтому вы можете cd
выполнить команду find / grep / xargs / ag / ack / etc и не отвлекаться на сторонние совпадения
- Используйте простые и понятные названия
- npm теперь требует имен пакетов в нижнем регистре. Я нахожу это в основном ужасным, но я должен следовать за стадом, поэтому имена файлов должны использовать
kebab-case
, хотя имя переменной для этого в JavaScript должно быть camelCase
, потому что -
является знаком минус в JavaScript.
- имя переменной совпадает с базовым именем пути модуля, но с
kebab-case
преобразованным в camelCase
- Группировка по соединению, а не по функции
- Это серьезное отклонение от соглашения по Ruby on Rails
app/views
, app/controllers
, app/models
и т. Д.
- Функции добавляются в полный стек, поэтому я хочу сосредоточиться на полном стеке файлов, которые имеют отношение к моей функции. Когда я добавляю поле телефонного номера в модель пользователя, мне не важен какой-либо контроллер, кроме пользовательского контроллера, и меня не волнует какая-либо модель, кроме пользовательской модели.
- Таким образом, вместо того, чтобы редактировать 6 файлов, каждый из которых находится в своем собственном каталоге, и игнорировать тонны других файлов в этих каталогах, этот репозиторий организован таким образом, что все файлы, которые мне нужны для создания функции, располагаются в одном месте
- По своей природе MVC, пользовательский вид связан с пользовательским контроллером, который связан с пользовательской моделью. Поэтому, когда я изменяю модель пользователя, эти 3 файла часто меняются вместе, но контроллер сделок или контроллер клиента разъединяются и, следовательно, не участвуют. То же самое относится и к проектам, отличным от MVC.
- Разъединение в стиле MVC или MOVE с точки зрения того, какой код входит, какой модуль все еще поощряется, но распространение файлов MVC в одноуровневые каталоги просто раздражает.
- Таким образом, каждый из моих файлов маршрутов имеет часть маршрутов, которыми он владеет. Файл в стиле rails
routes.rb
удобен, если вы хотите получить обзор всех маршрутов в приложении, но при создании объектов и исправлении ошибок вам важны только маршруты, относящиеся к изменяемой части.
- Хранить тесты рядом с кодом
- Это всего лишь пример «группирования по связям», но я хотел конкретно это назвать. Я написал много проектов, в которых тесты живут в параллельной файловой системе, называемой «тестами», и теперь, когда я начал размещать свои тесты в том же каталоге, что и соответствующий им код, я больше никогда не вернусь. Это более модульное решение, с которым гораздо проще работать в текстовых редакторах, и оно избавляет от большого количества бессмысленного пути "../../ ..". Если вы сомневаетесь, попробуйте несколько проектов и решите сами. Я не собираюсь делать ничего, кроме этого, чтобы убедить вас, что это лучше.
- Уменьшить сквозное сцепление с событиями
- Легко подумать: «Хорошо, всякий раз, когда создается новая сделка, я хочу отправить электронное письмо всем продавцам», а затем просто введите код для отправки этих электронных писем по маршруту, который создает сделки.
- Однако это соединение в конечном итоге превратит ваше приложение в гигантский шарик грязи.
- Вместо этого DealModel должен просто вызывать событие "create" и совершенно не знать, что еще система может сделать в ответ на это.
- Когда вы кодируете таким образом, становится намного более возможным поместить весь код, связанный с пользователем, в
app/users
, потому что повсюду нет гнезда связанной бизнес-логики, нарушающего чистоту базы кода пользователя.
- Поток кода отслеживается
- Не делай волшебных вещей. Не загружайте файлы из магических каталогов в файловой системе. Не будь Rails. Приложение начинается с
app/server.js:1
, и вы можете увидеть все, что оно загружает и выполняет, следуя коду.
- Не создавайте DSL для ваших маршрутов. Не делайте глупого метапрограммирования, когда оно не требуется.
- Если ваше приложение настолько велико, что выполнение
magicRESTRouter.route(somecontroller, {except: 'POST'})
- это большой выигрыш для вас за 3 основных app.get
, app.put
, app.del
звонка, вы, вероятно, создаете монолитное приложение, которое слишком велико для эффективно работать на. Получите удовольствие от БОЛЬШИХ выигрышей, а не от преобразования 3 простых линий в 1 сложную.
Использовать имена файлов в нижнем регистре
- Этот формат позволяет избежать проблем с чувствительностью к регистру файловой системы на разных платформах
- npm запрещает использование заглавных букв в именах новых пакетов, и это хорошо работает
Экспресс.js Особенности
Не использовать app.configure
. Это почти полностью бесполезно, и вам просто не нужно это. Из-за бессмысленной коппасты он в большом количестве шаблонов.
- ПОРЯДОК СРЕДНЕГО ОБЕСПЕЧЕНИЯ И МАРШРУТОВ ПО ЭКСПРЕСС-МАТЕРИАЛАМ !!!
- Почти каждая проблема маршрутизации, которую я вижу в stackoverflow, является неупорядоченным экспресс-промежуточным программным обеспечением
- В общем, вы хотите, чтобы ваши маршруты были разделены и не зависели от порядка
- Не используйте
app.use
для всего приложения, если вам действительно нужно это промежуточное ПО только для 2 маршрутов (я смотрю на вас, body-parser
)
- Убедитесь, что, когда все сказано и сделано, у вас точно такой заказ:
- Любое очень важное промежуточное программное обеспечение для всего приложения
- Все ваши маршруты и различные промежуточные программы маршрутов
- ТО обработчики ошибок
- К сожалению, будучи вдохновленным синатрой, express.js в основном предполагает, что все ваши маршруты будут в
server.js
, и будет ясно, как они упорядочены. Для приложений среднего размера хорошо разбить вещи на отдельные модули маршрутов, но при этом возникает опасность неработающего промежуточного программного обеспечения
Трюк с символическими ссылками приложения
Существует множество подходов, обрисованных и подробно обсужденных сообществом в великой сущности Лучшие локальные пути require () для Node.js . Вскоре я могу предпочесть либо "просто иметь дело с большим количеством ../../../ ..", либо использовать модуль requireFrom . Однако в данный момент я использую трюк с символьными ссылками, подробно описанный ниже.
Таким образом, один из способов избежать внутрипроектных требований с помощью назойливых относительных путей, таких как require("../../../config")
, заключается в использовании следующего трюка:
- создайте символическую ссылку под node_modules для вашего приложения
- cd node_modules && ln -nsf ../app
- добавить только символическую ссылку node_modules / app , а не всю папку node_modules, для git
- git add -f node_modules / app
- Да, вы все равно должны иметь "node_modules" в вашем
.gitignore
файле
- Нет, вы не должны помещать "node_modules" в ваш репозиторий git. Некоторые люди порекомендуют вам сделать это. Они неверны.
- Теперь вы можете требовать внутрипроектные модули, используя этот префикс
var config = require("app/config");
var DealModel = require("app/deals/deal-model")
;
- По сути, это делает внутрипроектную работу очень похожей на ту, которая требуется для внешних модулей npm.
- Извините, пользователи Windows, вам нужно придерживаться относительных путей родительского каталога.
Конфигурация
Как правило, модули кода и классы ожидают передачи только базового объекта JavaScript options
. Только app/server.js
должен загружать модуль app/config.js
. Оттуда он может синтезировать небольшие объекты options
для настройки подсистем по мере необходимости, но соединение каждой подсистемы с большим глобальным модулем конфигурации, полным дополнительной информации, является плохой связью.
Старайтесь централизовать создание соединений с БД и передавать их в подсистемы, а не передавать параметры соединения и заставлять подсистемы самостоятельно устанавливать исходящие соединения.
NODE_ENV
Это еще одна заманчивая, но ужасная идея, перенесенная из Rails. В вашем приложении должно быть ровно 1 место, app/config.js
, которое смотрит на переменную окружения NODE_ENV
. Все остальное должно принимать явную опцию в качестве аргумента конструктора класса или параметра конфигурации модуля.
Если в модуле электронной почты есть опция для доставки электронной почты (SMTP, запись в stdout, в очередь и т. Д.), Он должен выбрать опцию, такую как {deliver: 'stdout'}
, но он абсолютно не должен проверять NODE_ENV
.
Тесты
Теперь я храню свои тестовые файлы в том же каталоге, что и соответствующий им код, и использую соглашения об именах расширений файлов, чтобы отличать тесты от производственного кода.
foo.js
имеет код модуля "foo"
foo.tape.js
имеет основанные на узлах тесты для foo и живет в том же каталоге
foo.btape.js
можно использовать для тестов, которые необходимо выполнить в среде браузера
Я использую глобусы файловой системы и команду find . -name '*.tape.js'
, чтобы при необходимости получать доступ ко всем моим тестам.
Как организовать код в каждом .js
файле модуля
Сфера действия этого проекта в основном связана с тем, куда идут файлы и каталоги, и я не хочу добавлять много других областей, но я просто упомяну, что я организовал свой код в 3 отдельных раздела.
- Для открытия блока CommonJS требуются вызовы зависимостей состояний
- Основной блок кода из чистого JavaScript. Никакого загрязнения CommonJS здесь. Не ссылаться на экспорт, модуль или запрос.
- Закрытие блока CommonJS для настройки экспорта