首先, 不要恐慌!
Fastify 從一開(kāi)始就搭建成非常模塊化的系統(tǒng). 我們搭建了非常強(qiáng)健的 API 來(lái)允許你創(chuàng)建命名空間, 來(lái)添加工具方法. Fastify 創(chuàng)建的封裝模型可以讓你在任何時(shí)候?qū)⒛愕膽?yīng)用分割成不同的微服務(wù), 而無(wú)需重構(gòu)整個(gè)應(yīng)用.
內(nèi)容清單
就像在 JavaScript 萬(wàn)物都是對(duì)象, 在 Fastify 萬(wàn)物都是插件.你的路由, 你的工具方法等等都是插件. 無(wú)論添加什么功能的插件, 你都可以使用 Fastify 優(yōu)秀又獨(dú)一無(wú)二的 API: register.
fastify.register(
require('./my-plugin'),
{ options }
)
register 創(chuàng)建一個(gè)新的 Fastify 上下文, 這意味著如果你對(duì) Fastify 的實(shí)例做任何改動(dòng), 這些改動(dòng)不會(huì)反映到上下文的父級(jí)上. 換句話(huà)說(shuō), 封裝!
為什么封裝這么重要?那么, 假設(shè)你創(chuàng)建了一個(gè)具有開(kāi)創(chuàng)性的初創(chuàng)公司, 你會(huì)怎么做? 你創(chuàng)建了一個(gè)包含所有東西的 API 服務(wù)器, 所有東西都在同一個(gè)地方, 一個(gè)龐然大物!現(xiàn)在, 你增長(zhǎng)得非常迅速, 想要改變架構(gòu)去嘗試微服務(wù). 通常這意味著非常多的工作, 因?yàn)榻徊嬉蕾?lài)和缺少關(guān)注點(diǎn)的分離.Fastify 在這個(gè)層面上可以幫助你很多, 多虧了封裝模型, 它完全避免了交叉依賴(lài), 并且?guī)椭銓⒔M織成高聚合的代碼塊.
讓我們回到如何正確地使用 register.插件必須輸出一個(gè)有以下參數(shù)的方法
module.exports = function (fastify, options, done) {}
fastify 就是封裝的 Fastify 實(shí)例, options 就是選項(xiàng)對(duì)象, 而 done 是一個(gè)在插件準(zhǔn)備好了之后必須要調(diào)用的方法.
Fastify 的插件模型是完全可重入的和基于圖(數(shù)據(jù)結(jié)構(gòu))的, 它能夠處理任何異步代碼并且保證插件的加載順序, 甚至是關(guān)閉順序! 如何做到的? 很高興你發(fā)問(wèn)了, 查看下 avvio! Fastify 在 .listen(), .inject() 或者 .ready() 被調(diào)用了之后開(kāi)始加載插件.
在插件里面你可以做任何想要做的事情, 注冊(cè)路由, 工具方法 (我們馬上會(huì)看到這個(gè)) 和進(jìn)行嵌套的注冊(cè), 只要記住當(dāng)所有都設(shè)置好了后調(diào)用 done!
module.exports = function (fastify, options, done) {
fastify.get('/plugin', (request, reply) => {
reply.send({ hello: 'world' })
})
done()
}
那么現(xiàn)在你已經(jīng)知道了如何使用 register API 并且知道它是怎么工作的, 但我們?nèi)绾谓o Fastify 添加新的功能, 并且分享給其他的開(kāi)發(fā)者?
好了, 假設(shè)你寫(xiě)了一個(gè)非常好的工具方法, 因此你決定在你所有的代碼里都能夠用這個(gè)方法. 你改怎么做? 可能是像以下代碼一樣:
// your-awesome-utility.js
module.exports = function (a, b) {
return a + b
}
const util = require('./your-awesome-utility')
console.log(util('that is ', 'awesome'))
現(xiàn)在你需要在所有需要這個(gè)方法的文件中引入它. (別忘了你可能在測(cè)試中也需要它).
Fastify 提供了一個(gè)更優(yōu)雅的方法, 裝飾器. 創(chuàng)建一個(gè)裝飾器非常簡(jiǎn)單, 只要使用 decorate API:
fastify.decorate('util', (a, b) => a + b)
現(xiàn)在你可以在任意地方通過(guò) fastify.util 調(diào)用你的方法, 甚至在你的測(cè)試中.這里神奇的是: 你還記得之前我們討論的封裝? 同時(shí)使用 register 和 decorate 可以實(shí)現(xiàn), 讓我用例子來(lái)闡明這個(gè)事情:
fastify.register((instance, opts, done) => {
instance.decorate('util', (a, b) => a + b)
console.log(instance.util('that is ', 'awesome'))
done()
})
fastify.register((instance, opts, done) => {
console.log(instance.util('that is ', 'awesome')) // 這里會(huì)拋錯(cuò)
done()
})
在第二個(gè)注冊(cè)器中調(diào)用 instance.util 會(huì)拋錯(cuò), 因?yàn)?nbsp;util 只存在第一個(gè)注冊(cè)器的上下文中.讓我們更深入地看一下: 當(dāng)使用 register API 每次都會(huì)創(chuàng)建一個(gè)新的上下文而且這避免了上文提到的這個(gè)狀況.
但是注意, 封裝只會(huì)在父級(jí)和同級(jí)中有效, 不會(huì)在子級(jí)中有效.
fastify.register((instance, opts, done) => {
instance.decorate('util', (a, b) => a + b)
console.log(instance.util('that is ', 'awesome'))
fastify.register((instance, opts, done) => {
console.log(instance.util('that is ', 'awesome')) // 這里不會(huì)拋錯(cuò)
done()
})
done()
})
fastify.register((instance, opts, done) => {
console.log(instance.util('that is ', 'awesome')) // 這里會(huì)拋錯(cuò)
done()
})
PS: 如果你需要全局的工具方法, 請(qǐng)注意要聲明在應(yīng)用根作用域上. 或者你可以使用 fastify-plugin 工具, 參考.
decorate 不是唯一可以用來(lái)擴(kuò)展服務(wù)器的功能的 API, 你還可以使用 decorateRequest 和 decorateReply.
decorateRequest 和 decorateReply? 為什么我們已經(jīng)有了 decorate 還需要它們?好問(wèn)題, 是為了讓開(kāi)發(fā)者更方便地使用 Fastify. 讓我們看看這個(gè)例子:
fastify.decorate('html', payload => {
return generateHtml(payload)
})
fastify.get('/html', (request, reply) => {
reply
.type('text/html')
.send(fastify.html({ hello: 'world' }))
})
這個(gè)可行, 但可以變得更好!
fastify.decorateReply('html', function (payload) {
this.type('text/html') // this 是 'Reply' 對(duì)象
this.send(generateHtml(payload))
})
fastify.get('/html', (request, reply) => {
reply.html({ hello: 'world' })
})
你可以對(duì) request 對(duì)象做同樣的事:
fastify.decorate('getHeader', (req, header) => {
return req.headers[header]
})
fastify.addHook('preHandler', (request, reply, done) => {
request.isHappy = fastify.getHeader(request.raw, 'happy')
done()
})
fastify.get('/happiness', (request, reply) => {
reply.send({ happy: request.isHappy })
})
這個(gè)也可行, 但可以變得更好!
fastify.decorateRequest('setHeader', function (header) {
this.isHappy = this.headers[header]
})
fastify.decorateRequest('isHappy', false) // 這會(huì)添加到 Request 對(duì)象的原型中, 好快!
fastify.addHook('preHandler', (request, reply, done) => {
request.setHeader('happy')
done()
})
fastify.get('/happiness', (request, reply) => {
reply.send({ happy: request.isHappy })
})
我們見(jiàn)識(shí)了如何擴(kuò)展服務(wù)器的功能并且如何處理封裝系統(tǒng), 但是假如你需要加一個(gè)方法, 每次在服務(wù)器 "emits" 事件的時(shí)候執(zhí)行這個(gè)方法, 該怎么做?
你剛剛構(gòu)建了工具方法, 現(xiàn)在你需要在每個(gè)請(qǐng)求的時(shí)候都執(zhí)行這個(gè)方法, 你大概會(huì)這樣做:
fastify.decorate('util', (request, key, value) => { request.key = value })
fastify.get('/plugin1', (request, reply) => {
fastify.util(request, 'timestamp', new Date())
reply.send(request)
})
fastify.get('/plugin2', (request, reply) => {
fastify.util(request, 'timestamp', new Date())
reply.send(request)
})
我想大家都同意這個(gè)代碼是很糟的. 代碼重復(fù), 可讀性差并且不能擴(kuò)展.
那么你該怎么消除這個(gè)問(wèn)題呢? 是的, 使用鉤子方法!
fastify.decorate('util', (request, key, value) => { request.key = value })
fastify.addHook('preHandler', (request, reply, done) => {
fastify.util(request, 'timestamp', new Date())
done()
})
fastify.get('/plugin1', (request, reply) => {
reply.send(request)
})
fastify.get('/plugin2', (request, reply) => {
reply.send(request)
})
現(xiàn)在每個(gè)請(qǐng)求都會(huì)運(yùn)行工具方法, 很顯然你可以注冊(cè)任意多的需要的鉤子方法.有時(shí), 你希望只在一個(gè)路由子集中執(zhí)行鉤子方法, 這個(gè)怎么做到? 對(duì)了, 封裝!
fastify.register((instance, opts, done) => {
instance.decorate('util', (request, key, value) => { request.key = value })
instance.addHook('preHandler', (request, reply, done) => {
instance.util(request, 'timestamp', new Date())
done()
})
instance.get('/plugin1', (request, reply) => {
reply.send(request)
})
done()
})
fastify.get('/plugin2', (request, reply) => {
reply.send(request)
})
現(xiàn)在你的鉤子方法只會(huì)在第一個(gè)路由中運(yùn)行!
你可能已經(jīng)注意到, request and reply 不是標(biāo)準(zhǔn)的 Nodejs request 和 response 對(duì)象, 而是 Fastify 對(duì)象.
Fastify 支持 開(kāi)箱即用的 Express/Restify/Connect 中間件, 這意味著你可以直接插入你原來(lái)的代碼而不會(huì)有任何問(wèn)題! (當(dāng)然, 更快)假設(shè)你是從 Express 或者 Restify 這類(lèi)框架過(guò)來(lái)的, 并且你已經(jīng)知道哪些中間件是你需要的, 你也不想重寫(xiě)這塊工作, Fastify 正好可以幫助你實(shí)現(xiàn)目標(biāo). 我們是怎么做到的? 請(qǐng)查看我們的中間件引擎, middie.
const yourMiddleware = require('your-middleware')
fastify.use(yourMiddleware)
完美, 現(xiàn)在你知道了(幾乎)所有的擴(kuò)展 Fastify 的工具. 但可能你遇到了一個(gè)大問(wèn)題: 如何分發(fā)你的代碼?
我們推薦將所有代碼包裹在一個(gè)注冊(cè)器中分發(fā), 這樣你的插件可以支持異步啟動(dòng) (decorate 是一個(gè)同步 API), 例如建立數(shù)據(jù)庫(kù)鏈接.
等等? 你不是告訴我 register 會(huì)創(chuàng)建封裝的上下文, 那么我創(chuàng)建的不是就外層不可見(jiàn)了?是的, 我是說(shuō)過(guò). 但我沒(méi)告訴你的是, 你可以通過(guò) fastify-plugin 模塊告訴 Fastify 不要進(jìn)行封裝.
const fp = require('fastify-plugin')
const dbClient = require('db-client')
function dbPlugin (fastify, opts, done) {
dbClient.connect(opts.url, (err, conn) => {
fastify.decorate('db', conn)
done()
})
}
module.exports = fp(dbPlugin)
你還可以告訴 fastify-plugin 去檢查安裝的 Fastify 版本, 萬(wàn)一你需要特定的 API.
正如前面所述,F(xiàn)astify 在 .listen()、.inject() 以及 .ready() 被調(diào)用,也即插件被聲明 之后 才開(kāi)始加載插件。這么一來(lái),即使插件通過(guò) decorate 向外部的 fastify 實(shí)例注入了變量,在調(diào)用 .listen()、.inject() 和 .ready() 之前,這些變量是獲取不到的。
當(dāng)你需要在 register 方法的 options 參數(shù)里使用另一個(gè)插件注入的變量時(shí),你可以向 options 傳遞一個(gè)函數(shù)參數(shù),而不是對(duì)象:
const fastify = require('fastify')()
const fp = require('fastify-plugin')
const dbClient = require('db-client')
function dbPlugin (fastify, opts, done) {
dbClient.connect(opts.url, (err, conn) => {
fastify.decorate('db', conn)
done()
})
}
fastify.register(fp(dbPlugin), { url: 'https://example.com' })
fastify.register(require('your-plugin'), parent => {
return { connection: parent.db, otherOption: 'foo-bar' }
})
在上面的例子中,register 方法的第二個(gè)參數(shù)的 parent 變量是注冊(cè)了插件的外部 fastify 實(shí)例的一份拷貝。這就意味著我們可以獲取到之前聲明的插件所注入的變量了。
自 Node.js v13.3.0 開(kāi)始, ESM 也被支持了!寫(xiě)插件時(shí),你只需要將其作為 ESM 模塊導(dǎo)出即可!
// plugin.mjs
async function plugin (fastify, opts) {
fastify.get('/', async (req, reply) => {
return { hello: 'world' }
})
}
export default plugin
你的插件也可能在啟動(dòng)的時(shí)候失敗. 或許你預(yù)料到這個(gè)并且在這種情況下有特定的處理邏輯. 你該怎么實(shí)現(xiàn)呢? after API 就是你需要的. after 注冊(cè)一個(gè)回調(diào), 在注冊(cè)之后就會(huì)調(diào)用這個(gè)回調(diào), 它可以有三個(gè)參數(shù).回調(diào)會(huì)基于不同的參數(shù)而變化:
讓我們看看如何使用它:
fastify
.register(require('./database-connector'))
.after(err => {
if (err) throw err
})
太棒了, 現(xiàn)在你已經(jīng)知道了所有創(chuàng)建插件需要的關(guān)于 Fastify 和它的插件系統(tǒng)的知識(shí), 如果你寫(xiě)了插件請(qǐng)告訴我們! 我們會(huì)將它加入到 生態(tài) 章節(jié)中!
如果你想要看看真正的插件例子, 查看:
如果感覺(jué)還差什么? 告訴我們! :)
更多建議: