Хорошо, это было давно, и это популярный вопрос, поэтому я решил создать репозиторий 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 мертв, пора MOVE на
- и многие другие, как современные, так и исторические
Каждый из них хорошо вписывается в другую структуру каталогов. Для целей этого примера это просто строительные леса, а не полностью работающее приложение, но я предполагаю следующие ключевые моменты архитектуры:
- На сайте есть несколько традиционных статических страниц / шаблонов.
- «Приложение» часть сайта разработана как одностраничный стиль приложения.
- Приложение предоставляет браузеру API в стиле REST / JSON.
- Приложение моделирует простой бизнес-домен, в данном случае это приложение для автосалона.
А как насчет Ruby on Rails?
На протяжении всего проекта будет темой, что многие идеи, воплощенные в Ruby on Rails, и принятые ими решения "Convention over Configuration", хотя они широко приняты и используются, на самом деле не очень полезны, а иногда являются противоположностью тому, что в этом хранилище. рекомендую.
Здесь я хочу сказать, что существуют основополагающие принципы организации кода, и, основываясь на этих принципах, соглашения 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, просто считайте его « прото-модулем », ожидая, что когда он преодолеет некоторый порог размера, он будет извлечен.
- Некоторые люди , такие как @ hij1nx даже включать в
app/node_modules
каталог и есть package.json
файлы в прото-модулях каталогов , чтобы облегчить этот переход и действовать в качестве напоминания.
- Быть легко найти код
- Нашей целью является сборка или исправление ошибки, и наша цель состоит в том, чтобы у разработчика не было проблем с поиском исходных файлов.
- Имена значимы и точны
- Неправильный код полностью удален, не оставлен в бесхозном файле или просто закомментирован
- Быть дружественным к поиску
- весь первоисточник исходного кода находится в
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 в одноуровневые каталоги просто раздражает.
- Таким образом, каждый из моих файлов маршрутов имеет ту часть маршрутов, которой он владеет.
routes.rb
Файл в стиле rails удобен, если вы хотите получить обзор всех маршрутов в приложении, но при создании объектов и исправлении ошибок вам важны только маршруты, относящиеся к изменяемой части.
- Хранить тесты рядом с кодом
- Это всего лишь пример «группирования по связям», но я хотел конкретно это назвать. Я написал много проектов, в которых тесты живут в параллельной файловой системе, называемой «тестами», и теперь, когда я начал размещать свои тесты в том же каталоге, что и соответствующий им код, я больше никогда не вернусь. Это более модульное решение, с которым гораздо проще работать в текстовых редакторах, и оно устраняет большую часть бессмысленного пути "../../ ..". Если вы сомневаетесь, попробуйте несколько проектов и решите сами. Я не собираюсь ничего делать, чтобы убедить вас, что это лучше.
- Уменьшите сквозную связь с событиями
- Легко подумать: «Хорошо, всякий раз, когда создается новая сделка, я хочу отправить электронное письмо всем продавцам», а затем просто введите код для отправки этих электронных писем по маршруту, который создает сделки.
- Однако эта связь в конечном итоге превратит ваше приложение в гигантский шарик грязи.
- Вместо этого 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 запрещает использование заглавных букв в именах новых пакетов, и это хорошо работает
особенности express.js
Не используйте app.configure
. Это почти полностью бесполезно, и вам просто не нужно это. Это в большом количестве из-за бессмысленной коппасты.
- ПОРЯДОК СРЕДНЕГО ОБЕСПЕЧЕНИЯ И МАРШРУТОВ ПО ЭКСПРЕССАМ !!!
- Почти каждая проблема маршрутизации, которую я вижу в stackoverflow, является неупорядоченным экспресс-промежуточным ПО
- В общем, вы хотите, чтобы ваши маршруты были разделены и не полагались на порядок
- Не используйте
app.use
для всего приложения, если вам действительно нужно это промежуточное ПО только для 2 маршрутов (я смотрю на вас, body-parser
)
- Убедитесь, что, когда все сказано и сделано, у вас точно такой заказ:
- Любое очень важное промежуточное программное обеспечение для всего приложения
- Все ваши маршруты и различные промежуточные программы маршрутов
- ТО обработчики ошибок
- К сожалению, будучи вдохновленным синатрой, express.js в основном предполагает, что все ваши маршруты будут в пути,
server.js
и будет ясно, как они упорядочены. Для приложений среднего размера разбить вещи на отдельные модули маршрутов - это хорошо, но оно представляет опасность неработающего промежуточного программного обеспечения
Трюк с символической ссылкой на приложение
Есть много подходов , изложенных и обсужденные на длину сообщества в большой сущности лучше местным требуют () пути для Node.js . Вскоре я могу предпочесть либо "просто иметь дело с большим количеством ../../../ ..", либо использовать модуль requireFrom. Тем не менее, в данный момент я использую трюк с символическими ссылками, описанный ниже.
Таким образом, один из способов избежать внутрипроектных требований с помощью раздражающих относительных путей, таких как require("../../../config")
использование следующего трюка:
- создайте символическую ссылку под node_modules для вашего приложения
- cd node_modules && ln -nsf ../app
- добавьте в Git только символическую ссылку node_modules / app , а не всю папку node_modules
- 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, вам нужно придерживаться относительных путей родительского каталога.
конфигурация
Как правило, модули кода и классы ожидают options
передачи только базового объекта JavaScript . Только модуль 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 для настройки экспорта