實務上經常會遇到程式流程夾雜許多非核心,但又必須得做的行為,例如 logging, auit, notification 或 cache.
這篇文章介紹如何使用 babel decorator plugin 實現 Aspect Object Programming。
將共用的功能抽到 decorator,讓整個流程變的清楚好懂。
開頭提到的這些行為在不同實作間會一直重複發生。對於想了解流程的人來說,這些其實不是核心的部分,如下圖所示:
note: 在 SRE 的世界同樣也會發生每個 container 需要加入非業務邏輯的功能,如提供 HTTPS。其解決方案是 https://tachingchen.com/tw/blog/desigining-distributed-systems-the-sidecar-pattern-concept/ 稱為 sidecar pattern
Babel Decorator Plugin
Install
在安裝 babel plugin 前,請先確保已安裝 @babel/core and @babel/preset-env
1 | npm install --save-dev @babel/plugin-proposal-decorators @babel/eslint-parser |
Usage
設定 babel config:
1 | module.exports = { |
如果專案有使用 ESLint,需要設定 parser 讓 ESLint 認得 decorator
1 | { |
Example
我們以 log 為例,babel decorator 本筫上是一個 wrapper function。其 signature 為 (value, context)
value
就是包裝的對象,可以是 function 或 class,使用 apply 取得對象的結果。
如此一來,我們可以選擇在包裝對象的前後插入程式碼,如:
1 | function log(value, context) { |
這裡宣告 log decorator,能印出呼叫對象的時間點及 error handling。
另一個需要注意的是 context
參數,它是一個 object 具有包裝對象的資訊。我們可以到 decorator playground 試玩,可以發現 context
有以下 property:
- kind
- name
- isStatic
- isPrivate
- getMetadata
- setMetadata
常用的有 kind
和 name
,前者為包裝對象的類型,在這個範例為 method
;後者為對象的名稱。
這些資訊能讓 decorator 做更有彈性的處理,例如針對 class 的 decorator。
需要注意的是,假如 decorator 對象是 static method,則包裝後的 method 也會是 static。
note: babel 最近 release Stage 3 decorators,其
context
有些許差異,使用上需多做留意。
我們也可以給予 decorator 參數,讓行為有更多變化,例如:
1 | const map = new Map(); |