FastifyMiddleTechnical

Как работает модель инкапсуляции (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-риски: безопасность, отказоустойчивость, логирование и тесты.

Sources

Related topics