Node.JS 模塊驅(qū)動的方法學和標準化的 Stream 很適合創(chuàng)建 “微服務”(Microservices) 的系統(tǒng)架構 《What are some best architecture practices when designing a nodejs system?》。
James Halliday(Substack) 4年前有寫了一系列的模塊來幫助構建基于“微服務”(雖然那個時候這個名詞還不流行)的系統(tǒng)架構。其中服務間調(diào)用的核心模塊就是 dnode。dnode 已經(jīng)很久沒更新了,原因是代碼和功能(很小的功能集)已經(jīng)非常穩(wěn)定。
Vanilar Version
一個簡單的 dnode 例子如下:
dnode = require 'dnode'
net = require 'net'
grappling = require 'grappling-hook'
log = console.log.bind console
error = console.error.bind console
apis =
transform: (s, cb) ->
log '--original func calls'
cb null, s.replace(/[aeiou]{2,}/, 'oo').toUpperCase()
server = net.createServer (c) ->
d = dnode apis
c.pipe(d).pipe c
server.listen 5004
d = dnode()
d.on 'remote', (remote) ->
remote.transform 'beep', (err, s) ->
if err?
error 'Errors', err
else
log 'beep => ' + s
d.end()
c = net.connect 5004
c.pipe(d).pipe c
這段代碼很簡短,執(zhí)行結果是:
--original func calls
beep => BOOP
其功能為 Server 自動向 Client 發(fā)布了 apis 這個 rpc 接口,Client 可以在 d.on 'remote'
事件中獲得這個接口,然后調(diào)用 Server 提供的 transform
方法。
我在這里不會詳細介紹 dnode,你可以去它的 Github Repos. 學習。如果想了解協(xié)議細節(jié),我這有個中文版。
Hooked Version
我的需求是,要在 dnode Server 的 API 上,添加 Hook,這樣我可以在每個 API 方法的運行前后,根據(jù)需要插入特定的 pre
或者 post
邏輯。這樣的“注入”需求在后臺開發(fā)中很常見。
Javascript 的Hook 庫很多,我這里用的是 grappling-hook。用它的原因一方面是因為其功能比較豐富,另一方面是前一段研究 keystone.js,對這塊比較熟悉。
Hook 一個對象方法的例子如下:
grappling = require 'grappling-hook'
instance = grappling.create()
instance.addHooks {
transform: (s, cb) ->
cb null, s.replace(/[aeiou]{2,}/, 'oo').toUpperCase()
}
instance.pre 'transform', (s, done) ->
log 'before transform'
done()
.post 'transform', (s, done) ->
log 'after transform!'
done()
instance.transform 'beep', (err, res)-> console.log 'result', res
grappling-hook 的詳細用法請自行讀文檔。
上面這段程序的輸出結果是:
before transform
BOOP
after transform!
看起來可以工作了。
不過這里有一個問題,就是如果你把 instance
直接傳遞給 dnode (例如:d = dnode instance
)是有問題的。因為 instance 除了有 transform
之外包含大量的輔助方法。這些東西可多會被 dnode 作為接口定義傳遞給 Client,這不是我們要的,因此要清理,例子代碼如下:
...
apis =
transform: (s, cb) ->
log '--original func calls'
cb null, s.replace(/[aeiou]{2,}/, 'oo').toUpperCase()
wrap = (apis) ->
instance = grappling.create()
instance.addHooks apis
hooked = {}
hooked[k] = instance[k] for k of apis
{ instance, hooked }
{instance, hooked} = wrap apis
# Equip middlewares
# Callback func MUST have same numbers of arguments as the origin
instance.pre 'transform', (s, done) ->
log 'before transform'
done()
.post 'transform', (s, done) ->
log 'after transform!'
done()
...
wrap
函數(shù)返回一個“干凈的” hooked
過的 API 定義對象,這是可以被作為接口定義傳遞給 Client 的。同時,你可以通過 wrap
返回的另一個 instance
對象來慢慢添加你的 pre
、post
方法。而且,這些你可以對同一個方法添加不止一個 pre
、post
hooks。
任務完成。如果想直接看可執(zhí)行程序,源代碼在這里:https://github.com/jacobbubu/dnodeHook 。