插件指南

Fastify 从一开始就建成了一个非常模块化的系统,通过创建命名空间来为 Fastify 扩展方法,我们构建了一个创建封装模型的系统,你可以随时将应用程序拆分成多个微服务器,而无需重构整个应用程序。

Register

在 Javascript 中一切都是对象,而在 Fastify 中一切都是插件。
路由、工具方法等几乎所有都是插件,不管它们的功能是什么,只需要同一个api: register

fastify.register(
  require('./my-plugin'),
  { options },
  callback
)

register 创建了一个新的 Fastify 上下文,这表示对 Fstify 实例的修改将不会反映到父作用域。

Why encapsulation is important?
Well, let's say you are creating a new disruptive startup, what do you do? You create an api server with all your stuff, everything in the same place, a monolith!
Ok, you are growing very fast and you want to change your architecture and try microservices. Usually this implies an huge amount of work, because of cross dependencies and the lack of separation of concerns.
Fastify helps you a lot in this direction, because thanks to the encapsulation model it will completely avoid cross dependencies, and will help you structure your code in cohesive blocks.

如何正确使用 register
插件方法必须含有如下三个参数:

module.exports = function (fastify, options, next) {}

fastify 是一个封装过的 Fastify 实例, options 是一个配置对象, next 方法必须在插件准备好了之后调用。

Fastify 的插件模型是基于图的,高度可重用的,它没有任何异步代码的问题,它保证插件的加载顺序,甚至是紧密的订单!查看 avvio!

你可以在一个插件中做任何事,注册路由、扩展方法甚至使用其他插件,只要记住调用 next() 即可。

module.exports = function (fastify, options, next) {
  fastify.get('/plugin', (req, reply) => {
    reply.send({ hello: 'world' })
  })

  next()
}

Decorators

当你写了一个工具方法,并且希望在其他文件中使用时,你会怎么做?可能你会这样做:

// your-awesome-utility.js
module.exports = function (a, b) {
  return a + b
}
const util = require('./your-awesome-utility')
console.log(util('that is ', ' awesome'))

现在你可以在任何你想使用的地方引入它。

Fastify 提供了一个优雅的方法做这件事: 装饰器
创建一个装饰器非常简单,只用调用 decotare 即可:

fastify.decorate('util', (a, b) => a + b)

现在可以通过调用 fastify.util 来使用工具方法,也可以在测试环境中这么做。
还记得上面说的封装吗?使用 registerdecorate

fastify.register((instance, opts, next) => {
  instance.decorate('util', (a, b) => a + b)
  console.log(instance.util('that is ', ' awesome'))

  next()
})

fastify.register((instance, opts, next) => {
  console.log(instance.util('that is ', ' awesome')) // 抛出异常

  next()
})

在第二个 register 函数中 instance.util 会抛出异常,因为 util 只存在于第一个 register 函数的上下文中。
注意:封装只适用于祖先和兄弟作用域,不适用于子作用域。

fastify.register((instance, opts, next) => {
  instance.decorate('util', (a, b) => a + b)
  console.log(instance.util('that is ', ' awesome'))

  fastify.register((instance, opts, next) => {
    console.log(instance.util('that is ', ' awesome')) // 不会抛出异常
    next()
  })

  next()
})

fastify.register((instance, opts, next) => {
  console.log(instance.util('that is ', ' awesome')) // 会抛出异常

  next()
})

如果你希望定义的方法在应用的任何地方都可以用,你可以在根作用域中定义。或者使用 fastify-plugin npm包。

decorate 并不是扩展服务器功能唯一的api, 还可以使用 decorateRequestdecorateReply

decorateRequestdecorateReply 并不是多余的,它对开发者更加友好:

fastify.decorate('html', payload => {
  return generateHtml(payload)
})

fastify.get('/html', (req, reply) => {
  reply
    .type('text/html')
    .send(fastify.html({ hello: 'world' }))
})

上述代码可以正常工作,我们来将它变得更好:

fastify.decorateReply('html', function (payload) {
  this.type('text/html') // this is the 'Reply' object
  this.send(generateHtml(payload))
})

fastify.get('/html', (req, reply) => {
  reply.html({ hello: 'world' })
})

同样可以封装 request 对象:

fastify.decorate('getHeader', (req, header) => {
  return req.headers[header]
})

fastify.addHook('preHandler', (req, reply, done) => {
  req.isHappy = fastify.getHeader(req.req, 'happy')
  done()
})

fastify.get('/happiness', (req, reply) => {
  reply.send({ happy: req.isHappy })
})

改进后:

fastify.decorateRequest('setHeader', function (header) {
  this.isHappy = this.req.headers[header]
})

fastify.decorateRequest('isHappy', false) // this will be added to the Request object prototype, yay speed!

fastify.addHook('preHandler', (req, reply, done) => {
  req.setHeader('happy')
  done()
})

fastify.get('/happiness', (req, reply) => {
  reply.send({ happy: req.isHappy })
})

Hooks

你刚刚写了一个很不错的方法,现在你想在每个路由请求中应用,你可能会这样做:

fastify.decorate('util', (req, key, value) => { req.key = value })

fastify.get('/plugin1', (req, reply) => {
  fastify.util(req, 'timestamp', new Date())
  reply.send(req)
})

fastify.get('/plugin2', (req, reply) => {
  fastify.util(req, 'timestamp', new Date())
  reply.send(req)
})

这么做是糟糕的,代码重复率高,可读性差,不易扩展,我们可以这样做:

fastify.decorate('util', (req, key, value) => { req.key = value })

fastify.addHook('preHandler', (req, reply, done) => {
  fastify.util(req, 'timestamp', new Date())
  done()
})

fastify.get('/plugin1', (req, reply) => {
  reply.send(req)
})

fastify.get('/plugin2', (req, reply) => {
  reply.send(req)
})

现在每个请求都会运行定义的方法。
只在某些特定的路由请求中执行定义的方法,可以这样做:

fastify.register((instance, opts, next) => {
  instance.decorate('util', (req, key, value) => { req.key = value })

  instance.addHook('preHandler', (req, reply, done) => {
    instance.util(req, 'timestamp', new Date())
    done()
  })

  instance.get('/plugin1', (req, reply) => {
    reply.send(req)
  })

  next()
})

fastify.get('/plugin2', (req, reply) => {
  reply.send(req)
})

正和你可能注意到的那样,请求和响应不是标准的Nodejs请求和响应对象,而是Fastify的对象。
如果你是来自 Express 和 Restify 的用户,你有一些已经写好的中间件,Fastify 允许你复用你的中间件。

Middlewares

Fastify支持开箱即用的Express / Restify / Connect中间件,这意味着您只需要插入旧代码即可使用!

const yourMiddleware = require('your-middleware')
fastify.use(yourMiddleware)

How to handle encapsulation and distribution

现在你几乎会使用所有的 Fastify 工具了,但是你可还会踩坑。

如何分配代码?

首选的方法是将所有代码包裹在 register 函数中,The preferred way to distribute an utility is to wrap all your code inside a register, 这样你的插件可以支持异步引导(因为decorate是一个同步的api),例如在数据库连接的情况下。

注意使用 fastify-plugin 模块让扩展的 Fastify 实例方法可以应用到根作用域。

const fp = require('fastify-plugin')
const dbClient = require('db-client')

function dbPlugin (fastify, opts, next) {
  dbClient.connect(opts.url, (err, conn) => {
    fastify.decorate('db', conn)
    next()
  })
}

module.exports = fp(dbPlugin)

Let's start!

Awesome, now you know everything you need to know about Fastify and its plugin system to start build your first plugin, and please if you do, tell us! We will add it to the ecosystem section of our documentation!

If you want to see some real world example, checkout:

  • point-of-view Templates rendering (ejs, pug, handlebars, marko) plugin support for Fastify.
  • fastify-mongodb Fastify MongoDB connection plugin, with this you can share the same MongoDb connection pool in every part of your server.
  • fastify-multipart Multipart support for Fastify
  • fastify-helmet Important security headers for Fastify

Do you feel it's missing something here? Let us know! :)

results matching ""

    No results matching ""