Как работает модель инкапсуляции (encapsulation model) в Fastify? Что такое scope плагина?
Каждый register() создаёт дочерний scope, который наследует декораторы и хуки родителя, но не наоборот. Обёртка fastify-plugin (fp) отключает инкапсуляцию и «всплывает» декоратор в родительский контекст.
Модель инкапсуляции в Fastify
Fastify строит дерево контекстов через систему плагинов. Каждый вызов fastify.register() создаёт дочерний scope — изолированный контейнер, в котором живут декораторы, хуки, схемы и маршруты, зарегистрированные внутри этого плагина. Родительский scope видит только то, что зарегистрировано в нём самом; дочерний наследует всё от родителя, но не наоборот.
Это достигается через avvio — внутренний загрузчик плагинов Fastify. Когда вы вызываете register, фреймворк создаёт новый экземпляр Fastify («дочерний экземпляр») и передаёт его в функцию плагина. Маршруты, добавленные в этом экземпляре, ограничены его scope.
import Fastify from 'fastify';
const app = Fastify({ logger: true });
// Родительский scope — глобальный декоратор
app.decorate('db', { query: () => 'parent-db' });
// Дочерний scope (изолированный)
app.register(async function childPlugin(child) {
// Дочерний scope наследует 'db' от родителя
child.decorate('secret', 'token-xyz');
child.get('/private', async (req, reply) => {
return { secret: child.secret, db: child.db.query() };
});
});
// Этот маршрут НЕ видит 'secret' — его нет в родительском scope
app.get('/public', async (req, reply) => {
// app.secret === undefined
return { db: app.db.query() };
});
await app.listen({ port: 3000 });
Пробрасывание через scope: fastify-plugin
Если нужно, чтобы декоратор или хук из плагина стали доступны в родительском scope, оберните функцию плагина в fastify-plugin (fp). Это отключает инкапсуляцию для данного плагина — он «всплывает» в родительский контекст.
import fp from 'fastify-plugin';
const dbPlugin = fp(async function(fastify, opts) {
// fastify.pg теперь будет виден во всём дереве
fastify.decorate('pg', { query: () => 'real-db' });
}, {
name: 'my-db-plugin',
fastify: '4.x'
});
app.register(dbPlugin);
// Теперь app.pg доступен здесь и в любых дочерних scope
app.get('/all', async (req) => ({ result: app.pg.query() }));
Scope хуков
Хуки (onRequest, preHandler, onSend и др.) тоже подчиняются инкапсуляции. Хук, зарегистрированный в дочернем scope, выполняется только для маршрутов этого scope:
app.register(async function authScope(child) {
child.addHook('preHandler', async (req, reply) => {
if (!req.headers.authorization) {
reply.code(401).send({ error: 'Unauthorized' });
}
});
child.get('/me', async (req) => ({ user: 'from auth scope' }));
});
// Маршрут ниже НЕ проходит через preHandler выше
app.get('/health', async () => ({ ok: true }));
Подводные камни
- Если вы забыли обернуть плагин в
fp, декоратор не будет виден в родительском scope — TypeScript не поможет, ошибка будет только в runtime при обращении кfastify.myDecorator. - Порядок регистрации важен: плагин, зависящий от декоратора другого плагина, должен быть зарегистрирован после него.
fastify-pluginобходит инкапсуляцию только для одного уровня — если плагин вложен глубоко, нужно оборачивать каждый уровень отдельно или использовать fp на всём пути.- Хуки, добавленные через
fp-плагин, применяются глобально — легко случайно добавить аутентификацию ко всем маршрутам, включая публичные. - В тестах с
fastify.inject()нужно дождатьсяawait app.ready(), иначе плагины ещё не инициализированы и тест упадёт с ошибкой «decorator not found». - Две версии одного плагина в одном дереве конфликтуют по имени (поле
nameв fp): Fastify бросит ошибку дублирования — решение: передавать разные имена или использоватьskipOverride. - Схемы JSON (через
addSchema) тоже инкапсулированы — схема, добавленная в дочернем scope, не видна в родительском для валидации ответа.
Common mistakes
- Дает общий ответ про Node.js и не называет конкретные API Fastify.
- Не объясняет, где в lifecycle находится encapsulation model и scope плагина.
- Не разделяет validation, authorization, business logic и persistence.
- Игнорирует ошибки, лимиты входных данных, observability и тестирование.
What the interviewer is testing
- Может объяснить encapsulation model и scope плагина на примере кода.
- Называет ключевые API: decorate(), register().
- Использует точные API Fastify, а не вымышленные hooks/decorators/methods.
- Видит production-риски: безопасность, отказоустойчивость, логирование и тесты.