Система плагинов и процесс загрузки¶
Плагин Fastify — это важный инструмент в распоряжении разработчика. Каждая функциональность, кроме корневого экземпляра сервера, должна быть обернута в плагин. Плагины — это ключ к многократному использованию, совместному использованию кода и достижению правильной инкапсуляции между экземплярами Fastify.
Корневой экземпляр Fastify будет загружать все зарегистрированные плагины асинхронно, следуя порядку регистрации во время последовательности загрузки. Кроме того, плагин может зависеть от других, и Fastify проверяет эти зависимости и завершает последовательность загрузки с ошибкой, если обнаруживает их отсутствие.
Эта глава начинается с объявления простого плагина, а затем, шаг за шагом, добавляет к нему новые слои. Мы узнаем, почему параметр options
имеет решающее значение и как метод регистрации Fastify использует его во время последовательности загрузки. Последняя цель — понять, как плагины взаимодействуют друг с другом благодаря инкапсуляции.
Чтобы разобраться в этой непростой теме, мы познакомимся с некоторыми основными понятиями:
- Что такое плагин?
- Параметр
options
- Инкапсуляция
- Последовательность загрузки
- Обработка ошибок загрузки и плагинов
Технические требования
Чтобы изучить эту главу, вам понадобится следующее:
- Текстовый редактор, например VS Code
- Рабочая установка Node.js v18
- Доступ к оболочке, например Bash или CMD.
Все примеры кода в этой главе можно найти на GitHub по адресу https://github.com/PacktPublishing/Accelerating-Server-Side-Development-with-Fastify/tree/main/Chapter%202.
Что такое плагин?¶
Fastify плагин — это компонент, который позволяет разработчикам расширять и добавлять функциональные возможности в свои серверные приложения. Одними из наиболее распространенных вариантов использования плагина являются обработка подключения к базе данных или расширение возможностей по умолчанию — например, разбор запроса или сериализация ответа.
Благодаря своим уникальным свойствам плагины являются основными строительными блоками нашего приложения. Среди наиболее заметных свойств можно выделить следующие:
- Плагин может регистрировать другие плагины внутри себя.
- Плагин по умолчанию создает новую область видимости, которая наследуется от родительской. Это поведение распространяется и на его дочерние области и так далее, хотя использование контекста родителя все еще возможно.
- Плагин может получать параметр
options
, который можно использовать для управления его поведением, построением и возможностью повторного использования. - Плагин может определять маршруты с диапазоном и префиксом, что делает его идеальным маршрутизатором.
На данный момент должно быть понятно, что там, где в других фреймворках есть различные сущности, такие как промежуточное ПО, маршрутизаторы и плагины, в Fastify есть только плагины. Таким образом, даже если плагины Fastify печально известны своей сложностью в освоении, мы можем использовать наши знания практически для всего, как только поймем их!
Мы должны начать наше путешествие в мир плагинов с самого начала. В следующих двух разделах мы узнаем, как объявить и использовать наш первый плагин.
Создание нашего первого плагина¶
Давайте посмотрим, как создать наш первый фиктивный плагин. Чтобы его можно было протестировать, нам нужно зарегистрировать его в корневом экземпляре. Так как мы сосредоточимся на плагинах, мы сохраним наш сервер Fastify как можно более простым, повторив базовый сервер, который мы видели в Глава 1, и внеся лишь одно небольшое изменение. В файле index.cjs
мы объявим наш плагин в строке, а затем посмотрим, как использовать отдельные файлы для разных плагинов:
1 2 3 4 5 6 7 8 9 |
|
После создания корневого экземпляра Fastify ([1]
) мы добавляем наш первый плагин. Для этого мы передаем функцию определения плагина в качестве первого аргумента ([2]
) в register
. Эта функция получает новый экземпляр Fastify, наследуя все, что было у корневого экземпляра до этого момента, и аргумент options
.
Аргумент options
В данный момент мы не используем аргумент options
. Однако в следующем разделе мы узнаем о его важности и о том, как его использовать.
Наконец, мы вызываем метод ready ([3]
). Эта функция запускает последовательность загрузки и возвращает Promise
, который будет выполнен, когда все плагины будут загружены. На данный момент в наших примерах нам не нужен прослушивающий сервер, поэтому вместо него допустимо вызвать ready
. Более того, listen
внутренне ожидает события .ready()
, которое будет отправлено в любом случае.
Порядок загрузки
Последовательность загрузки Fastify — это ряд операций, которые выполняет основной экземпляр Fastify для загрузки всех плагинов, хуков и декораторов. Если ошибок не возникнет, сервер запустится и будет слушать указанный порт. Подробнее об этом мы расскажем в отдельном разделе.
Давайте запустим предыдущий фрагмент в терминале и посмотрим на результат:
1 2 3 4 5 |
|
Давайте разберем выполнение нашего сниппета:
- Создается экземпляр Fastify с включенным логгером.
- В корневом экземпляре Fastify регистрируется наш первый плагин, и выполняется код внутри функции плагина.
- Экземпляр
Promise
, возвращаемый методомready
, разрешается после отправки событияready
.
Этот процесс Node.js завершается без каких-либо ошибок; это происходит потому, что мы использовали ready
вместо метода listen
. Метод объявления, который мы только что рассмотрели, является лишь одним из двух возможных. В следующем разделе мы рассмотрим другой, поскольку многие примеры в Интернете используют именно его.
Альтернативная сигнатура функции плагина¶
Как и почти в каждом API, Fastify имеет два альтернативных способа объявить функцию плагина. Единственное различие заключается в наличии или отсутствии третьего аргумента колбек-функции, обычно называемого done
. Следуя старому доброму шаблону колбек-функции Node.js, эта функция должна быть вызвана, чтобы сообщить, что загрузка текущего плагина завершена без ошибок.
С другой стороны, если во время загрузки произошла ошибка, done
может получить необязательный аргумент err
, который может быть использован для прерывания последовательности загрузки. То же самое происходит и в мире промисов — если промис разрешен, плагин загружается; если промис отклонен, отказ будет передан корневому экземпляру, и процесс загрузки завершится.
Давайте посмотрим фрагмент callbacks.cjs
, в котором используется определение плагина в стиле callback:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Первый плагин загружается без ошибок, благодаря вызову done
без переданных аргументов ([1]
). С другой стороны, второй вылетает до вызова done()
([2]
). Здесь мы видим, как важно перехватывать ошибки и вызывать done(err)
([3]
); без этого вызова Fastify будет думать, что в процессе регистрации не произошло никаких ошибок, и продолжит последовательность загрузки!
Независимо от того, какой стиль вы используете, плагины всегда являются асинхронными функциями, и каждый паттерн имеет свою обработку ошибок. Важно не смешивать эти два стиля и быть последовательным при выборе того или иного:
- Вызов
done
, если используется стиль обратного вызова - Разрешение/отклонение промиса, если мы выбрали стиль, основанный на промисах.
Мы еще вернемся к этому аргументу в разделе Ошибки загрузки, где мы увидим, что произойдет, если мы неправильно используем промисы и обратные вызовы.
Примеры, основанные на промисах
В этой книге используются сигнатуры на основе промисов, так как их легче выполнять благодаря ключевым словам async
/await
.
В этом разделе мы изучили два различных метода объявления нашего первого плагина и его регистрации в экземпляре Fastify. Однако мы только нащупали грань возможностей плагинов Fastify. В следующем разделе мы рассмотрим параметр options
и то, как он может пригодиться при работе с разделяемыми функциями.
Изучение параметра options¶
В этом разделе мы подробно рассмотрим необязательный параметр options и то, как мы можем разрабатывать многократно используемые плагины. Функция объявления плагина — это не что иное, как фабричная функция, которая вместо того, чтобы возвращать новую сущность, как это обычно делают фабричные функции, добавляет поведение в экземпляр Fastify. Если мы посмотрим на это так, то можем считать параметр options
аргументами нашего конструктора.
Для начала вспомним сигнатуру функции объявления плагина:
1 |
|
Как мы можем передать наши пользовательские аргументы в параметр options
? Оказывается, у метода register
есть и второй параметр. Таким образом, объект, который мы используем в качестве аргумента, будет передан Fastify в качестве параметра options
плагина:
1 |
|
Теперь, внутри функции myPlugin
, мы можем получить доступ к этому значению, просто используя options.first
.
Стоит отметить, что Fastify оставляет за собой три специфические опции, которые имеют особое значение:
prefix
logLevel
logSerializers
Опции журнала
Мы рассмотрим logLevel
и logSerializer
в отдельной главе. Здесь же мы сосредоточимся только на prefix
и пользовательских опциях.
Имейте в виду, что в будущем могут быть добавлены дополнительные зарезервированные опции. Следовательно, разработчикам следует всегда рассматривать возможность использования пространства имен, чтобы избежать будущих коллизий, даже если это не является обязательным. Пример можно увидеть в фрагменте options-namespacing.cjs
:
1 2 3 4 5 6 7 8 9 10 11 |
|
Вместо того чтобы добавлять наши пользовательские свойства на верхнем уровне параметра options
, мы сгруппируем их в пользовательском ключе, что снизит вероятность коллизии имен в будущем. В большинстве случаев передача объекта — это хорошо, но иногда нам нужна большая гибкость.
В следующем разделе мы узнаем больше о типе параметров options
и используем его для выполнения более сложных задач.
Тип параметра options¶
До сих пор мы видели, что параметр options
— это объект с некоторыми зарезервированными и пользовательскими свойствами. Но options
также может быть функцией, которая возвращает объект. Если передана функция, Fastify вызовет ее и передаст возвращаемый объект в качестве параметра options
плагину. Чтобы лучше понять это, в фрагменте options-function.cjs
мы перепишем предыдущий пример, используя функцию вместо объекта:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
На первый взгляд, не должно быть понятно, зачем нам этот альтернативный тип для опций, но взгляд на сигнатуру указывает нам на правильное направление — она получает родительский экземпляр Fastify в качестве единственного аргумента.
Рассмотрение примера options-function-parent.cjs
должно прояснить, как мы можем получить доступ к родительским опциям:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
Сначала мы декорируем корневой экземпляр пользовательским свойством ([1]
), а затем передаем его в качестве значения нашему плагину ([2]
). В реальном сценарии mySpecialProp
может быть соединением с базой данных, любым значением, зависящим от окружения, или даже тем, что добавил другой плагин.
Опция префикса¶
В начале этой главы мы узнали, что можем определять маршруты внутри плагина. Опция prefix
пригодится нам здесь, потому что она позволяет добавить пространство имен к объявлению маршрута. Существует несколько вариантов использования этой опции, и большинство из них мы рассмотрим в более продвинутых главах, но здесь стоит упомянуть пару из них:
- Поддержка различных версий наших API
- Повторное использование одного и того же плагина и определения маршрутов для различных приложений, каждый раз предоставляя другую точку монтирования
Сниппет users-router.cjs
поможет нам лучше понять этот параметр. Прежде всего, мы определяем плагин в отдельном файле и экспортируем его:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Декларация маршрута
Так как мы фокусируемся на плагинах и стараемся сделать примеры как можно короче, в этом разделе используется стиль «короткого объявления» для маршрутов. Более того, схемы и некоторые другие важные опции также отсутствуют. Мы увидим в этой книге, что существуют гораздо лучшие, формально корректные и полные варианты объявления маршрутов.
Мы определяем два маршрута; первый возвращает все элементы нашей коллекции ([1]
), а второй позволяет нам добавлять записи в массив пользователя ([2]
). Поскольку мы не хотим усложнять обсуждение, в качестве источника данных мы используем массив; он определяется на корневом экземпляре Fastify, как мы узнаем из следующего фрагмента. В реальном сценарии это, конечно же, был бы какой-то доступ к базе данных. Наконец, в пункте [3]
мы добавляем ко всем определенным нами маршрутам префикс с пространством имен пользователя, как это принято в RESTful.
Теперь, когда мы определили пространство имен, мы можем импортировать и добавить этот router к корневому экземпляру в index-with-router.cjs
. Мы также можем использовать опцию prefix для присвоения уникального пространства имен нашим маршрутам и обработки версионности API:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
|
Прежде всего, мы украшаем корневой экземпляр Fastify свойством users
([1]
); как и ранее, он будет выступать в качестве нашей базы данных в этом примере. На [2]
мы регистрируем маршрутизатор нашего пользователя с префиксом v1
. Затем мы регистрируем новый плагин с объявлением inline ([3]
), используя пространство имен v2
(каждый маршрут, добавленный в этот плагин, будет иметь пространство имен v2
). На [4]
мы регистрируем маршруты того же пользователя во второй раз, а также добавляем новый объявленный маршрут delete
([5]
).
Метод printRoutes
Этот метод может быть полезен во время разработки. Если мы не уверены в полном пути наших маршрутов, он напечатает их все за нас!
Благодаря [6]
мы можем обнаружить все смонтированные нами маршруты:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Действительно, префиксные определения маршрутов — это очень интересная функция. Она позволяет нам повторно использовать одни и те же объявления маршрутов более одного раза. Это один из важнейших элементов многократного использования плагинов Fastify. В нашем примере мы используем всего два уровня вложенности префиксов, но на практике ограничений нет. Мы избегаем дублирования кода, дважды используя одни и те же определения GET
и POST
и добавляя только один новый маршрут DELETE
к одному и тому же пространству имен пользователя, когда это необходимо.
В этом разделе мы рассмотрели, как использовать параметр options
для достижения лучшей многоразовости плагина и управления его регистрацией в экземпляре Fastify. Этот параметр имеет несколько зарезервированных свойств, используемых для указания Fastify, как обращаться с регистрируемым плагином. Более того, мы можем добавить столько свойств, сколько необходимо плагину, зная, что Fastify передаст их на этапе регистрации.
Поскольку мы уже использовали инкапсуляцию в этом разделе, даже не подозревая об этом, она станет темой следующего раздела.
Понимание инкапсуляции¶
До сих пор мы написали несколько плагинов. Мы достаточно хорошо знаем, как они устроены и какие аргументы получает плагин. Но нам еще предстоит обсудить одну недостающую вещь — концепцию инкапсуляции.
Давайте вспомним сигнатуру определения функции плагина:
1 |
|
Как мы уже знаем, первым параметром является экземпляр Fastify. Этот экземпляр создается заново и наследуется от внешней области видимости. Предположим, что к корневому экземпляру было добавлено что-то, например, с помощью декоратора. В этом случае он будет присоединен к экземпляру Fastify плагина, и его можно будет использовать, как если бы он был определен внутри текущего плагина.
Однако обратное не верно. Если мы добавляем функциональные возможности внутри плагина, то они будут видны только в контексте текущего плагина.
Контекст против области видимости
Прежде всего, давайте рассмотрим определения обоих терминов. Контекст указывает на текущее значение неявной переменной метода 'this'
. Область видимости (scope) — это набор правил, управляющих видимостью переменной с точки зрения функции. В сообществе Fastify эти два термина используются как взаимозаменяемые и относятся к экземпляру Fastify, с которым мы сейчас работаем. По этой причине в этой книге мы будем использовать оба слова, означающие одно и то же.
Давайте рассмотрим пример в файле encapsulation.cjs
:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Запуск этого фрагмента приведет к такому результату:
1 2 3 4 5 |
|
Сначала мы декорируем корневой экземпляр ([1]
), добавляя к нему строку. Затем внутри myPlugin
мы печатаем декорированное значение корневого экземпляра и добавляем новое свойство к экземпляру Fastify. В теле определения плагина мы выводим оба значения в консоль, чтобы убедиться, что они заданы ([2]
). Наконец, мы видим, что после того, как приложение Fastify готово, на корневом уровне мы можем получить доступ к добавленному нами значению только вне нашего плагина ([3]
). Но что же здесь произошло? В обоих случаях мы использовали метод .decorate
для добавления нашего значения в экземпляр. Почему оба значения видны в myPlugin
, но на верхнем уровне видно только корневое значение? Так и должно быть, и это происходит благодаря инкапсуляции — Fastify создает новый контекст каждый раз, когда входит в новый плагин. Мы называем эти новые контексты дочерними контекстами. Дочерний контекст наследует только от родительского, и все, что добавляется в дочерний контекст, не будет видно ни в родительском, ни в контекстах братьев и сестер. Уровень аннексии родитель-ребенок бесконечен, и мы можем иметь контексты, которые являются детьми для своих родителей и родителями для своих детей.
К сущностям, на которые влияет область видимости, относятся:
- Декораторы
- Хуки
- Плагины
- маршруты.
Как мы видим, поскольку маршруты зависят от контекста, мы уже использовали инкапсуляцию в предыдущем разделе, даже если тогда мы этого не знали. Мы дважды зарегистрировали один и тот же маршрут на одном и том же корневом экземпляре, но с разными префиксами. В реальных приложениях широко распространены более сложные сценарии с несколькими дочерними и внучатыми контекстами. Мы можем использовать следующую диаграмму для рассмотрения более сложного примера:
На рисунке 2.1 мы видим довольно сложный сценарий. У нас есть корневой экземпляр Fastify, который регистрирует два корневых плагина. Каждый корневой плагин создает новый дочерний контекст, в котором мы снова можем объявить и зарегистрировать столько плагинов, сколько захотим. Вот и все — мы можем иметь бесконечную вложенность для наших плагинов, и каждый уровень глубины будет создавать новый инкапсулированный контекст.
Однако Fastify оставляет полный контроль над инкапсуляцией разработчику, и мы рассмотрим, как управлять этим в следующем разделе.
Обработка контекста¶
До сих пор контекст, на который мы опирались, был основан на поведении Fastify по умолчанию. В большинстве случаев это работает, но есть случаи, когда нам нужна большая гибкость. Если нам нужно разделить контекст между соседями или изменить родительский контекст, мы все равно можем это сделать. Это пригодится для более сложных плагинов, например тех, которые работают с соединениями с базами данных.
В нашем распоряжении есть несколько инструментов:
- Скрытое свойство
skip-override
. - пакет
fastify-plugin
.
Мы начнем с использования skip-override
, а затем перейдем к fastify-plugin
; хотя они могут быть использованы для достижения одного и того же результата, последний имеет дополнительные возможности.
Здесь мы будем использовать тот же пример, что и раньше, но теперь добавим скрытое свойство skip-override
, чтобы обеспечить доступ к декорированной переменной в области видимости верхнего уровня. Сниппет skip-override.cjs
поможет нам понять его использование:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
В этом фрагменте кода есть только одно существенное изменение по сравнению с предыдущим: мы используем Symbol.for('skip-override')
, чтобы предотвратить создание Fastify нового контекста ([1]
). Одного этого достаточно, чтобы корень был декорирован переменной fastify.myPlugin
([2]
). Мы видим, что декоратор также доступен из внешней области видимости ([3]
); вот результат:
1 2 3 4 5 |
|
Symbol.for
Вместо того чтобы использовать просто свойство skip-override
в виде строки, Fastify использует Symbol.for
, чтобы скрыть его и избежать коллизий имен. Когда символ создается, он добавляется в реестр символов во время выполнения. При вызове метод .for
проверяет, присутствует ли уже символ, к которому мы пытаемся получить доступ. Если нет, он сначала создает его, а затем возвращает. Подробнее о символах.
fastify-plugin¶
Использование skip-override
вполне нормально, но есть лучший способ контролировать поведение инкапсуляции. Как и многое в мире Fastify, модуль fastify-plugin
— это не более чем функция, которая оборачивает плагин, добавляет к нему некоторые метаданные и возвращает их.
Основные функции, включенные в fastify-plugin
, следующие:
- Добавление свойства
skip-override
за нас - Предоставление имени для плагина, если оно не передано явно
- Проверка минимальной версии Fastify
- Прикрепление предоставленных пользовательских метаданных к возвращаемому плагину.
Мы должны использовать fastify-plugin
каждый раз, когда у нас есть плагин, который должен добавить поведение в родительский контекст. Чтобы иметь возможность использовать его, нам сначала нужно установить его из реестра npmjs.com
:
1 |
|
Его сигнатура напоминает метод .register
. Он принимает два параметра:
- Точное определение функции плагина, как мы уже видели
- Необязательный объект
options
с четырьмя необязательными свойствами:
name
-
Мы можем использовать это строковое свойство, чтобы дать имя нашему плагину. Присвоение имени нашему плагину очень важно, так как позволяет передать его в массив зависимостей, как мы скоро увидим. Кроме того, если во время загрузки произойдет что-то непредвиденное, Fastify будет использовать это свойство для более точной трассировки стека.
fastify
-
Мы можем использовать это строковое свойство для проверки минимальной версии Fastify, которая необходима нашему плагину для корректной работы. Это свойство принимает в качестве значения любой допустимый диапазон SemVer.
decorators
-
Мы можем использовать этот объект, чтобы убедиться, что родительский экземпляр был украшен свойствами, которые мы используем в нашем плагине.
dependencies
-
Если наш плагин зависит от функциональности других плагинов, мы можем использовать этот массив строк для проверки соблюдения всех зависимостей — значениями являются имена плагинов. Мы более подробно рассмотрим это свойство в следующей главе, поскольку оно связано с процессом загрузки.
SemVer
SemVer расшифровывается как Semantic Versioning, и его цель — помочь разработчикам управлять своими зависимостями. Она состоит из трех чисел, разделенных двумя точками, по схеме MAJOR.MINOR.PATCH
. Если в код добавляются какие-либо разрушающие изменения, то число MAJOR
должно быть увеличено. С другой стороны, если добавлены новые возможности, но нет никаких ломающих изменений, то номер MINOR
должен быть увеличен. Наконец, если все изменения делаются только для исправления ошибок, то номер PATCH
увеличивается.
Соглашение об именовании fp
В сообществе Fastify принято импортировать пакет fastify-plugin
как fp
, потому что это короткое, но многозначительное имя переменной. В этой книге мы будем использовать это соглашение каждый раз, когда будем иметь с ним дело.
Давайте рассмотрим пример fp-myplugin.cjs
, в котором используются необязательные свойства метаданных:
1 2 3 4 5 6 7 8 9 10 11 |
|
В [1]
мы передаем myPlugin
в качестве первого аргумента и экспортируем обернутый плагин в качестве экспорта по умолчанию. Второй аргумент — это объекты опций:
- Мы задаем явное имя нашему плагину (
[2]
). - Мы устанавливаем минимальную версию Fastify на
4.x
([3]
). - Свойство
decorators
принимает ключи сущностей, которые могут быть украшены —fastify
,request
иreply
. Здесь мы проверяем, установлено ли у родительского экземпляра Fastify свойствоroot
([4]
).
В fastify-plugin
объект options
прикрепляется к нашему плагину в уникальном скрытом свойстве под названием Symbol.for('plugin-meta')
. Fastify будет искать это свойство во время регистрации плагина, и если найдет, то будет действовать в соответствии с его содержимым.
В фрагменте fp-myplugin-index.cjs
мы импортируем и регистрируем наш плагин, проверяя различные результаты:
1 2 3 4 5 6 7 8 9 |
|
Сначала мы украшаем корневой экземпляр fastify строковым свойством ([1]
). Затем мы регистрируем наш плагин ([2]
). Помните, что мы указали корневое свойство как обязательное в метаданных fastify-plugin
. Перед регистрацией myPlugin
, Fastify проверяет, объявлено ли свойство в родительском контексте, и если оно там есть, то переходит к процессу загрузки. Наконец, поскольку fastify-plugin
добавляет свойство skip-override
для нас, мы можем получить доступ к свойству myPlugin
в корневой области видимости без каких-либо проблем ([3]
). Давайте посмотрим на результат этого фрагмента:
1 2 3 4 |
|
Все работает, как и ожидалось!
Теперь, посмотрев на fp-myplugin-index-missing-root.cjs
, мы можем проверить, что произойдет, если декоратор root
будет отсутствовать у экземпляра root
, как он был объявлен в fp-myplugin-index.cjs
по адресу [1]
, как было показано ранее:
1 2 3 4 5 6 7 8 |
|
Запуск этого файла приведет к сбою и прерыванию процесса загрузки:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
Мы видим, что Fastify использовал имя myPlugin
в ошибке FST_ERR_PLUGIN_NOT_PRESENT_IN_IN_STANCE
, что помогает нам понять проблему. Это очень полезно при работе с десятками зарегистрированных плагинов.
Свойство name
Мы видели, что параметр fastify-plugin
options
является необязательным, как и его свойство name
. Но что произойдет, если мы не передадим его? Оказывается, имя будет сгенерировано и прикреплено к плагину. Если наш плагин является именованной функцией (содержит свойство name), то оно будет использовано в качестве имени плагина. В противном случае следующим кандидатом будет имя файла. В обоих случаях будет добавлена стандартная часть — auto-{N}
. N
— это автоматически увеличивающееся число, начинающееся с 0. Добавляемая часть нужна для того, чтобы избежать коллизий в именах, поскольку разработчик не предоставляет эти имена, а Fastify не хочет блокировать процесс загрузки из-за непреднамеренных коллизий. Важно помнить, что присвоение явного имени нашим плагинам считается лучшей практикой.
В этом разделе мы рассмотрели одну из основных, но самых сложных концепций Fastify — капсуляцию. Мы узнали, как Fastify обеспечивает поведение по умолчанию, которое подходит для наиболее распространенных случаев, но дает разработчику полную власть, когда это необходимо. Кроме того, такие инструменты, как skip-override
и fastify-plugin
, являются основополагающими при работе с более сложными сценариями, где контроль над контекстом имеет решающее значение.
Но как Fastify узнает правильный порядок регистрации плагинов? Является ли этот процесс детерминированным? Мы узнаем это и многое другое о последовательности загрузки в следующем разделе.
Изучение последовательности загрузки¶
В предыдущем разделе мы узнали, что плагин — это просто асинхронная функция с четко определенными параметрами. Мы также увидели, что плагины Fastify — это основная сущность, которую мы используем для добавления возможностей и функций в наши приложения. В этом разделе мы узнаем, что такое последовательность загрузки, как плагины взаимодействуют друг с другом и как Fastify обеспечивает соблюдение всех ограничений разработчика, прежде чем запустить HTTP-сервер.
Прежде всего, необходимо сказать, что последовательность загрузки Fastify также является асинхронной. Fastify загружает каждый плагин, добавленный с помощью метода register
, по очереди, соблюдая порядок регистрации. Fastify начинает этот процесс только после вызова .listen()
или .ready()
. После этого он ждет выполнения всех промисов (или вызова всех завершенных обратных вызовов, если используется стиль обратных вызовов), а затем выдает событие готовности. Если мы прошли этот путь, то можем быть уверены, что наше приложение работает и готово к приему входящих запросов.
Avvio
За процесс загрузки отвечает Avvio
, библиотека, которую можно использовать и отдельно. Avvio
обрабатывает все сложности, связанные с асинхронной загрузкой — обработку ошибок, порядок загрузки, диспетчеризацию события ready, позволяя разработчикам гарантировать, что приложение будет запущено после того, как все загрузится без ошибок.
Эта почему-то недооцененная и переосмысленная функция на самом деле является одной из самых мощных. Асинхронный процесс загрузки дает несколько ключевых преимуществ:
- После разрешения промиса
listen
/ready
мы уверены, что все плагины загружены без ошибок и последовательность загрузки завершена. - Это позволяет нам всегда регистрировать наши плагины детерминированным способом, обеспечивая порядок загрузки и закрытия.
- Если во время регистрации плагина возникнет ошибка, она прервёт последовательность загрузки, что позволит разработчикам действовать соответствующим образом.
Несмотря на то, что процесс загрузки Fastify очень многогранен, он обрабатывает практически все из коробки. Открытый API невелик — в нем всего два метода! Первый — это метод register
, который мы уже много раз использовали. Второй — after
, и, как мы увидим, он используется редко, потому что register
интегрирует свой функционал для большинства случаев использования. Давайте рассмотрим их подробнее.
thenable
Несколько методов экземпляра Fastify возвращают thenable
, объект, который имеет метод then()
. Самое важное свойство thenable
заключается в том, что цепочки промисов и async
/await
работают нормально. Использование thenable
вместо настоящего промиса имеет одно основное преимущество — оно позволяет одновременно ожидать объект и связывать его с другими вызовами в цепочке, что позволяет свободно использовать API.
Метод регистрации экземпляра¶
На данный момент мы почти все знаем об этом основном методе. Начнем с его сигнатуры:
1 |
|
Параметр options
является основополагающим для передачи пользовательских опций нашим плагинам во время регистрации, и это ворота для многократного использования плагинов. Например, мы можем зарегистрировать один и тот же плагин, работающий с базой данных, с другой строкой подключения или, как мы видели, использовать другую опцию префикса для регистрации одного и того же обработчика на разных путях маршрута. Более того, если мы не используем модуль fastify-plugin
или скрытое свойство skip-override
, register
создает новый контекст, изолируя все, что делает наш плагин.
Но что представляет собой возвращаемое значение register
? Это thenable
экземпляр Fastify, и он приносит два существенных преимущества:
- Он добавляет возможность цепочки вызовов методов экземпляра.
- При необходимости мы можем использовать
await
при вызовеregister
.
Сначала мы рассмотрим цепочку методов экземпляра Fastify в файле register-chain.cjs
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Мы определяем три фиктивных плагина, которые при регистрации делают только одну вещь — записывают в журнал имя своей функции ([1]
). Затем, в пункте [2]
, мы регистрируем их оба, используя цепочку методов. Первый вызов .register(plugin1)
возвращает экземпляр Fastify, что позволяет выполнить последующий вызов, .register(plugin2)
. Мы можем использовать цепочку методов с большинством методов экземпляра. Мы можем разорвать цепочку вызовов и вызвать регистр непосредственно на экземпляре ([3]
).
Ожидание регистра¶
Есть еще одна вещь, которую следует сказать о методе register
. Мы можем использовать оператор await
после каждого вызова register
, чтобы дождаться завершения регистрации. Оператор await
можно использовать после каждого вызова register
, чтобы дождаться загрузки всех плагинов, добавленных до этого момента, как показано в следующем фрагменте await-register.cjs
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
|
Чтобы понять, что происходит, запустим этот фрагмент:
1 2 3 4 5 6 |
|
Поскольку этот пример довольно длинный и сложный, давайте разобьем его на более мелкие части:
- Мы объявляем три плагина
[1]
, каждый из которых добавляет по одному декоратору к экземпляру Fastify - Мы используем
fastify-plugin
для декорирования корневого экземпляра и для регистрации этих плагинов ([2]
и[3]
), как мы узнали в предыдущем разделе. - Даже если наши плагины идентичны, мы видим, что результаты
console.log
различны; два плагина, зарегистрированные с помощью оператораawait
([2]
), уже украсили корневой экземпляр ([4]
) - И наоборот, последний плагин, зарегистрированный без
await
([3]
), добавляет свой декоратор только после разрешения промиса события ready ([5]
и[6]
)
Теперь должно быть понятно, что если мы также хотим, чтобы третий декоратор был доступен до полной готовности приложения, достаточно добавить оператор await на [3]
!
В заключение можно сказать, что в большинстве случаев нам не нужно ждать при регистрации наших плагинов. Единственным исключением может быть случай, когда нам нужен доступ к чему-то, что плагин сделал во время загрузки. Например, у нас есть плагин, который подключается к внешнему источнику, и мы хотим быть уверены, что он подключен, прежде чем продолжить последовательность загрузки.
Метод after
экземпляра¶
Аргумент функции метода after
вызывается Fastify автоматически, когда все плагины, добавленные до этого момента, завершают загрузку. В качестве единственного параметра он определяет колбек-функцию с необязательным аргументом ошибки:
1 |
|
Если мы не передадим обратный вызов, то after
вернет объект thenable
, который можно ожидать. В этой версии ожидание after
можно заменить на ожидание register
, поведение будет таким же. Поэтому в примере after.cjs
мы увидим версию метода after
в стиле callback:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
|
Мы объявляем и затем регистрируем те же три плагина, что и в предыдущих примерах ([1]
). На [2]
мы начинаем нашу цепочку вызовов методов, добавляя вызов after
([3]
) перед последним register
([4]
). Внутри обратного вызова after
мы убеждаемся, если ошибка равна null, что первые два плагина загружены правильно; на самом деле, декораторы имеют правильные значения. Если мы запустим этот фрагмент, то получим тот же результат, что и в предыдущем случае.
Fastify гарантирует, что все обратные вызовы after
будут вызваны до того, как будет отправлено событие готовности приложения. Этот метод может быть полезен, если мы предпочитаем цепочку методов экземпляра, но при этом нам нужен контроль над последовательностью загрузки.
Порядок деклараций¶
Даже если Fastify соблюдает правильный порядок регистрации плагинов, мы все равно должны следовать некоторым хорошим практикам, чтобы поддерживать более последовательное и предсказуемое поведение загрузки. Если во время загрузки происходит что-то ужасное, использование следующего порядка деклараций поможет выяснить, в чем проблема:
- Плагины, установленные с
npmjs.com
. - Наши плагины
- Декораторы
- Хуки
- Сервисы, маршруты и так далее
Такой порядок объявления гарантирует, что все вещи, объявленные в текущем контексте, будут доступны. Поскольку в Fastify все может и должно быть определено внутри плагина, мы должны повторять предыдущую структуру на каждом уровне регистрации.
Последовательность загрузки — это серия операций, которые Fastify выполняет для загрузки всех зарегистрированных плагинов и запуска сервера. Мы узнали, что этот процесс является детерминированным, поскольку порядок регистрации имеет значение. Наконец, мы узнали, что в распоряжении разработчиков есть два метода тонкой настройки последовательности загрузки, несмотря на то, что Fastify предоставляет поведение по умолчанию.
Во время загрузки может произойти одна или несколько ошибок. В следующем разделе мы рассмотрим наиболее распространенные ошибки и узнаем, как с ними бороться.
Обработка ошибок загрузки и плагинов¶
В этом разделе мы разберем некоторые наиболее распространенные ошибки загрузки Fastify и способы их устранения. Что же такое ошибки загрузки? Все ошибки, возникающие во время начальной загрузки нашего приложения, до того, как будет отправлено событие готовности и сервер начнет прослушивать входящие соединения, называются ошибками загрузки.
Эти ошибки обычно возникают, когда во время регистрации плагина происходит что-то непредвиденное. Если во время регистрации плагина возникнут необработанные ошибки, Fastify с помощью Avvio
сообщит нам об этом и остановит процесс загрузки.
Можно выделить два типа ошибок:
- Ошибки, которые можно исправить
- Ошибки, которые не подлежат восстановлению никакими способами.
Сначала мы рассмотрим самую распространенную невосстанавливаемую ошибку, ERR_AVVIO_PLUGIN_TIMEOUT
, которая обычно означает, что мы забыли сообщить Fastify о необходимости продолжить процесс загрузки.
Затем мы познакомимся с инструментами, которые Fastify предоставляет нам для восстановления после других видов ошибок. Важно отметить, что чаще всего мы не хотим восстанавливаться после ошибки, и лучше просто сделать так, чтобы сервер упал во время загрузки!
ERR_AVVIO_PLUGIN_TIMEOUT
¶
Чтобы процесс загрузки не затягивался до бесконечности, Fastify установил максимальное время загрузки плагина. Если плагин загружается дольше этого времени, Fastify будет выбрасывать ошибку ERR_AVVIO_PLUGIN_TIMEOUT
. По умолчанию таймаут установлен на 10 секунд, но это значение можно легко изменить с помощью серверной опции pluginTimeout
.
Это одна из самых распространенных ошибок загрузки, и обычно она возникает по двум причинам:
- Мы забыли вызвать обратный вызов done при регистрации плагина.
- Промис регистрации не был вовремя разрешен.
Это также самая запутанная ошибка, и она, безусловно, является неустранимой. Код, написанный в timeout-error.cjs
, специально генерирует эту ошибку, позволяя нам проанализировать трассировку стека, чтобы понять, как мы можем ее заметить и найти место ее возникновения:
1 2 3 4 5 6 7 8 |
|
Здесь мы регистрируем наш плагин. Мы даем ему имя, потому что, как мы увидим, оно поможет в трассировке стека. Даже если плагин не является async
-функцией, мы намеренно не вызываем колбек-функцию done, чтобы дать Fastify понять, что регистрация прошла без ошибок.
Если мы запустим этот фрагмент, то получим ошибку ERR_AVVIO_PLUGIN_TIMEOUT
:
1 2 3 4 5 6 7 8 9 10 11 |
|
Как мы сразу видим, ошибка довольно простая. Fastify использует имя функции myPlugin
, чтобы указать нам правильное направление. Более того, она предполагает, что мы могли забыть вызвать 'done'
или разрешить промис. Стоит отметить, что в реальных сценариях эта ошибка обычно возникает при проблемах с подключением — например, база данных недоступна в момент регистрации плагина.
Восстановление после ошибки загрузки¶
Обычно, если во время загрузки происходит ошибка, значит, что-то серьезное мешает запуску нашего приложения. В таких случаях, как мы уже видели, лучшее, что может сделать Fastify, — это остановить процесс загрузки и уведомить нас об ошибках, с которыми он столкнулся. Однако есть несколько случаев, когда мы можем исправить ошибку и продолжить процесс загрузки. Например, у нас есть дополнительный плагин для измерения метрик приложения, и мы хотим запустить приложение, даже если оно загружается некорректно.
В файле error-after.cjs
показано, как восстановиться после ошибки с помощью нашего старого друга, метода after
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Сначала мы объявляем фиктивный плагин ([1]
), который всегда бросает. Затем мы регистрируем его и используем метод after
для проверки ошибок регистрации ([2]
). Если ошибка найдена, мы выводим ее в консоль. Вызов after
позволяет «поймать» ошибку загрузки, и поэтому процесс загрузки не остановится, так как мы справились с неожиданным поведением. Мы можем запустить сниппет, чтобы проверить, что он работает так, как ожидалось:
1 2 3 |
|
Поскольку последней строкой в журнале является app ready
, мы знаем, что процесс загрузки прошел успешно, и наше приложение запустилось!
Резюме
В этой главе мы узнали о важности плагинов и о том, как работает процесс загрузки Fastify. Все в Fastify можно и даже нужно поместить в плагин. Это базовый строительный блок масштабируемых и поддерживаемых приложений, благодаря инкапсуляции и предсказуемому порядку загрузки. Более того, мы можем использовать fastify-plugin
для управления инкапсуляцией по умолчанию и управления зависимостями между плагинами.
Мы узнали, что наши приложения — это не что иное, как набор плагинов Fastify, которые работают вместе. Некоторые из них используются для инкапсуляции маршрутизаторов, используя префиксы для их пространств имен. Другие используются для добавления основных функций, таких как подключение к базам данных или другим внешним системам. Кроме того, мы можем устанавливать и использовать плагины ядра и сообщества непосредственно из npm.
Кроме того, мы рассказали об асинхронном характере процесса загрузки и о том, как каждый шаг может быть отложен в случае необходимости. Гарантируется, что если во время загрузки плагинов возникнет какая-либо ошибка, процесс загрузки остановится, а ошибка будет записана в консоль.
В следующей главе мы изучим все способы объявления конечных точек нашего приложения. Мы увидим, как добавлять обработчики маршрутов и как избежать основных подводных камней. И наконец, то, что мы узнали в этой главе о плагинах, пригодится нам при работе с маршрутизацией и группировкой маршрутов!