Перейти к содержанию

Система плагинов и процесс загрузки

Плагин 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
const Fastify = require('fastify')
const app = Fastify({ logger: true }) // [1]
app.register(async function (fastify, opts) { // [2]
    app.log.info('Registering my first plugin.')
})
app.ready() // [3]
  .then(() => { app.log.info('All plugins are now
    registered!')
})

После создания корневого экземпляра Fastify ([1]) мы добавляем наш первый плагин. Для этого мы передаем функцию определения плагина в качестве первого аргумента ([2]) в register. Эта функция получает новый экземпляр Fastify, наследуя все, что было у корневого экземпляра до этого момента, и аргумент options.

Аргумент options

В данный момент мы не используем аргумент options. Однако в следующем разделе мы узнаем о его важности и о том, как его использовать.

Наконец, мы вызываем метод ready ([3]). Эта функция запускает последовательность загрузки и возвращает Promise, который будет выполнен, когда все плагины будут загружены. На данный момент в наших примерах нам не нужен прослушивающий сервер, поэтому вместо него допустимо вызвать ready. Более того, listen внутренне ожидает события .ready(), которое будет отправлено в любом случае.

Порядок загрузки

Последовательность загрузки Fastify — это ряд операций, которые выполняет основной экземпляр Fastify для загрузки всех плагинов, хуков и декораторов. Если ошибок не возникнет, сервер запустится и будет слушать указанный порт. Подробнее об этом мы расскажем в отдельном разделе.

Давайте запустим предыдущий фрагмент в терминале и посмотрим на результат:

1
2
3
4
5
$ node index.cjs
{"level":30,"time":1621855819132,"pid":5612,"hostname":"my.
local","msg":"Registering my first plugin."}
{"level":30,"time":1621855819133,"pid":5612,"hostname":"my.
local","msg":"All plugins are now registered!"}

Давайте разберем выполнение нашего сниппета:

  1. Создается экземпляр Fastify с включенным логгером.
  2. В корневом экземпляре Fastify регистрируется наш первый плагин, и выполняется код внутри функции плагина.
  3. Экземпляр 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
//...
app.register(function noError(fastify, opts, done) {
app.log.info('Registering my first plugin.')
    // we need to call done explicitly to let fastify go to
       the next plugin
    done() // [1]
})
app.register(function (fastify, opts, done) {
app.log.info('Registering my second plugin.')
try {
    throw new Error('Something bad happened!')
    done() // [2]
} catch (err) {
    done(err) // [3]
}
})

Первый плагин загружается без ошибок, благодаря вызову done без переданных аргументов ([1]). С другой стороны, второй вылетает до вызова done() ([2]). Здесь мы видим, как важно перехватывать ошибки и вызывать done(err) ([3]); без этого вызова Fastify будет думать, что в процессе регистрации не произошло никаких ошибок, и продолжит последовательность загрузки!

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

  • Вызов done, если используется стиль обратного вызова
  • Разрешение/отклонение промиса, если мы выбрали стиль, основанный на промисах.

Мы еще вернемся к этому аргументу в разделе Ошибки загрузки, где мы увидим, что произойдет, если мы неправильно используем промисы и обратные вызовы.

Примеры, основанные на промисах

В этой книге используются сигнатуры на основе промисов, так как их легче выполнять благодаря ключевым словам async/await.

В этом разделе мы изучили два различных метода объявления нашего первого плагина и его регистрации в экземпляре Fastify. Однако мы только нащупали грань возможностей плагинов Fastify. В следующем разделе мы рассмотрим параметр options и то, как он может пригодиться при работе с разделяемыми функциями.

Изучение параметра options

В этом разделе мы подробно рассмотрим необязательный параметр options и то, как мы можем разрабатывать многократно используемые плагины. Функция объявления плагина — это не что иное, как фабричная функция, которая вместо того, чтобы возвращать новую сущность, как это обычно делают фабричные функции, добавляет поведение в экземпляр Fastify. Если мы посмотрим на это так, то можем считать параметр options аргументами нашего конструктора.

Для начала вспомним сигнатуру функции объявления плагина:

1
async function myPlugin(fastify, [options])

Как мы можем передать наши пользовательские аргументы в параметр options? Оказывается, у метода register есть и второй параметр. Таким образом, объект, который мы используем в качестве аргумента, будет передан Fastify в качестве параметра options плагина:

1
app.register(myPlugin, { first: 'option' });

Теперь, внутри функции myPlugin, мы можем получить доступ к этому значению, просто используя options.first.

Стоит отметить, что Fastify оставляет за собой три специфические опции, которые имеют особое значение:

  • prefix
  • logLevel
  • logSerializers

Опции журнала

Мы рассмотрим logLevel и logSerializer в отдельной главе. Здесь же мы сосредоточимся только на prefix и пользовательских опциях.

Имейте в виду, что в будущем могут быть добавлены дополнительные зарезервированные опции. Следовательно, разработчикам следует всегда рассматривать возможность использования пространства имен, чтобы избежать будущих коллизий, даже если это не является обязательным. Пример можно увидеть в фрагменте options-namespacing.cjs:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
app.register(
    async function myPlugin(fastify, options) {
        console.log(options.myplugin.first);
    },
    {
        prefix: 'v1',
        myPlugin: {
            first: 'custom option',
        },
    }
);

Вместо того чтобы добавлять наши пользовательские свойства на верхнем уровне параметра options, мы сгруппируем их в пользовательском ключе, что снизит вероятность коллизии имен в будущем. В большинстве случаев передача объекта — это хорошо, но иногда нам нужна большая гибкость.

В следующем разделе мы узнаем больше о типе параметров options и используем его для выполнения более сложных задач.

Тип параметра options

До сих пор мы видели, что параметр options — это объект с некоторыми зарезервированными и пользовательскими свойствами. Но options также может быть функцией, которая возвращает объект. Если передана функция, Fastify вызовет ее и передаст возвращаемый объект в качестве параметра options плагину. Чтобы лучше понять это, в фрагменте options-function.cjs мы перепишем предыдущий пример, используя функцию вместо объекта:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
app.register(
    async function myPlugin(fastify, options) {
        app.log.info(options.myplugin.first); // option
    },
    function options(parent) {
        // [1]
        return {
            prefix: '1',
            myPlugin: {
                first: 'option',
            },
        };
    }
);

На первый взгляд, не должно быть понятно, зачем нам этот альтернативный тип для опций, но взгляд на сигнатуру указывает нам на правильное направление — она получает родительский экземпляр Fastify в качестве единственного аргумента.

Рассмотрение примера options-function-parent.cjs должно прояснить, как мы можем получить доступ к родительским опциям:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
const Fastify = require('fastify');
function options(parent) {
    return {
        prefix: 'v1',
        myPlugin: {
            first: parent.mySpecialProp, // [2]
        },
    };
}
const app = Fastify({ logger: true });
app.decorate('mySpecialProp', 'root prop'); // [1]
app.register(async function myPlugin(fastify, options) {
    app.log.info(options.myplugin.first); // 'root prop'
}, options);
app.ready();

Сначала мы декорируем корневой экземпляр пользовательским свойством ([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
module.exports = async function usersRouter(fastify, _) {
    fastify.register(
        async function routes(child, _options) {
            // [1]
            child.get('/', async (_request, reply) => {
                reply.send(child.users);
            });
            child.post('/', async (request, reply) => {
                // [2]
                const newUser = request.body;
                child.users.push(newUser);
                reply.send(newUser);
            });
        },
        { prefix: 'users' } // [3]
    );
};

Декларация маршрута

Так как мы фокусируемся на плагинах и стараемся сделать примеры как можно короче, в этом разделе используется стиль «короткого объявления» для маршрутов. Более того, схемы и некоторые другие важные опции также отсутствуют. Мы увидим в этой книге, что существуют гораздо лучшие, формально корректные и полные варианты объявления маршрутов.

Мы определяем два маршрута; первый возвращает все элементы нашей коллекции ([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
const Fastify = require('fastify');
const usersRouter = require('./users-router.cjs');
const app = Fastify();
app.decorate('users', [
    // [1]
    {
        name: 'Sam',
        age: 23,
    },
    {
        name: 'Daphne',
        age: 21,
    },
]);
app.register(usersRouter, { prefix: 'v1' }); // [2]
app.register(
    async function usersRouterV2(fastify, options) {
        // [3]
        fastify.register(usersRouter); // [4]
        fastify.delete('/users/:name', (request, reply) => {
            // [5]
            const userIndex = fastify.users.findIndex(
                (user) => user.name === request.params.name
            );
            fastify.users.splice(userIndex, 1);
            reply.send();
        });
    },
    { prefix: 'v2' }
);
app.ready().then(() => {
    console.log(app.printRoutes());
}); // [6]

Прежде всего, мы украшаем корневой экземпляр 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
$ node users-router-index.cjs
└── /
    ├── v
    │   ├── 1
    │   │   └── /users (GET)
    │   │       /users (POST)
    │   │       └── / (GET) # [1]
    │   │           / (POST)
    │   └── 2
    │       └── /users (GET)
    │           /users (POST)
    │           └── / (GET) # [1]
    │               / (POST)
    └── v2/users/:name (DELETE)

Действительно, префиксные определения маршрутов — это очень интересная функция. Она позволяет нам повторно использовать одни и те же объявления маршрутов более одного раза. Это один из важнейших элементов многократного использования плагинов Fastify. В нашем примере мы используем всего два уровня вложенности префиксов, но на практике ограничений нет. Мы избегаем дублирования кода, дважды используя одни и те же определения GET и POST и добавляя только один новый маршрут DELETE к одному и тому же пространству имен пользователя, когда это необходимо.

В этом разделе мы рассмотрели, как использовать параметр options для достижения лучшей многоразовости плагина и управления его регистрацией в экземпляре Fastify. Этот параметр имеет несколько зарезервированных свойств, используемых для указания Fastify, как обращаться с регистрируемым плагином. Более того, мы можем добавить столько свойств, сколько необходимо плагину, зная, что Fastify передаст их на этапе регистрации.

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

Понимание инкапсуляции

До сих пор мы написали несколько плагинов. Мы достаточно хорошо знаем, как они устроены и какие аргументы получает плагин. Но нам еще предстоит обсудить одну недостающую вещь — концепцию инкапсуляции.

Давайте вспомним сигнатуру определения функции плагина:

1
async function myPlugin(fastify, options)

Как мы уже знаем, первым параметром является экземпляр Fastify. Этот экземпляр создается заново и наследуется от внешней области видимости. Предположим, что к корневому экземпляру было добавлено что-то, например, с помощью декоратора. В этом случае он будет присоединен к экземпляру Fastify плагина, и его можно будет использовать, как если бы он был определен внутри текущего плагина.

Однако обратное не верно. Если мы добавляем функциональные возможности внутри плагина, то они будут видны только в контексте текущего плагина.

Контекст против области видимости

Прежде всего, давайте рассмотрим определения обоих терминов. Контекст указывает на текущее значение неявной переменной метода 'this'. Область видимости (scope) — это набор правил, управляющих видимостью переменной с точки зрения функции. В сообществе Fastify эти два термина используются как взаимозаменяемые и относятся к экземпляру Fastify, с которым мы сейчас работаем. По этой причине в этой книге мы будем использовать оба слова, означающие одно и то же.

Давайте рассмотрим пример в файле encapsulation.cjs:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
const Fastify = require('fastify');
const app = Fastify({ logger: true });
app.decorate('root', 'hello from the root instance.'); // [1]
app.register(async function myPlugin(fastify, _options) {
    console.log('myPlugin -- ', fastify.root);
    fastify.decorate('myPlugin', 'hello from myPlugin.'); // [2]
    console.log('myPlugin -- ', fastify.myPlugin);
});
app.ready().then(() => {
    console.log('root -- ', app.root); // [3]
    console.log('root -- ', app.myPlugin);
});

Запуск этого фрагмента приведет к такому результату:

1
2
3
4
5
$ node encapsulation.cjs
myPlugin --  hello from the root instance.
myPlugin --  hello from myPlugin.
root --  hello from the root instance.
root --  undefined

Сначала мы декорируем корневой экземпляр ([1]), добавляя к нему строку. Затем внутри myPlugin мы печатаем декорированное значение корневого экземпляра и добавляем новое свойство к экземпляру Fastify. В теле определения плагина мы выводим оба значения в консоль, чтобы убедиться, что они заданы ([2]). Наконец, мы видим, что после того, как приложение Fastify готово, на корневом уровне мы можем получить доступ к добавленному нами значению только вне нашего плагина ([3]). Но что же здесь произошло? В обоих случаях мы использовали метод .decorate для добавления нашего значения в экземпляр. Почему оба значения видны в myPlugin, но на верхнем уровне видно только корневое значение? Так и должно быть, и это происходит благодаря инкапсуляции — Fastify создает новый контекст каждый раз, когда входит в новый плагин. Мы называем эти новые контексты дочерними контекстами. Дочерний контекст наследует только от родительского, и все, что добавляется в дочерний контекст, не будет видно ни в родительском, ни в контекстах братьев и сестер. Уровень аннексии родитель-ребенок бесконечен, и мы можем иметь контексты, которые являются детьми для своих родителей и родителями для своих детей.

К сущностям, на которые влияет область видимости, относятся:

  • Декораторы
  • Хуки
  • Плагины
  • маршруты.

Как мы видим, поскольку маршруты зависят от контекста, мы уже использовали инкапсуляцию в предыдущем разделе, даже если тогда мы этого не знали. Мы дважды зарегистрировали один и тот же маршрут на одном и том же корневом экземпляре, но с разными префиксами. В реальных приложениях широко распространены более сложные сценарии с несколькими дочерними и внучатыми контекстами. Мы можем использовать следующую диаграмму для рассмотрения более сложного примера:

Рисунок 2.1: Пример сложной иерархии плагинов

Рисунок 2.1: Пример сложной иерархии плагинов
.

На рисунке 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
const Fastify = require('fastify');
async function myPlugin(fastify, _options) {
    console.log('myPlugin -- ', fastify.root);
    fastify.decorate('myPlugin', 'hello from myPlugin.'); //[2]
    console.log('myPlugin -- ', fastify.myPlugin);
}
myPlugin[Symbol.for('skip-override')] = true; // [1]
const app = Fastify({ logger: true });
app.decorate('root', 'hello from the root instance.');
app.register(myPlugin);
app.ready().then(() => {
    console.log('root -- ', app.root);
    console.log('root -- ', app.myPlugin); //[3]
});

В этом фрагменте кода есть только одно существенное изменение по сравнению с предыдущим: мы используем Symbol.for('skip-override'), чтобы предотвратить создание Fastify нового контекста ([1]). Одного этого достаточно, чтобы корень был декорирован переменной fastify.myPlugin ([2]). Мы видим, что декоратор также доступен из внешней области видимости ([3]); вот результат:

1
2
3
4
5
$ node skip-override.cjs
myPlugin --  hello from the root instance.
myPlugin --  hello from myPlugin.
root --  hello from the root instance.
root --  hello from myPlugin.

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
$> npm i fastify-plugin

Его сигнатура напоминает метод .register. Он принимает два параметра:

  1. Точное определение функции плагина, как мы уже видели
  2. Необязательный объект 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
const fp = require('fastify-plugin');
async function myPlugin(fastify, _options) {
    console.log('myPlugin decorates the parent instance.');
    fastify.decorate('myPlugin', 'hello from myPlugin.');
}
module.exports = fp(myPlugin, {
    // [1]
    name: 'myPlugin', // [2]
    fastify: '4.x', // [3]
    decorators: { fastify: ['root'] }, // [4]
});

В [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
const Fastify = require('fastify');
const myPlugin = require('./fp-myplugin.cjs');
const app = Fastify({ logger: true });
app.decorate('root', 'hello from the root instance.')[1]; //
app.register(myPlugin); // [2]
app.ready().then(() => {
    console.log('root -- ', app.root);
    console.log('root -- ', app.myPlugin); //[3]
});

Сначала мы украшаем корневой экземпляр fastify строковым свойством ([1]). Затем мы регистрируем наш плагин ([2]). Помните, что мы указали корневое свойство как обязательное в метаданных fastify-plugin. Перед регистрацией myPlugin, Fastify проверяет, объявлено ли свойство в родительском контексте, и если оно там есть, то переходит к процессу загрузки. Наконец, поскольку fastify-plugin добавляет свойство skip-override для нас, мы можем получить доступ к свойству myPlugin в корневой области видимости без каких-либо проблем ([3]). Давайте посмотрим на результат этого фрагмента:

1
2
3
4
$ node fp-myplugin-index.cjs
myPlugin decorates the parent instance.
root --  hello from the root instance.
root --  hello from myPlugin.

Все работает, как и ожидалось!

Теперь, посмотрев на fp-myplugin-index-missing-root.cjs, мы можем проверить, что произойдет, если декоратор root будет отсутствовать у экземпляра root, как он был объявлен в fp-myplugin-index.cjs по адресу [1], как было показано ранее:

1
2
3
4
5
6
7
8
const Fastify = require('fastify');
const myPlugin = require('./fp-myplugin.cjs');
const app = Fastify({ logger: true });
app.register(myPlugin);
app.ready().then(() => {
    console.log('root -- ', app.root);
    console.log('root -- ', app.myPlugin);
});

Запуск этого файла приведет к сбою и прерыванию процесса загрузки:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ node fp-myplugin-index-missing-root.cjs
/node_modules/fastify/lib/pluginUtils.js:98
      throw new FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE(decorator,
withPluginName, instance)
            ^
FastifyError [Error]: The decorator 'root' required by 'myPlugin' is
not present in Fastify
    at /node_modules/fastify/lib/pluginUtils.js:98:13
    at Array.forEach (<anonymous>)
    at _checkDecorators (/node_modules/fastify/lib/pluginUtils.
js:95:14)
    at Object.checkDecorators (/node_modules/fastify/lib/pluginUtils.
js:81:27)
    at Object.registerPlugin (/node_modules/fastify/lib/pluginUtils.
js:137:19)
    at Boot.override (/node_modules/fastify/lib/pluginOverride.
js:28:57)
    at Plugin.exec (/node_modules/avvio/plugin.js:79:33)
    at Boot.loadPlugin (/node_modules/avvio/plugin.js:272:10)
    at processTicksAndRejections (node:internal/process/task_
queues:83:21) {
  code: 'FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE',
  statusCode: 500
}

Мы видим, что 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
.register(async function plugin(instance, pluginOptions), options)

Параметр 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
const Fastify = require('fastify');
// [1]
async function plugin1(fastify, options) {
    app.log.info('plugin1');
}
async function plugin2(fastify, options) {
    app.log.info('plugin2');
}
async function plugin3(fastify, options) {
    app.log.info('plugin3');
}
const app = Fastify({ logger: true });
app.register(plugin1) // [2]
    .register(plugin2);
app.register(plugin3); // [3]
app.ready().then(() => {
    console.log('app ready');
});

Мы определяем три фиктивных плагина, которые при регистрации делают только одну вещь — записывают в журнал имя своей функции ([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
const Fastify = require('fastify');
const fp = require('fastify-plugin');
async function boot() {
    // [1]
    async function plugin1(fastify, options) {
        fastify.decorate('plugin1Decorator', 'plugin1');
    }
    async function plugin2(fastify, options) {
        fastify.decorate('plugin2Decorator', 'plugin2');
    }
    async function plugin3(fastify, options) {
        fastify.decorate('plugin3Decorator', 'plugin3');
    }
    const app = Fastify({ logger: true });
    await app // [2]
        .register(fp(plugin1))
        .register(fp(plugin2));
    console.log(
        'plugin1Decorator',
        app.hasDecorator('plugin1Decorator')
    );
    console.log(
        'plugin2Decorator',
        app.hasDecorator('plugin2Decorator')
    );
    app.register(fp(plugin3)); // [3]
    console.log(
        'plugin3Decorator',
        app.hasDecorator('plugin3Decorator')
    );
    await app.ready();
    console.log('app ready');
    console.log(
        'plugin3Decorator',
        app.hasDecorator('plugin3Decorator')
    );
}
boot();

Чтобы понять, что происходит, запустим этот фрагмент:

1
2
3
4
5
6
$ node register-await.mjs
plugin1Decorator true # [4]
plugin2Decorator true
plugin3Decorator false # [5]
app ready
plugin3Decorator true # [6]

Поскольку этот пример довольно длинный и сложный, давайте разобьем его на более мелкие части:

  • Мы объявляем три плагина [1], каждый из которых добавляет по одному декоратору к экземпляру Fastify
  • Мы используем fastify-plugin для декорирования корневого экземпляра и для регистрации этих плагинов ([2] и [3]), как мы узнали в предыдущем разделе.
  • Даже если наши плагины идентичны, мы видим, что результаты console.log различны; два плагина, зарегистрированные с помощью оператора await ([2]), уже украсили корневой экземпляр ([4])
  • И наоборот, последний плагин, зарегистрированный без await ([3]), добавляет свой декоратор только после разрешения промиса события ready ([5] и [6])

Теперь должно быть понятно, что если мы также хотим, чтобы третий декоратор был доступен до полной готовности приложения, достаточно добавить оператор await на [3]!

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

Метод after экземпляра

Аргумент функции метода after вызывается Fastify автоматически, когда все плагины, добавленные до этого момента, завершают загрузку. В качестве единственного параметра он определяет колбек-функцию с необязательным аргументом ошибки:

1
.after(function (err) {})

Если мы не передадим обратный вызов, то 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
const Fastify = require('fastify');
const fp = require('fastify-plugin');
async function boot() {
    // [1]
    async function plugin1(fastify, options) {
        fastify.decorate('plugin1Decorator', 'plugin1');
    }
    async function plugin2(fastify, options) {
        fastify.decorate('plugin2Decorator', 'plugin2');
    }
    async function plugin3(fastify, options) {
        fastify.decorate('plugin3Decorator', 'plugin3');
    }
    const app = Fastify({ logger: true });
    await app // [2]
        .register(fp(plugin1))
        .register(fp(plugin2))
        .after((_error) => {
            // [3]
            console.log(
                'plugin1Decorator',
                app.hasDecorator('plugin1Decorator')
            );
            console.log(
                'plugin2Decorator',
                app.hasDecorator('plugin2Decorator')
            );
        })
        .register(fp(plugin3)); // [4]
    console.log(
        'plugin3Decorator',
        app.hasDecorator('plugin3Decorator')
    );
    await app.ready();
    console.log('app ready');
}
boot();

Мы объявляем и затем регистрируем те же три плагина, что и в предыдущих примерах ([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
const Fastify = require('fastify');
const app = Fastify({ logger: true });
app.register(function myPlugin(fastify) {
    console.log('Registering my first plugin.');
});
app.ready().then(() => {
    console.log('app ready');
});

Здесь мы регистрируем наш плагин. Мы даем ему имя, потому что, как мы увидим, оно поможет в трассировке стека. Даже если плагин не является async-функцией, мы намеренно не вызываем колбек-функцию done, чтобы дать Fastify понять, что регистрация прошла без ошибок.

Если мы запустим этот фрагмент, то получим ошибку ERR_AVVIO_PLUGIN_TIMEOUT:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$ node timeout-error.cjs
Registering my first plugin.
Error: ERR_AVVIO_PLUGIN_TIMEOUT: plugin did not start in time:
myPlugin. You may have forgotten to call 'done' function or to resolve
a Promise
    at Timeout._onTimeout (/node_modules/avvio/plugin.js:123:19)
    at listOnTimeout (internal/timers.js:554:17)
    at processTimers (internal/timers.js:497:7) {
  code: 'ERR_AVVIO_PLUGIN_TIMEOUT',
  fn: [Function: myPlugin]
}

Как мы сразу видим, ошибка довольно простая. 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
const Fastify = require('fastify');
// [1]
async function plugin1(fastify, options) {
    throw new Error('Kaboom!');
}
const app = Fastify({ logger: true });
app.register(plugin1).after((err) => {
    if (err) {
        // [2]
        console.log(
            `There was an error loading plugin1:
          '${err.message}'. Skipping.`
        );
    }
});
app.ready().then(() => {
    console.log('app ready');
});

Сначала мы объявляем фиктивный плагин ([1]), который всегда бросает. Затем мы регистрируем его и используем метод after для проверки ошибок регистрации ([2]). Если ошибка найдена, мы выводим ее в консоль. Вызов after позволяет «поймать» ошибку загрузки, и поэтому процесс загрузки не остановится, так как мы справились с неожиданным поведением. Мы можем запустить сниппет, чтобы проверить, что он работает так, как ожидалось:

1
2
3
$ node error-after.cjs
There was an error loading plugin1: 'Kaboom!'. Skipping.
app ready

Поскольку последней строкой в журнале является app ready, мы знаем, что процесс загрузки прошел успешно, и наше приложение запустилось!

Резюме

В этой главе мы узнали о важности плагинов и о том, как работает процесс загрузки Fastify. Все в Fastify можно и даже нужно поместить в плагин. Это базовый строительный блок масштабируемых и поддерживаемых приложений, благодаря инкапсуляции и предсказуемому порядку загрузки. Более того, мы можем использовать fastify-plugin для управления инкапсуляцией по умолчанию и управления зависимостями между плагинами.

Мы узнали, что наши приложения — это не что иное, как набор плагинов Fastify, которые работают вместе. Некоторые из них используются для инкапсуляции маршрутизаторов, используя префиксы для их пространств имен. Другие используются для добавления основных функций, таких как подключение к базам данных или другим внешним системам. Кроме того, мы можем устанавливать и использовать плагины ядра и сообщества непосредственно из npm.

Кроме того, мы рассказали об асинхронном характере процесса загрузки и о том, как каждый шаг может быть отложен в случае необходимости. Гарантируется, что если во время загрузки плагинов возникнет какая-либо ошибка, процесс загрузки остановится, а ошибка будет записана в консоль.

В следующей главе мы изучим все способы объявления конечных точек нашего приложения. Мы увидим, как добавлять обработчики маршрутов и как избежать основных подводных камней. И наконец, то, что мы узнали в этой главе о плагинах, пригодится нам при работе с маршрутизацией и группировкой маршрутов!

Комментарии