什麼是 AOP?參考下面文章:
https://openhome.cc/Gossip/SpringGossip/AOPConcept.html
其中
Cross-cutting concerns若直接撰寫在負責某商務的物件之流程中,會使得維護程式的成本增高,例如若您今天要將物件中的記錄功能修改或是移除該服務,則必須修改所 有撰寫曾記錄服務的程式碼,然後重新編譯,另一方面,Cross-cutting concerns混雜於商務邏輯之中,使得商務物件本身的邏輯或程式的撰寫更為複雜。
現在為了要加入日誌(Logging)與安全(Security)檢查等服務,物件的程式碼中若被硬生生的寫入相關的 Logging、Security 程式片段,則可使用以下圖解表示出 Cross-cutting 與 Cross-cutting concerns 的概念:
Cross-cutting concerns 若直接撰寫在負責某商務的物件之流程中,會使得維護程式的成本增高,例如若您今天要將物件中的日誌功能修改或是移除該服務,則必須修改所 有撰寫曾日誌服務的程式碼,然後重新編譯,另一方面,Cross-cutting concerns 混雜於商務邏輯之中,使得商務物件本身的邏輯或程式的撰寫更為複雜。
細節就要再請大家直接透過上面文章進行了解
目前在 Node.js 找到比較多人用的 AOP 套件如下:
此文章將會用 meld 來做展示
假設我們要在訂單建立完成後要進行 email 的送出
// 建立訂單
const order = await OrderService.createOrder(transaction, data);
// 將訂單資料送出 mail
await MessageService.sendMail(order);
當我們需要把它改成使用 SMS 簡訊的形式,我們就需要改為
// 建立訂單
const order = await OrderService.createOrder(transaction, data);
// 將訂單資料送出 SMS
await MessageService.sendSMS(order);
只有一個地方還好,若有多個地方的話就需要改為用 if else 判斷,如
// 建立訂單
const order = await OrderService.createOrder(transaction, data);
// 將訂單資料送出
if(config.sendEmail)
await MessageService.sendMail(order);
if(config.sendSMS)
await MessageService.sendSMS(order);
這樣是其中一個作法,我們可以換個方式,參考下面圖片
來源:http://www.slideshare.net/WidhianBramantya/icoict-new
其中 OrderService.createOrder(transaction, data);
我們可以根據需要將一些附加功能組合後成為實際上要用的函式。
除了一開始介紹的 send mail 的功能,也有像是 logging 或是權限控管,讓我們主要的程式專注在主要的商業邏輯,其他附加的功能可以根據需要掛載上去。
有了主要的概念後,我們可以使用 meld
來改寫目前的程式,在伺服器啟動時,我們可以呼叫下面函式:
var sendMailAround = async function(joinpoint) {
console.log("=== sendMailAround start ===");
var result = await joinpoint.proceed();
await MessageService.sendMail(result);
console.log("=== sendMailAround success ===");
return order;
}
global.OrderService.createOrder = meld.around(OrderService.createOrder, sendMailAround);
其中 meld.around(OrderService.createOrder, sendMailAround);
將把 OrderService.createOrder
轉化為 joinpoint.proceed();
如此我們就可以在 sendMailAround
進行後續的處理,原本的邏輯就會被改為
var result = await joinpoint.proceed();
await MessageService.sendMail(result);
類似的處理方式可以改為
meld.around(OrderService.createOrder, sendSmsAround);
如此就可以讓本來需要 if else 的處理,改為組合積木的方式來進行,若 log 或是 email 我們都不需要,主要的程式碼不需要做任何調整,只要把設定檔拿掉就可以讓主要流程繼續運作。
另外一個角度,若有跟類似需要送 mail 的函式,比如註冊成功,我們可以重覆使用 sendMailAround 如:
meld.around(AuthService.register, sendSmsAround);
重覆使用 sendSmsAround 這層附加功能,只要在設計上考慮到回傳格式的一致。另外一方面來說,若有考慮到這樣的應用情境,也可以讓程式碼更容易進行組合拆解。
實際使用如下
console.log("=== OrderService.createOrder start ===");
const order = await OrderService.createOrder(transaction, data);
console.log("=== OrderService.createOrder end ===");
AOP 設置:
var sendMailAround = async function(joinpoint) {
console.log("=== sendMailAround start ===");
var result = await joinpoint.proceed();
await MessageService.sendMail(result);
console.log("=== sendMailAround success ===");
return order;
}
global.OrderService.createOrder = meld.around(OrderService.createOrder, sendMailAround);
執行結果如下
=== OrderService.createOrder start ===
=== sendMailAround start ===
2017-02-10 15:20:35.40 <info> OrderService.js:17 (_callee$) 產生訂單編號: 20170210539400001
=== sendMailAround success ===
=== OrderService.createOrder end ===
若我們不需要 send mail 我們只要把下面程式碼移除
//global.OrderService.createOrder = meld.around(OrderService.createOrder, sendMailAround);
實際測試就會變為
=== OrderService.createOrder start ===
2017-02-10 15:22:18.80 <info> OrderService.js:17 (_callee$) 產生訂單編號: 20170210879800001
=== OrderService.createOrder end ===
可以很方便的進行替換或是移除。
結論
AOP 的概念,筆者一開始知道這樣的應用是從 JAVA Spring 的框架而來,在 JAVA 語言是一個很成熟的技術,唯 Node.js 還未有 Spring 這樣的公司在背後發展維護類似 Spring 這樣的框架,即使是上面星星數較多的專案,也已經 2 年多沒有在維護,著時可惜。
不過 AOP 這樣不同方向的開發方式是值得參考的,除了讓商業邏輯可以更乾淨之外,也可以用讓主要程式根據不同需求進行組合,原本寫程式為橫向一層一層堆疊,變得可以用縱向切入的方式替程式碼加上不同的處理,在程式架構上可以有更多選擇。
同樣的概念也一直不停在不同的語言實作,所以學習程式開發,還是要在一個語言學習夠久,相關特性也比較容易融會貫通,與大家分享!