iT邦幫忙

2022 iThome 鐵人賽

DAY 13
0
Modern Web

Fastify 101系列 第 13

[Fastify] Day13 - Decorator

  • 分享至 

  • xImage
  •  

大家好,我是 Yubin

透過 Decorator API,可以讓開發者自訂 Fastify 的核心物件,包括 FastifyInstance, FastifyRequestFastifyReply

這篇文章來講一下 Decorator 這個東西。


Decorator

如果想在 Fastify 的各個生命週期之間共用物件或函式,定義 Decorator 是不錯的做法。

如果沒有 decorator,藉由 JavaScript 動態型別的特性,或許可以寫成這樣。

// bad example!! Don't do this
server.addHook('preHandler', (request, reply, done) => {
  request.user = 'Yubin'
  done()
})

server.get('/', function (request, reply) {
  reply.send(`Hello, ${request.user}`)
})

這個方式是直接把想要的欄位,放進 request 物件中。
但這種作法不僅會破壞原本的形狀,也會影響 JavaScript 執行時期的最佳化,操作不慎可能會破壞整個 Fastify 的生命週期。

我們可以利用 decorate 的方式:

// Decorate request with a 'user' property
server.decorateRequest('user', '')

// Update our property
server.addHook('preHandler', (request, reply, done) => {
  request.user = 'Yubin'
  done()
})

server.get('/', (request, reply) => {
  reply.send(`Hello, ${request.user}!`) // 'Hello, Yubin!'
})

上述程式範例中,定義 FastifyRequest 有一個 decorate 叫做 user,給定的值為 '' (空字串)。

然後在 preHandler hook 中,透過 request.user 指定值,
GET / route 中取用 request.user 的值來做處理。

可以觀察到,透過 Decorator API,我們可以把想要的變數加進 Fastify 核心物件中。

要注意的是,decorator 的初始值要跟他的 Type 呼應,
預期是空字串的話用 '',預期是空物件的話可以用 null

Decorator API

  • decorate(name, value, [dependencies])

定義 server instance,也就是 FastifyInstance 的 decorate。

  • decorateRequest(name, value, [dependencies])

定義 request 物件,也就是 FastifyRequest 的 decorate。

  • decorateReply(name, value, [dependencies])

定義 reply 物件,也就是 FastifyReply 的 decorate。

這三個名字很像的 API 分別定義 server/request/reply 的 decorator。

第一個參數 name,指的是那個 decorator 的名稱,型態為字串。
第二個參數 value,指的是那個 decorator 的值或物件或方法。
第三個參數 dependencies 是 Optional 的,指的是該 decorator 相依於哪些 decorators。

若該 decorator 沒有滿足相依性的檢查,在啟動 Server 的時候會拋出 FST_ERR_DEC_MISSING_DEPENDENCY 的錯誤,讓伺服器啟動失敗。


可以定義一些實用的方法或讓整個 Fastify 生命週期共用的資訊,如下範例:

server.decorate('utility', function () {
  // Something very useful
})

server.decorate('conf', {
  db: 'some.db',
  port: 3000
})

定義好 decorator 後,可以在任意地方透過該變數名稱存取到相應的物件或方法。

fastify.utility()
console.log(fastify.conf.db)

要特別注意的是,若要定義物件給 FastifyRequest 或 FastifyReply 的 decorator,如下:

// bad practice, don't do it
server.decorateRequest('data', { name: 'Yubin'})

上述程式中,所有的 Request 都會共享同一個物件。任何變動都會影響到其他 request,可能會導致安全性的漏洞或 memory leak。
為了讓每個 request 進來都有完整的值,可以在 onRequest hook 中對該 decorator 賦值。

這邊使用 fastify-plugin 定義 plugin

import fastifyPlugin = from 'fastify-plugin'

async function myPlugin (app) {
  app.decorateRequest('foo', null)
  app.addHook('onRequest', async (req, reply) => {
    req.foo = { bar: 42 }
  })
}

export default fastifyPlugin(myPlugin)

同樣的 issue 在 FastifyReply 上也會發生,在處理 decorator 的時候要注意。


  • hasDecorator(name)
  • hasRequestDecorator(name)
  • hasReplyDecorator(name)

這三個 Decorator API,會回傳 server/request/reply 中有沒有特定的 decorator 存在,
回傳型態為 boolean。

fastify.hasDecorator('utility')
fastify.hasRequestDecorator('utility')
fastify.hasReplyDecorator('utility')

注意,在同一個 Scope 中,重複定義相同名字的 decorator 會拋出錯誤。

Decorator with TypeScript

上面的程式大多是 JavaScript 範例,我們在使用 TypeScript 做開發的時候,只有定義 Decorators 是不夠的,因為 TypeScript Engine 無法推斷我們已經對該型態做擴展,所以無法讀取新的欄位。

以這段程式來看:

server.decorateRequest('name', 'Yubin')

server.get('/', (request, reply) => {
  reply.status(200).send({ msg: `Hello ${request.name}` })
})

會發現 TypeScript 提示說,沒有 name 這個欄位。

https://ithelp.ithome.com.tw/upload/images/20220928/201511486DSnA1Gu8B.jpg

這是因為,雖然我們知道透過 decorateRequest 方法,可以增加 decorator 在 FastifyRequest 上,讓我們可以對 request 物件拿到 name 這個欄位。但 request 的型態依然是 FastifyRequest,裡面並不存在 name 這個欄位,所以 TypeScript 會噴錯,也無法順利 Compile。

解法就是,告訴 TypeScript 我對 FastifyRequest 進行擴充了(多了 name 欄位)。

可以運用 declare moduleinterface,範例如下:

declare module 'fastify' {
  interface FastifyRequest {
    name: string
  }
}

server.decorateRequest('name', 'Yubin')

server.get('/', (request, reply) => {
  reply.status(200).send({ msg: `Hello ${request.name}` })
})

這樣一來,TypeScript 就知道 FastifyRequest 裡面多了一個 name 的欄位,並且型態是 string,型態正確的情況下除了可以順利 Compile,也可以讓我們對程式的信心程度更加提升。

上述程式以 FastifyRequest 為範例,若要增加的是 FastifyInstance 或 FastifyReply 的 Decorators,只要對各自的 interface 進行擴充就沒問題了。


上一篇
[Fastify] Day12 - FastifyReply
下一篇
[Fastify] Day14 - Plugin
系列文
Fastify 10130
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言