作用域与封装 #

一、作用域概念 #

1.1 什么是作用域 #

Fastify的作用域是指插件注册内容的可见范围。每个通过 register 注册的插件都会创建一个新的作用域。

1.2 作用域类型 #

text
┌─────────────────────────────────────────────┐
│              Root Scope (根作用域)           │
│  ┌───────────────────────────────────────┐  │
│  │      Plugin A Scope (插件A作用域)      │  │
│  │  ┌─────────────────────────────────┐  │  │
│  │  │   Plugin B Scope (插件B作用域)   │  │  │
│  │  └─────────────────────────────────┘  │  │
│  └───────────────────────────────────────┘  │
│  ┌───────────────────────────────────────┐  │
│  │      Plugin C Scope (插件C作用域)      │  │
│  └───────────────────────────────────────┘  │
└─────────────────────────────────────────────┘

1.3 封装规则 #

规则 说明
内部可见 子作用域可以访问父作用域的内容
外部不可见 父作用域无法访问子作用域的内容
兄弟隔离 同级作用域之间相互隔离

二、默认封装行为 #

2.1 基本封装 #

javascript
const fastify = require('fastify')()

fastify.register(async function (fastify, opts) {
  fastify.decorate('internal', 'plugin value')
  
  console.log('Inside plugin:', fastify.internal)
})

fastify.ready(() => {
  console.log('Outside plugin:', fastify.internal)
})

输出:

text
Inside plugin: plugin value
Outside plugin: undefined

2.2 路由封装 #

javascript
const fastify = require('fastify')()

fastify.register(async function (fastify, opts) {
  fastify.get('/internal', async (request, reply) => {
    return { message: 'Internal route' }
  })
})

fastify.ready(() => {
  console.log(fastify.printRoutes())
})

2.3 钩子封装 #

javascript
const fastify = require('fastify')()

fastify.register(async function (fastify, opts) {
  fastify.addHook('onRequest', async (request, reply) => {
    console.log('Plugin hook')
  })
  
  fastify.get('/plugin-route', async (request, reply) => {
    return { message: 'Plugin route' }
  })
})

fastify.get('/root-route', async (request, reply) => {
  return { message: 'Root route' }
})

/plugin-route 会触发钩子,/root-route 不会。

三、破坏封装 #

3.1 使用fastify-plugin #

javascript
const fp = require('fastify-plugin')

async function myPlugin(fastify, opts) {
  fastify.decorate('shared', 'global value')
}

module.exports = fp(myPlugin)

现在 shared 装饰器在整个应用中都可用:

javascript
const fastify = require('fastify')()

fastify.register(require('./my-plugin'))

fastify.ready(() => {
  console.log(fastify.shared)
})

3.2 封装级别控制 #

javascript
const fp = require('fastify-plugin')

async function myPlugin(fastify, opts) {
  fastify.decorate('value', 'shared')
}

module.exports = fp(myPlugin, {
  encapsulate: false
})

3.3 部分破坏封装 #

javascript
const fp = require('fastify-plugin')

async function myPlugin(fastify, opts) {
  fastify.decorate('shared', 'global')
  
  fastify.register(async function (fastify, opts) {
    fastify.decorate('internal', 'local')
  })
}

module.exports = fp(myPlugin)

shared 全局可用,internal 只在内部可用。

四、作用域继承 #

4.1 装饰器继承 #

javascript
const fastify = require('fastify')()

fastify.decorate('root', 'root value')

fastify.register(async function (fastify, opts) {
  console.log('Can access root:', fastify.root)
  
  fastify.decorate('level1', 'level 1 value')
  
  fastify.register(async function (fastify, opts) {
    console.log('Can access root:', fastify.root)
    console.log('Can access level1:', fastify.level1)
  })
})

4.2 钩子继承 #

javascript
const fastify = require('fastify')()

fastify.addHook('onRequest', async (request, reply) => {
  console.log('Root hook')
})

fastify.register(async function (fastify, opts) {
  fastify.addHook('onRequest', async (request, reply) => {
    console.log('Plugin hook')
  })
  
  fastify.get('/test', async (request, reply) => {
    return { message: 'Test' }
  })
})

访问 /test 会依次执行两个钩子。

4.3 插件继承 #

javascript
const fastify = require('fastify')()

fastify.register(require('@fastify/jwt'), {
  secret: 'secret'
})

fastify.register(async function (fastify, opts) {
  fastify.get('/protected', {
    preHandler: async (request, reply) => {
      await request.jwtVerify()
    }
  }, async (request, reply) => {
    return { user: request.user }
  })
})

五、作用域隔离 #

5.1 兄弟作用域隔离 #

javascript
const fastify = require('fastify')()

fastify.register(async function (fastify, opts) {
  fastify.decorate('pluginA', 'value A')
})

fastify.register(async function (fastify, opts) {
  console.log('Cannot access pluginA:', fastify.pluginA)
})

5.2 前缀隔离 #

javascript
const fastify = require('fastify')()

fastify.register(async function (fastify, opts) {
  fastify.get('/', async (request, reply) => {
    return { message: 'API v1' }
  })
}, { prefix: '/api/v1' })

fastify.register(async function (fastify, opts) {
  fastify.get('/', async (request, reply) => {
    return { message: 'API v2' }
  })
}, { prefix: '/api/v2' })

5.3 配置隔离 #

javascript
const fastify = require('fastify')()

fastify.register(async function (fastify, opts) {
  fastify.decorate('config', { version: 'v1', features: ['a', 'b'] })
  
  fastify.get('/config', async (request, reply) => {
    return fastify.config
  })
}, { prefix: '/v1' })

fastify.register(async function (fastify, opts) {
  fastify.decorate('config', { version: 'v2', features: ['a', 'b', 'c'] })
  
  fastify.get('/config', async (request, reply) => {
    return fastify.config
  })
}, { prefix: '/v2' })

六、作用域控制 #

6.1 控制装饰器可见性 #

javascript
const fp = require('fastify-plugin')

async function myPlugin(fastify, opts) {
  const privateData = 'private'
  
  fastify.decorate('public', 'public value')
  
  fastify.decorateRequest('getPrivate', function () {
    return privateData
  })
}

module.exports = fp(myPlugin)

6.2 控制钩子作用域 #

javascript
const fastify = require('fastify')()

fastify.register(async function (fastify, opts) {
  fastify.addHook('onRequest', async (request, reply) => {
    console.log('Scoped hook')
  })
  
  fastify.register(require('./routes'), { prefix: '/api' })
})

fastify.get('/public', async (request, reply) => {
  return { message: 'Public route' }
})

6.3 控制路由作用域 #

javascript
const fastify = require('fastify')()

fastify.register(async function (fastify, opts) {
  fastify.register(async function (fastify, opts) {
    fastify.get('/users', async (request, reply) => {
      return { users: [] }
    })
  }, { prefix: '/api' })
}, { prefix: '/v1' })

七、实际应用场景 #

7.1 多租户应用 #

javascript
const fastify = require('fastify')()

const tenants = {
  tenant1: {
    db: 'mongodb://localhost/tenant1',
    config: { name: 'Tenant 1' }
  },
  tenant2: {
    db: 'mongodb://localhost/tenant2',
    config: { name: 'Tenant 2' }
  }
}

for (const [tenant, config] of Object.entries(tenants)) {
  fastify.register(async function (fastify, opts) {
    fastify.register(require('@fastify/mongodb'), {
      url: config.db
    })
    
    fastify.decorate('tenantConfig', config.config)
    
    fastify.get('/info', async (request, reply) => {
      return {
        tenant,
        config: fastify.tenantConfig
      }
    })
  }, { prefix: `/${tenant}` })
}

7.2 API版本控制 #

javascript
const fastify = require('fastify')()

fastify.register(require('./routes/v1'), { prefix: '/api/v1' })
fastify.register(require('./routes/v2'), { prefix: '/api/v2' })

fastify.get('/api', async (request, reply) => {
  return {
    versions: ['v1', 'v2'],
    latest: 'v2'
  }
})

routes/v1.js

javascript
module.exports = async function (fastify, opts) {
  fastify.get('/users', async (request, reply) => {
    return { version: 'v1', users: [] }
  })
}

routes/v2.js

javascript
module.exports = async function (fastify, opts) {
  fastify.get('/users', async (request, reply) => {
    return { version: 'v2', users: [], meta: {} }
  })
}

7.3 模块化功能 #

javascript
const fastify = require('fastify')()

fastify.register(require('./modules/users'), { prefix: '/users' })
fastify.register(require('./modules/products'), { prefix: '/products' })
fastify.register(require('./modules/orders'), { prefix: '/orders' })

modules/users/index.js

javascript
module.exports = async function (fastify, opts) {
  fastify.register(require('./routes'))
  fastify.register(require('./hooks'))
  fastify.register(require('./services'))
}

7.4 条件性功能 #

javascript
const fastify = require('fastify')()

fastify.register(async function (fastify, opts) {
  if (process.env.ENABLE_ADMIN === 'true') {
    fastify.register(require('./admin'), { prefix: '/admin' })
  }
  
  if (process.env.ENABLE_METRICS === 'true') {
    fastify.register(require('./metrics'), { prefix: '/metrics' })
  }
})

八、作用域调试 #

8.1 打印插件树 #

javascript
fastify.ready(() => {
  console.log(fastify.printPlugins())
})

输出:

text
root
├── plugin-a
│   └── plugin-b
└── plugin-c

8.2 检查装饰器 #

javascript
fastify.ready(() => {
  console.log('Has db:', fastify.hasDecorator('db'))
  console.log('Has user:', fastify.hasRequestDecorator('user'))
  console.log('Has success:', fastify.hasReplyDecorator('success'))
})

8.3 调试作用域 #

javascript
fastify.register(async function (fastify, opts) {
  console.log('Plugin scope:', fastify.pluginName)
  
  fastify.register(async function (fastify, opts) {
    console.log('Nested scope:', fastify.pluginName)
  })
})

九、最佳实践 #

9.1 合理使用封装 #

javascript
const fp = require('fastify-plugin')

async function sharedPlugin(fastify, opts) {
  fastify.decorate('shared', 'global')
}

module.exports = fp(sharedPlugin)

async function isolatedPlugin(fastify, opts) {
  fastify.decorate('internal', 'local')
}

module.exports = isolatedPlugin

9.2 避免过度封装 #

javascript
const fp = require('fastify-plugin')

async function databasePlugin(fastify, opts) {
  const db = await connect(opts)
  fastify.decorate('db', db)
}

module.exports = fp(databasePlugin, {
  name: 'database'
})

9.3 清晰的作用域边界 #

javascript
fastify.register(async function (fastify, opts) {
  fastify.decorate('internal', 'value')
  
  fastify.get('/internal-route', async (request, reply) => {
    return { internal: fastify.internal }
  })
}, { prefix: '/module' })

十、常见问题 #

10.1 装饰器未定义 #

问题

javascript
fastify.register(async function (fastify, opts) {
  fastify.decorate('value', 'test')
})

console.log(fastify.value)

解决

javascript
const fp = require('fastify-plugin')

async function myPlugin(fastify, opts) {
  fastify.decorate('value', 'test')
}

module.exports = fp(myPlugin)

10.2 钩子未触发 #

问题:钩子只在特定路由触发。

解决:使用 fastify-plugin 或在根作用域注册钩子。

10.3 插件加载顺序 #

问题:依赖插件未加载。

解决

javascript
module.exports = fp(myPlugin, {
  name: 'my-plugin',
  dependencies: ['database', 'redis']
})

十一、总结 #

本章我们学习了:

  1. 作用域概念:什么是作用域、作用域类型
  2. 默认封装:装饰器封装、路由封装、钩子封装
  3. 破坏封装:fastify-plugin、封装级别控制
  4. 作用域继承:装饰器继承、钩子继承、插件继承
  5. 作用域隔离:兄弟隔离、前缀隔离、配置隔离
  6. 作用域控制:装饰器可见性、钩子作用域、路由作用域
  7. 实际应用:多租户、版本控制、模块化功能
  8. 作用域调试:打印插件树、检查装饰器
  9. 最佳实践:合理使用封装、避免过度封装
  10. 常见问题:装饰器未定义、钩子未触发、加载顺序

接下来让我们学习中间件!

最后更新:2026-03-28