Структура проекта и управление конфигурацией¶
Начиная с этой главы, мы будем создавать реальное RESTful облачное нативное приложение, начиная с начальной структуры проекта. Больше никаких примеров foo/bar и теории Fastify. Мы будем применять на практике то, что узнали в предыдущих главах. Это приведет нас к пониманию того, как построить приложение.
В этой главе мы создадим прочную структуру, которую вы сможете использовать в своих будущих проектах. Вы познакомитесь с пакетами сообщества, будете использовать их и при необходимости создавать собственные плагины.
Именно этот путь обучения мы рассмотрим в этой главе:
- Проектирование структуры приложения
- Улучшение структуры приложения
- Отладка приложения
- Совместное использование конфигурации приложения плагинами
- Использование плагинов Fastify
Технические требования
Как уже упоминалось в предыдущих главах, вам понадобится следующее:
- Работающая установка Node.js 18.
- VS Code IDE.
- Рабочая установка Docker.
- Репозиторий Git — настоятельно рекомендуется, но не является обязательным.
- Рабочая командная оболочка.
Все фрагменты в этой главе находятся на GitHub.
Проектирование структуры приложения¶
В этой главе мы спроектируем внутреннее приложение, которое будет открывать некоторые RESTful API.
Структура приложения позволяет писать приложения, которые легко внедрять, развивать и поддерживать. Хорошая система должна быть гибкой к вашим потребностям и изменениям в приложении. Кроме того, она должна предусматривать определенный дизайн реализации, чтобы позволить вам и вашей команде избежать основных «подводных камней», которые могут привести к нестабильному и не поддающемуся тестированию приложению.
В этой главе функции приложения будут рассмотрены лишь вскользь. По сути, проект-скаффолдинг не должен заботиться о них, но он должен применять их к любому проекту. По этой причине мы создадим приложение с некоторыми маршрутами проверки работоспособности и готовым к использованию соединением с MongoDB.
Мы представим набор плагинов Fastify, которые помогут нам структурировать наше приложение и уменьшить бремя написания с нуля некоторых утилит, которые уже были разработаны и протестированы на производстве множеством проектов.
Важнейшим моментом здесь является понимание того, почему мы построим следующую структуру именно таким образом. Структура, которую мы рассмотрим, не является обязательной, и вы можете критически отнестись к предложенному дизайну. Мы считаем, что важно персонализировать приложение, чтобы адаптировать его к вашим собственным потребностям и предпочтениям.
Мы закончили с разговорами. Давайте начнем создавать наше приложение!
Настройка репозитория¶
Прежде чем приступать к написанию кода, необходимо определить базовый уровень, с которого мы начнем. К счастью, создать пустое приложение Fastify довольно просто благодаря официальной утилите, созданной командой Fastify. Чтобы воспользоваться ею, нам нужно открыть командную оболочку и ввести следующие команды:
1 2 3 |
|
Выполнение этих команд создает новую папку fastify-app
и запускает из нее команду npm init
.
Команда init
Когда вы выполняете команду init
, npm запускает модуль create-fastify
, который вы можете найти в этом репозитории GitHub. Вы можете создать полномочия create-my-app
для создания подмостков приложения, чтобы ускорить инициализацию проекта.
В результате вы увидите следующие файлы и каталоги:
package.json
: Это точка входа в проект.app.js
: Это основной файл приложения. Он загружается первым.plugins/
: В этой папке хранятся пользовательские плагины. Она содержит несколько файлов примеров.routes/
: В этой папке хранятся конечные точки приложения. Она содержит несколько примеров конечных точек.test/
: Это папка, в которой мы пишем тесты нашего приложения.
Стартовое приложение готово к установке с помощью команды npm install
. После установки вам могут пригодиться эти скрипты, которые уже настроены:
npm test
: Этот скрипт запускает тест приложения-скаффолдинга.npm start
: Этот скрипт запустит ваше приложение.npm run dev
: Этот скрипт запустит ваше приложение в режиме разработки. Сервер автоматически перезагружает ваше приложение при каждом изменении файла.
Мы только что создали базовую конфигурацию приложения Fastify, которую мы будем настраивать для создания нашего приложения. Читая сгенерированный исходный код с помощью команды init
, вы найдете комментарии, которые помогут вам сориентироваться и дадут некоторые сведения, которые мы будем видеть на протяжении всей этой главы.
Версионирование приложений
Чтобы начать создание реального приложения, необходимо установить систему управления версией (VCS). Это программное обеспечение позволяет нам версионировать исходный код и управлять изменениями. Для решения этой задачи следует использовать программу Git, поскольку она является стандартным программным обеспечением де-факто в технологической индустрии. Однако изучение Git не является целью этой книги. Чтобы узнать, как установить и использовать Git, ознакомьтесь с разделом Технические требования.
На данном этапе все команды, о которых мы упоминали в этом разделе, должны работать на вашем компьютере. Потратьте немного времени, чтобы попробовать режим разработки, отредактировав файл routes/root.js
и добавив новый маршрут GET /hello
, пока сервер работает!
Понимание структуры приложения¶
Структура приложения, которую мы создали до сих пор, обладает отличными возможностями из коробки. Она основана на некоторых из следующих столпов:
- Она опирается на плагин
fastify-cli
для запуска приложения и предоставления режима разработчика. - Он использует преимущества плагина
@fastify/autoload
для автоматической загрузки всех файлов, содержащихся в папкахplugins/
иroutes/
.
Использование этих двух плагинов не является обязательным, но они предлагают множество отличных возможностей, которые нам понадобятся в определенный момент в процессе развития нашего приложения. Их использование сейчас поможет вам обрести уверенность в этих функциях и ускорит работу в дальнейшем.
Плагин fastify-cli
Плагин fastify-cli
интерфейс командной строки (CLI) помогает нам запустить наше приложение. Он используется в файле package.json
. Свойство scripts
использует команду fastify start
с некоторыми опциями для создания файлаapp.js
. Как вы заметили, файл app.js
экспортирует типичный интерфейс плагина Fastify async function (fastify, opts)
. Файл загружается в CLI как обычный вызов app.register()
, как мы видели в разделе Добавление экземпляра базового плагина главы 1. В данном случае мы не инстанцируем экземпляр корневого сервера Fastify и не вызываем метод listen
. Все эти задачи решает fastify-cli
, избавляя нас от кодового шаблона.
Более того, CLI улучшает процесс разработки, реализуя соответствующие настройки и опции:
- Он добавляет изящное завершение работы, о котором мы читали в разделе Завершение работы приложения в главе 1.
- По умолчанию он считывает корневой файл
.env
. Это файлключ=значение
, содержащий только строку. Он используется для описания параметров, которые будут считываться из настроек окружения операционной системы. Все эти переменные отображаются в объект Node.jsprocess.env
. - Он начинает прослушивать переменную окружения
PORT
для всех хостов. - Он принимает опцию
--debug
для запуска приложения в режиме debug для отладки вашего кода. - Он раскрывает флаг
--options
для настройки параметров сервера Fastify, поскольку мы не инстанцируем его напрямую. - Аргумент
--watch
включает автозагрузку сервера при изменении файла в проекте. - Аргумент
--pretty-logs
делает журналы вывода читаемыми в оболочке.
Подробную документацию по CLI вы можете найти в репозитории.
Мы собираемся настроить нашу установку fastify-cli
в следующем разделе: Улучшение структуры приложения.
Плагин @fastify/autoload
Плагин autoload автоматически загружает плагины, найденные в директории, и настраивает маршруты в соответствии со структурой папок. Другими словами, если вы создадите новый файл routes/test/foo.js
со следующим содержимым, будет объявлен новый маршрут GET /test/
:
1 2 3 4 5 |
|
Это поведение следует парадигме проектирования конвенция над конфигурацией. Ее фокус заключается в сокращении реализации для обеспечения желаемого поведения по умолчанию.
Используя плагин @fastify/autoload
, вы можете рассматривать каждый файл как инкапсулированный контекст, где каждая папка составляет prefix
плагина, как мы видели в Глава 2.
Учитывая, что нам не нужен весь код в автозагрузке, вполне можно потребовать от наших плагинов и маршрутов регистрировать необходимые части как дочерние, но для этого нужно найти четкий шаблон.
Плагин автозагрузки имеет несколько вариантов поведения по умолчанию и основан на соглашении об именовании, которое может запутать, если вы не изучите документацию. Мы не будем рассматривать все различные варианты и комбинации: их очень много, и чтобы объяснить их, потребуется целая книга! По этой причине мы настроим приложение по умолчанию, чтобы сделать его более прозрачным и простым в обслуживании, используя наиболее консолидированную настройку.
Улучшение структуры приложения¶
Мы становимся уверенными в нашей базовой структуре приложения, но она еще не готова. Мы можем улучшить ее, чтобы определить наши собственные правила и обеспечить лучший опыт разработчика. Сначала мы создадим прочный каркас проекта, сосредоточившись на общей картине. После этого мы завершим настройку Fastify.
Начало оптимального проекта¶
Хороший проект — это не только причудливая технология в действии, он также должен обеспечивать хороший опыт разработчика, снижая любую нагрузку. Согласно философии Fastify, мы должны создавать нашу новую кодовую базу, помня об этом аспекте. Поэтому мы кратко расскажем об этих аспектах, поскольку они бесценны и экономят время, но часто недооцениваются.
Файл README
Первое дополнение к нашему проекту — это файл README.md
. Типичный файл readme знакомит новичков с существующим проектом, отвечая на ряд основных информационных вопросов, таких как:
- Каковы требования к проекту? Нужна ли вам база данных или другие внешние ресурсы?
- Как установить приложение? Какой менеджер пакетов и версию Node.js оно использует?
- Как запустить приложение? Где можно найти недостающие данные (например, переменные окружения)?
- Как разрабатывать приложение? Есть ли какие-то соглашения, которым должны следовать разработчики?
- Как вы тестируете приложение? Требуются ли модульные или сквозные тесты?
- Как вы развертываете приложение? Каковы процессы и URL-адреса окружения?
- Что делать разработчику, если не хватает информации?
На этот набор вопросов может не быть ответов в начале проекта, но полезно отметить те, на которые нет ответов, и найти их в будущем.
Позитивные вибрации
Файл README оказывает множество других положительных эффектов на моральный дух команды. Прочитав его, разработчик почувствует себя продуктивным. Мы предлагаем вам прочитать следующую статью, если вы хотите узнать больше о важности файла README. Автор статьи — Том Престон-Вернер, сооснователь GitHub и многих других проектов с открытым исходным кодом.
Самое главное — поддерживать файл README в актуальном состоянии. Каждый читатель должен улучшать его, добавляя недостающие части или удаляя старые. Если этот описательный файл станет слишком большим, он станет практически нечитабельным. Поэтому подумайте о создании папки docs/
, чтобы разделить его на более удобные для чтения части.
Линтер кода
Еще одним важным шагом, который вы должны рассмотреть, является использование линтера. Линтер — это программа, которая статически анализирует исходный код и предупреждает вас о возможных проблемах или опечатках, чтобы вы не тратили часы на отладку и добавление сообщений console.log
.
Если вы не хотите выбирать линтер и настраивать его, мы предлагаем выбрать standard
. Это линтер с нулевой конфигурацией, который можно установить, выполнив команду npm install standard --save-dev
, и который готов к интеграции в пользовательские скрипты package.json
, как показано ниже:
1 2 3 4 5 |
|
Таким образом, мы сможем запустить npm run lint
, чтобы проверить наш исходный код и получить обратную связь. Если результата не будет, значит, все в порядке! Запуск npm run lint:fix
автоматически исправит ошибки, когда это возможно — это полезно для проблем с форматированием.
Обратите внимание, что мы можем интегрировать проверку lint в требования к проекту. Для этого нам нужно изменить скрипты package.json
следующим образом:
1 2 |
|
Команда npm test
автоматически выполнит скрипты pretest
и posttest
, если они присутствуют. Вам может быть полезно прочитать документацию по npm pre
и post
, чтобы выполнить дополнительные команды до и после выполнения скрипта.
Сборка контейнера
Приложение Fastify, которое мы будем собирать в этой книге, может работать как на производственном сервере, так и на контейнерном движке. Первый может запустить наше приложение с помощью канонической команды npm start
. Второй требует контейнера для запуска нашего приложения.
Мы не будем углубляться в тему контейнеров, поскольку она выходит за рамки этой книги. Полезно вникнуть в эту тему на примере безопасного контейнера, который вы увидите далее. Эта конфигурация готова для создания производственных контейнеров Node.js.
Чтобы собрать контейнер Docker, содержащий наше программное обеспечение, мы должны создать документ Dockerfile
в корневом каталоге. Этот файл содержит инструкции по сборке образа нашего контейнера наиболее безопасным способом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
В предыдущем фрагменте сценария определено, как Docker должен создать контейнер. Эти шаги описаны следующим образом:
FROM
: Это запускает многоступенчатую сборку нашего приложения из базового образа в Node.js и с установленным npm.ENV
: Здесь задаются некоторые полезные переменные окружения, которые всегда будут установлены в контейнере.COPY
: Копирует файлыpackage.json
в папку контейнера.WORKDIR
: Устанавливает текущий рабочий каталог, из которого будут выполняться последующие команды.RUN npm ci
: Устанавливает зависимости проекта с помощью файлаpackage-lock
.COPY
: Копирует исходный код приложения в контейнер.RUN apk
: Устанавливает программу dumb-init в контейнер.USER
: Устанавливает пользователя контейнера по умолчанию во время выполнения. Это пользователь с наименьшими привилегиями для обеспечения безопасности нашей производственной среды.EXPOSE
,ENTRYPOINT
иCMD
: Они определяют внешний интерфейс контейнера и устанавливают запуск приложения в качестве команды по умолчанию при инициализации контейнера.
Этот файл представляет собой безопасный и полный дескриптор для создания контейнера приложений и является идеальной базовой основой для нашего проекта. Со временем он будет меняться по мере изменения логики запуска приложения.
Мы выбрали многоступенчатую сборку, потому что для успешной установки приложения вам может потребоваться предоставить некоторые секреты. Типичный пример — использование частного реестра npm. Эти секреты не должны храниться в Docker-образе приложения, иначе любой, кто получит доступ к Docker-образу, сможет слить токен npm и получить доступ к вашему приватному реестру npm. Вместо этого многоступенчатая сборка состоит из двух этапов:
- Создание образа
builder
, имеющего доступ к приватному реестру npm, и загрузка зависимостей приложения. - Скопируйте зависимости из образа
builder
в образ приложения, а затем выбросьте образbuilder
и его секреты.
Наконец, чтобы использовать этот файл, необходимо выполнить команду docker build -t my-app
, после чего начнется процесс сборки. Более подробно мы рассмотрим эту тему в Главе 10.
Папка test
Мы не должны забывать о тестировании нашего приложения. Напоминаем, что мы должны создать папку test/
, содержащую все тесты приложения, которые мы реализуем в Глава 9. Однако сначала нам нужно разобраться со структурой нашего приложения. Это связано с тем, что реализация тестов зависит от реализации проекта. Только после того, как мы достигнем стабильного решения, мы сможем написать наши основные утверждения, такие как следующее:
- Приложение запускается правильно
- Конфигурации загружаются в правильном порядке
Утверждения тестов должны положительно отвечать на эти вопросы, чтобы доказать, что наш конфигурационный скаффолдинг работает так, как ожидалось.
Мы завершили базовую настройку проекта. Речь шла не столько о Fastify, сколько о том, как начать хороший проект еще до написания нескольких строк кода. Мы не должны забывать, что каждая строчка кода или документации станет в будущем наследуемым кодом. Поэтому мы должны приложить все усилия, чтобы сделать его простым и безопасным в использовании.
Теперь давайте посмотрим, как продолжить структуру кодовой базы, чтобы подготовиться к написанию нашего первого реального приложения.
Управление директориями проекта¶
В настоящее время в скаффолдинге приложения есть две основные папки, загружаемые одинаковым образом плагином @fastify/autoload
. Но эти папки вовсе не равнозначны.
Нам нужно больше бакетов, чтобы упорядочить исходный код и сделать его понятным и читаемым. Например, мы видели, что структуры JSON-схем могут стать многословными, поэтому их следует убрать из реализации маршрутов, чтобы сосредоточиться на бизнес-логике.
Мы создадим новые папки проекта, чтобы определить окончательную структуру проекта и объявить, что они должны содержать. Каждая папка будет иметь свою собственную конфигурацию @fastify/autoload
.
Папки представлены в порядке загрузки, и вы не должны забывать об этом при написании:
1 2 3 4 5 6 |
|
Код сначала загрузит плагины, а затем маршруты. Если бы процесс был обратным, это привело бы к ошибкам. По этой причине следующие папки проекта представлены в порядке загрузки.
Загрузка плагинов.
Папка plugins/
должна содержать плагины, основанные на модуле fastify-plugin
. С этим модулем мы познакомились в Главе 2.
В этом каталоге следует хранить все компоненты приложения, которые необходимо зарегистрировать следующим образом:
- Как подключаемый компонент корневого экземпляра сервера, например подключение к базе данных.
- Как многократно используемые компоненты плагина. Эти файлы не предназначены для автозагрузки, но они должны быть зарегистрированы, когда это требуется маршрутами. Примером может быть плагин аутентификации.
По этой причине хорошим подходом является редактирование настроек автозагрузки папки plugins следующим образом:
1 2 3 4 5 6 |
|
С этой новой установкой свойство ignorePattern
позволяет нам игнорировать все те файлы, которые заканчиваются именем .no-load.js
. Эта опция позволяет с первого взгляда понять, что загружается, а что нет, что улучшает наглядность проекта. Обратите внимание, что шаблон не учитывает имя директории.
Настройте в соответствии с вашими предпочтениями
Если вам не нравится шаблон «no-load», вы можете изменить логику, установив значение свойства ignorePattern: /^((?!load\.js).)*$/
, и загружать только файлы с суффиксом .load.js
.
Свойство indexPattern
вместо этого отключает плагин @fastify/autoload
. По умолчанию, если каталог содержит файл index.js
, он будет загружен только один, пропуская все остальные файлы. Это может быть нежелательным поведением, которое предотвращает опция indexPattern
.
Наконец, свойство options позволяет нам предоставить объект конфигурации в качестве входных данных для загружаемых плагинов. В качестве примера возьмем файл plugins/support.js
. Он экспортирует интерфейс module.exports = fp(async function (fastify, opts)
. Параметр options
автозагрузки совпадает с аргументом opts. Таким образом, можно предоставить конфигурацию для всех плагинов. Более подробно мы рассмотрим этот аспект в разделе Загрузка конфигурации.
Загрузка схем
JSON-схемы являются важной частью безопасного проекта и нуждаются в соответствующем этапе в структуре приложения. Создание папки schemas/
для хранения всех JSON-схем удобно во время разработки приложения: вскоре вы узнаете, что мы будем работать с большим количеством схем.
В эту папку мы добавим файл loader.js
, у которого будет одна задача. Он должен добавить все JSON-схемы, которые понадобятся для нашего приложения:
1 2 3 4 5 |
|
Этот фрагмент кода на самом деле является плагином, но он может становиться все больше и больше. Выделение его из папки plugins/
позволяет нам избежать хаотичной, бесконечной прокрутки при навигации по кодовой базе.
Будет много схем для работы, потому что настоятельно рекомендуется определять схему для каждой части HTTP, которую нужно валидировать или сериализовать. Все HTTP-главы требуют различных типов валидации; например, поле id не должно вводиться в маршруте POST, но оно обязательно в маршруте PUT для обновления связанного ресурса. Попытка вписать общий объект схемы JSON в несколько частей HTTP может привести к неожиданным ошибкам валидации.
Существует автоматизация для загрузки схем в каталог. Поэтому нам нужно будет перечислить все файлы в текущей директории и запустить метод addSchema
. Как это реализовать, мы увидим в Главе 7.
Для загрузки файла loader.js
в наш проект используется плагин @fastify/autoload
. Это может показаться убийством мухи кувалдой, но это хороший метод, который можно использовать для еще большего разделения наших схем. Зарегистрируйте плагин в файле app.js
, как показано ниже:
1 2 3 4 |
|
Таким образом, плагин автозагрузки будет загружать только файлы loader.js
, созданные в дереве каталогов. Таким образом, мы сможем сделать вложенные папки, например, следующую:
schemas/headers/loaders.js
.schemas/params/loader.js
schemas/body/loader.js
.
Точно так же мы можем сделать больше подпапок для каждого HTTP-метода, и вы найдете оптимальную древовидную структуру, подходящую для вашего приложения. Мы обнаружили, что разделение схем по частям HTTP — лучший способ упорядочивания. Такое разделение еще больше ускоряет навигацию по источникам. Мы сможем создать несколько утилит, предназначенных для каждой части HTTP, например, несколько регулярных выражений для заголовков и сложные многократно используемые объекты для ввода тела.
Загрузка маршрутов
Папка routes/
содержит конечные точки приложения. Все файлы загружаются автоматически, что затрудняет разбиение кодовой базы на более мелкие части. На данном этапе файл utility.js
будет загружен командой @fastify/autoload
. Более того, определение файла index.js
будет препятствовать загрузке других файлов, как мы видели ранее в разделе «Загрузка плагинов».
Лучшие правила, которые мы предлагаем применять, когда речь идет о папке routes/
, следующие:
- Загружайте в автозагрузку только те файлы, которые заканчиваются на
*routes.js
. Все остальные файлы в папке отбрасывайте. Игнорируемые файлы можно зарегистрировать вручную или использовать в качестве кода утилиты. - Мы не должны использовать модуль
fastify-plugin
в этой папке. Если он нам понадобится, мы должны остановиться и подумать, можно ли перенести этот код в папкуplugins/
. - Включите функцию autohook в
@fastify/autoload
.
Чтобы применить эти правила, нам нужно настроить плагин автозагрузки, как показано в следующем примере кода:
1 2 3 4 5 6 7 8 9 |
|
Этот фрагмент кода вводит два новых параметра. Флаг autoHooks
позволяет зарегистрировать несколько хуков для каждого файла routes.js
. Параметр cascadeHooks
также включает эту функцию для подкаталогов.
Приведем пример этой структуры, которую мы рассмотрим далее в Глава 8. Учитывая это дерево папок, файл authHooks.js
экспортирует стандартный интерфейс плагина Fastify, но он должен настраивать только хуки жизненного цикла:
1 2 3 4 5 6 7 |
|
В этом примере настроены некоторые хуки onRequest
для проверки авторизации клиента. Итак, ожидаете ли вы, что routes/games/routes.js
будет аутентифицирован?
Если вы ответили «да», то вам подойдет cascadeHooks: true
. Мы думаем, что большинство из вас естественным образом обнаружит, что хуки, зарегистрированные как autoHooks
в родительской папке, добавляются в дочерние.
Если ответ отрицательный, вы можете изменить опцию cascadeHooks
на false
, чтобы не добавлять хуки в дочерние папки. В этом случае хуки будут загружены только для файлов readRoutes.js
и writeRoutes.js
. Возможно, вам придется продублировать файл authHooks.js
для каждой папки, для которой вам нужна аутентификация, или зарегистрировать его как plugins/
в корневом контексте.
Все эти правила должны быть перечислены в файле README.md
, чтобы прокричать о том, как разрабатывать новый набор маршрутов. Это поможет вашей команде присоединиться к проекту без детального изучения всех плагинов Fastify.
Теперь у нас есть четкая структура, готовая принять конечные точки бизнес-логики. Тем не менее, прежде чем двигаться дальше, нам нужно разобраться с последним аспектом структуры репозитория: как управлять конфигурацией.
Загрузка конфигурации¶
Конфигурация — это первый шаг, который должно выполнить наше приложение для корректного запуска. В разделе Понимание типов конфигурации главы 1 мы обсудили три типа конфигурации, которые необходимы нашему приложению:
- Опции сервера: Это настройка корневого экземпляра Fastify
- Конфигурация приложения: Это дополнительные настройки, которые определяют, как работает ваше приложение
- Конфигурация плагина: Здесь содержатся все параметры для настройки плагинов.
Эти конфигурации имеют разные источники: опция сервера — это объект, конфигурации плагинов — сложные объекты, а конфигурация приложения зависит от окружения. По этой причине их нужно обрабатывать по-разному.
Загрузка конфигурации сервера.
Инстанцирование сервера не находится под нашим контролем. За нас это делает плагин fastify-cli
, поэтому нам нужно настроить его, чтобы задать опции сервера.
Мы должны отредактировать скрипты start
и dev в нашем файле package.json
:
1 2 |
|
Мы модифицировали скрипт dev
для выполнения скрипта start
, чтобы уменьшить дублирование кода и избежать ошибок copy-and-paste. Двойное тире (--
) позволяет нам передавать дополнительные аргументы предыдущей команде. Таким образом, это похоже на добавление параметров к скрипту start
.
Добавление флага --option
в скрипт start
равносильно добавлению его в обе команды без дублирования.
Флаг --option
использует свойство app.js
options. Не забудем добавить опции сервера, которые мы хотим предоставить при инициализации Fastify, и поместить их в нижнюю часть файла:
1 2 3 4 5 6 7 |
|
При этом мы экспортируем тот же объект JSON, который мы предоставляли фабрике Fastify. Перезапуск сервера загрузит эти настройки. Внимательный разработчик может заметить, что мы не настроили опции logger
, но мы можем видеть логи в нашей консоли. Это происходит потому, что наши настройки объединены в аргументах fastify-cli
, а опция -l info
устанавливает уровень журнала на info
.
Централизация всех настроек в одном месте — лучшая практика, поэтому удалите аргумент -l
из скрипта package.json
и добавьте обычную конфигурацию logger
в экспортируемый JSON.
В целях централизации мы можем переместить app.js
module.exports.options
в новую выделенную папку configs/server-options.js
. Серверная опция не нуждается в асинхронной загрузке и может читать объект process.env
для доступа ко всем значениям файла .env
, загружаемым при запуске fastify-cli
.
Загрузка конфигурации приложения
Конфигурация приложения обязательна для каждого проекта. В ней хранятся секреты, такие как ключи API, пароли и URL-адреса соединений. В большинстве случаев она смешивается в разных источниках, таких как файловая система, переменные окружения или внешние менеджеры секретов, которые хранят эту информацию в защищенном виде, обеспечивая дополнительный контроль над видимостью переменных.
Мы сосредоточимся на одном основном типе загрузки: переменной окружения. Это самый распространенный тип, и он позволяет нам использовать Secret Managers. Чтобы углубиться в понимание внешних Secret Managers, мы предлагаем вам прочитать эту исчерпывающую статью. В ней рассказывается, как загрузить конфигурацию от самых известных провайдеров, таких как AWS, Google Cloud Platform и Hashicorp Vault.
Конфигурация среды привязана к системе, на которой работает программное обеспечение. В нашем случае это может быть наш компьютер, удаленный сервер или компьютер коллеги. Как уже говорилось ранее, Node.js по умолчанию загружает все переменные окружения операционной системы (ОС) в объект process.env
. Поэтому работа над несколькими проектами может быть неудобной, так как каждый раз придется менять конфигурацию ОС. Создание Replace с текстовым файлом .env
в корневой папке проекта является решением этой неприятной проблемы:
1 2 3 |
|
Этот файл будет прочитан при запуске, и он будет готов к доступу. Обратите внимание, что этот файл перезапишет свойство process.env
, если оно уже существует. Поэтому вы должны быть уверены, что то, что находится в вашем файле .env
, является источником истины для конфигурации приложения. Вы можете проверить правильность загрузки файла app.js
, добавив простой журнал:
1 2 3 |
|
Поскольку файл .env
может содержать конфиденциальные данные, вы никогда не должны фиксировать его в вашем репозитории. Вместо него следует зафиксировать и выложить в общий доступ файл .env.sample
. В нем перечислены все ключи, которые должны быть установлены в качестве переменных окружения, без каких-либо секретных значений.
Сохранение конфиденциальных данных в репозитории опасно тем, что тот, кто имеет к ним доступ, может получить доступ к верхним окружениям, таким как production
. Таким образом, безопасность переносится с доступа к среде, например серверу, на настройки Git-репозитория. Более того, если необходимо обновить переменную окружения, следует зафиксировать ее и опубликовать новую версию ПО, чтобы развернуть изменения в других окружениях. Это неправильно: программное обеспечение не должно быть привязано к значениям переменных окружения. Не забывайте отслеживать все файлы, которые должны быть секретными, в файлах .gitignore
и .dockerignore
.
Мы можем улучшить файл .env.sample
, чтобы сделать его как можно более простым. Для этого нам понадобится плагин @fastify/env
, который выбрасывает ошибку всякий раз, когда не находит ожидаемую переменную.
Прежде всего, нам нужна JSON-схема, описывающая наш файл .env
. Поэтому мы можем создать файл schemas/dotenv.json
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Среда схемы JSON довольно линейна. Она определяет свойство для каждой переменной, которую мы ожидаем. Мы можем задать значение по умолчанию и принудительно указать тип, как мы это сделали для свойства PORT
в схеме JSON. Еще один приятный момент — формат $id
. Он имеет синтаксис Uniform Resource Name (URN). Спецификация, рассмотренная в главе 5, объясняет, как он может быть Uniform Resource Identifier (URI). URI может быть URL, определяющим местоположение, или URN, когда он определяет имя ресурса, не указывая, где его можно получить.
Теперь мы не должны забыть обновить файл schemas/loader.js
, написав fastify.addSchema(require('./dotenv.json'))
для загрузки схемы.
Чтобы интегрировать плагин @fastify/env
, мы создадим первый плагин нашего приложения, plugins/config.js
:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Плагин будет загружен функцией автозагрузки, поэтому нам нужно запустить сервер и опробовать его. Запуск сервера без свойства MONGO_URL
остановит запуск и сообщит, что ключ отсутствует. Более того, он добавит декоратор к экземпляру Fastify, назвав значение confKey
. Таким образом, ключи .env
будут доступны для чтения свойства fastify.secrets
, отделяя код от глобального объекта process.env
.
Прежде чем углубляться в загрузку конфигурации плагинов, стоит обратить внимание на опцию name, которую мы только что задали в качестве входного параметра для функции fp
. Она станет ключом к пониманию раздела «Загрузка конфигураций плагинов».
Загрузка конфигураций плагинов
Настройки плагина зависят от конфигурации приложения. Он требует секретов приложения, чтобы настроить себя на работу так, как ожидается, но как мы можем настроить его?
Прежде чем приступить к работе, вам понадобится экземпляр MongoDB, запущенный в вашей среде разработки. Мы будем использовать эту базу данных в последующих главах. Вы можете скачать community edition бесплатно или использовать временный Docker-контейнер, запустив его с помощью следующей команды:
1 2 |
|
Эти команды Docker запускают и останавливают контейнер для целей разработки. Хранящиеся в нем данные будут потеряны после остановки, что делает его подходящим для нашего учебного процесса.
Отслеживайте все команды
Лучше всего хранить все полезные команды в свойстве скриптов package.json
для корректного запуска проекта. Таким образом, независимо от того, выберете ли вы Docker-контейнер или локальную установку MongoDB, вы сможете запустить npm run mongo:start
, чтобы получить работающий экземпляр, готовый к использованию.
После настройки MongoDB давайте интегрируем плагин @fastify/mongodb
. Он предоставляет доступ к базе данных MongoDB для использования в конечных точках приложения. Его нужно установить, выполнив команду npm install @fastify/mongodb
, а затем создать новый файл plugins/mongo-data-source.js
:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
В приведенном фрагменте кода конфигурация MongoDB содержится в файле самого плагина, но для корректной загрузки ему требуется конфигурация приложения. Это обеспечивается опцией dependencies
. Она представляет собой аргумент функции fp
, в котором перечислены все плагины, которые были загружены ранее. Как видите, параметр name
, который мы задали в предыдущем разделе «Загрузка конфигурации приложения», дает нам контроль над порядком загрузки плагинов.
Мы создаем прочную и четкую структуру проекта, которая поможет нам в повседневной работе:
- Она подсказывает нам, где должен быть написан код.
- Она принуждает использовать систему плагинов, улучшая наш исходный код, чтобы он полагался на инкапсуляцию вместо глобальных переменных и побочных эффектов
- Он заставляет объявлять зависимости между плагинами, чтобы избежать ошибок и контролировать порядок загрузки.
Можем ли мы использовать преимущества архитектуры Fastify в рамках этой структуры? Если что-то пойдет не так, как мы сможем справиться с ошибками? Давайте посмотрим на это дальше.
Отладка вашего приложения¶
В реальном приложении могут возникать ошибки! Как же мы можем справиться с ними в нашей структуре? Да, вы правильно догадались, с помощью плагина!
В производстве файлы журналов являются нашим отладчиком. Поэтому, прежде всего, важно писать хорошие и безопасные журналы. Давайте создадим новый файл плагина plugins/error-handler.js
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
Этот фрагмент кода представляет собой настройку обработчика ошибок, о котором мы узнали в главе 3. Его приоритеты заключаются в следующем:
- Зафиксировать ошибку в журнале приложения
- Скрыть полезную информацию при возникновении неожиданной ошибки и предоставить полезную информацию вызывающему пользователю, а также эффективно связаться со службой поддержки.
Эти несколько строк кода позволяют добиться многого, и их можно еще больше адаптировать под ваши нужды, например, добавить интернационализацию сообщений об ошибках.
После того как мы только что установили базовый уровень обработки ошибок, мы можем использовать отладчик IDE для обнаружения и решения проблем. Для этого нам нужно отредактировать скрипт package.json
приложения:
1 |
|
Это запустит процесс Node.js в режиме отладки. При запуске мы прочитаем сообщение в консоли, которое сообщит нам, как подключить отладчик:
1 2 3 |
|
Используя VS Code в качестве IDE, мы можем создать файл .vscode/launch.json
, как показано ниже:
1 2 3 4 5 6 7 8 9 10 11 |
|
Нажатие F5 подключит наш отладчик к процессу Node.js, и мы будем готовы установить точки останова и проверить, что происходит в нашем приложении, чтобы исправить это.
Теперь вы убедились, как быстро и легко можно настроить Fastify для выполнения сложных задач!
Вы улучшили свой контроль над кодовой базой, и теперь мы увидим новую технику управления конфигурацией приложения. По мере развития этих навыков вы освоите настройку лесов проекта.
Совместное использование конфигурации приложения между плагинами¶
В разделе «Загрузка конфигураций плагинов» обсуждалось, как плагин может получить доступ к конфигурации приложения. В этом случае плагин обращается к обычному объекту fastify.secret
для доступа к переменным окружения.
Конфигурация может развиваться и становиться более сложной. Но если бы вы просто хотели централизовать все настройки плагина в отдельном плагине, как бы вы могли это сделать?
Мы можем модифицировать плагин config.js
и переместить его в директорию configs/
. Таким образом, мы больше не будем загружать его автоматически. Затем мы можем интегрировать конфигурацию @fastify/mongodb
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
В фрагменте кода вы можете увидеть следующие основные изменения:
- Плагин экспонирует интерфейс
async
. - Для выполнения плагина ожидается реестр плагина
@fastify/env
. Таким образом,fastify.secrets
будет немедленно доступен. - К экземпляру Fastify добавлен новый декоратор.
- У плагина больше нет параметра name. Поскольку мы собираемся загружать его вручную, имя не обязательно. В любом случае, это хорошая практика, чтобы оставить его: мы хотим показать вам, что мы ломаем мосты между файлами
mongo-data-source.js
иconfig.js
.
Эти изменения нарушают нашу настройку по следующим причинам:
- Файл
config.js
не загружается - Файл
mongo-data-source.js
полагается наfastify.secrets
.
Чтобы исправить это, нам нужно отредактировать app.js
, как показано ниже:
1 2 |
|
Эти строки должны быть добавлены после конфигурации схем автозагрузки, потому что мы проверяем переменную окружения через схему schema:dotenv
.
После этого мы можем обновить параметры автозагрузки плагинов следующим образом:
1 2 3 4 5 6 7 |
|
Наконец, мы можем исправить файл mongo-data-source.js
, удалив из него много кода:
1 2 3 |
|
Как видите, он стал намного легче. Мы также убрали параметр dependencies, потому что не хотим обращаться к декоратору fastify.secret
.
Это изменение существенно повлияло на логику кода. При таком рестайле кода файл mongo-data-source.js
отделен от остальной части приложения, поскольку все настройки предоставляются входным аргументом opts
. Этот объект предоставляется плагином @fastify/autoload
, отображая параметр options
.
Теперь у вас есть полные и прочные знания о конфигурации и о том, как лучше всего ею управлять. Вы можете использовать предыдущий пример кода, чтобы уверенно настраивать плагины и играть внутри плагина автозагрузки. Вы увидите, что исходный код в репозитории книги использует первое решение, которое мы рассмотрели в разделе «Загрузка конфигураций плагинов».
Чтобы завершить проект, нам нужно добавить еще несколько функций, которые являются ключевыми элементами для того, чтобы считать эту базовую структуру прочной и готовой к использованию в процессе разработки. Мы узнаем о некоторых новых плагинах, которые добавят эти недостающие возможности в наше приложение.
Использование плагинов Fastify¶
Структура проекта почти завершена. Экосистема Fastify помогает нам улучшить нашу кодовую базу строительных лесов с помощью набора плагинов, о которых вы захотите узнать. Давайте узнаем о них и добавим их в папку plugins/
.
Как получить обзор проекта¶
Документирование полного списка всех конечных точек приложения — утомительная задача, но кто-то в команде все равно должен это делать. К счастью, у Fastify есть решение для этого: плагин @fastify/swagger
.
Вы можете интегрировать его, создав новый файл plugins/swagger.js
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Предыдущий код зарегистрирует плагин. Он автоматически создаст веб-страницы http://localhost:3000/docs
, на которых будут перечислены все конечные точки приложения. Обратите внимание, что документация будет опубликована только в том случае, если среда не находится в производстве, из соображений безопасности. Интерфейсы API должны быть доступны только тем людям, которые их используют.
Обратите внимание, что Swagger (OAS 2.0) и бывшая OpenAPI Specification (OAS 3.0) определяют стандарт для создания документации API. Возможно, вам будет интересно узнать больше об этом.
Как быть доступным¶
Одной из самых распространенных проблем при реализации API бэкенда является настройка кросс-оригинального обмена ресурсами (CORS). Вы столкнетесь с этой проблемой, когда фронтенд попытается вызвать ваши конечные точки из браузера, а запрос будет отклонен.
Чтобы решить эту проблему, вы можете установить плагин @fastify/cors
:
1 2 3 4 5 6 |
|
Обратите внимание, что код примера настроен таким образом, чтобы ваши API были доступны любому клиенту. Изучите этот аспект подробнее, чтобы правильно настроить этот плагин для ваших будущих приложений.
Резюме
В этой главе вы создали полноценный проект Fastify, который станет базовой структурой для ваших следующих проектов. Теперь вы можете начать новое приложение Fastify с нуля. Вы также можете контролировать все конфигурации, необходимые вашей кодовой базе, независимо от того, где они хранятся.
Мы рассмотрели некоторые из наиболее используемых и полезных плагинов Fastify для улучшения структуры и эргономики проекта. Теперь вы знаете, как их использовать и настраивать, и что их комбинации могут быть бесконечными.
Прочная и чистая структура, которую мы создали на данный момент, будет развиваться на протяжении всей книги. Прежде чем уделять больше времени структуре, нам нужно понять бизнес-логику приложения. Итак, приготовьтесь к следующей главе, в которой мы обсудим, как построить RESTful API.