摘要:甚至如果你的小程序的后臺邏輯不復雜,請求量不是特別大,完全可以在云函數里面做一個單一的微服務,根據路由來處理任務。介紹及用法為了方便大家試用,咱們騰訊云團隊開發了,云函數路由管理庫方便大家使用。
李成熙,騰訊云高級工程師。2014年度畢業加入騰訊AlloyTeam,先后負責過QQ群、花樣直播、騰訊文檔等項目。2018年加入騰訊云云開發團隊。專注于性能優化、工程化和小程序服務。微博 | 知乎 | Github概念回顧
在掘金開發者大會上,在推薦實踐那里,我有提到一種云函數的用法,我們可以將相同的一些操作,比如用戶管理、支付邏輯,按照業務的相似性,歸類到一個云函數里,這樣比較方便管理、排查問題以及邏輯的共享。甚至如果你的小程序的后臺邏輯不復雜,請求量不是特別大,完全可以在云函數里面做一個單一的微服務,根據路由來處理任務。
用下面三幅圖可以概括,我們來回顧一下:
比如這里就是傳統的云函數用法,一個云函數處理一個任務,高度解耦。
第二幅架構圖就是嘗試將請求歸類,一個云函數處理某一類的請求,比如有專門負責處理用戶的,或者專門處理支付的云函數。
最后一幅圖顯示這里只有一個云函數,云函數里有一個分派任務的路由管理,將不同的任務分配給不同的本地函數處理。
tcb-router 介紹及用法為了方便大家試用,咱們騰訊云 Tencent Cloud Base 團隊開發了 tcb-router,云函數路由管理庫方便大家使用。
那具體怎么使用 tcb-router 去實現上面提到的架構呢?下面我會逐一舉例子。
架構一:一個云函數處理一個任務
這種架構下,其實不需要用到 tcb-router,像普通那樣寫好云函數,然后在小程序端調用就可以了。
云函數
// 函數 router exports.main = (event, context) => { return { code: 0, message: "success" }; };
小程序端
wx.cloud.callFunction({ name: "router", data: { name: "tcb", company: "Tencent" } }).then((res) => { console.log(res); }).catch((e) => { console.log(e); });
架構二: 按請求給云函數歸類
此類架構就是將相似的請求歸類到同一個云函數處理,比如可以分為用戶管理、支付等等的云函數。
云函數
// 函數 user const TcbRouter = require("tcb-router"); exports.main = async (event, context) => { const app = new TcbRouter({ event }); app.router("register", async (ctx, next) => { await next(); }, async (ctx, next) => { await next(); }, async (ctx) => { ctx.body = { code: 0, message: "register success" } }); app.router("login", async (ctx, next) => { await next(); }, async (ctx, next) => { await next(); }, async (ctx) => { ctx.body = { code: 0, message: "login success" } }); return app.serve(); }; // 函數 pay const TcbRouter = require("tcb-router"); exports.main = async (event, context) => { const app = new TcbRouter({ event }); app.router("makeOrder", async (ctx, next) => { await next(); }, async (ctx, next) => { await next(); }, async (ctx) => { ctx.body = { code: 0, message: "make order success" } }); app.router("pay", async (ctx, next) => { await next(); }, async (ctx, next) => { await next(); }, async (ctx) => { ctx.body = { code: 0, message: "pay success" } }); return app.serve(); };
小程序端
// 注冊用戶 wx.cloud.callFunction({ name: "user", data: { $url: "register", name: "tcb", password: "09876" } }).then((res) => { console.log(res); }).catch((e) => { console.log(e); }); // 下單商品 wx.cloud.callFunction({ name: "pay", data: { $url: "makeOrder", id: "xxxx", amount: "3" } }).then((res) => { console.log(res); }).catch((e) => { console.log(e); });
架構三: 由一個云函數處理所有服務
云函數
// 函數 router const TcbRouter = require("tcb-router"); exports.main = async (event, context) => { const app = new TcbRouter({ event }); app.router("user/register", async (ctx, next) => { await next(); }, async (ctx, next) => { await next(); }, async (ctx) => { ctx.body = { code: 0, message: "register success" } }); app.router("user/login", async (ctx, next) => { await next(); }, async (ctx, next) => { await next(); }, async (ctx) => { ctx.body = { code: 0, message: "login success" } }); app.router("pay/makeOrder", async (ctx, next) => { await next(); }, async (ctx, next) => { await next(); }, async (ctx) => { ctx.body = { code: 0, message: "make order success" } }); app.router("pay/pay", async (ctx, next) => { await next(); }, async (ctx, next) => { await next(); }, async (ctx) => { ctx.body = { code: 0, message: "pay success" } }); return app.serve(); };
小程序端
// 注冊用戶 wx.cloud.callFunction({ name: "router", data: { $url: "user/register", name: "tcb", password: "09876" } }).then((res) => { console.log(res); }).catch((e) => { console.log(e); }); // 下單商品 wx.cloud.callFunction({ name: "router", data: { $url: "pay/makeOrder", id: "xxxx", amount: "3" } }).then((res) => { console.log(res); }).catch((e) => { console.log(e); });借鑒 Koa2 的中間件機制實現云函數的路由管理
小程序·云開發的云函數目前更推薦 async/await 的玩法來處理異步操作,因此這里也參考了同樣是基于 async/await 的 Koa2 的中間件實現機制。
從上面的一些例子我們可以看出,主要是通過 use 和 router 兩種方法傳入路由以及相關處理的中間件。
use 只能傳入一個中間件,路由也只能是字符串,通常用于 use 一些所有路由都得使用的中間件
// 不寫路由表示該中間件應用于所有的路由 app.use(async (ctx, next) => { }); app.use("router", async (ctx, next) => { });
router 可以傳一個或多個中間件,路由也可以傳入一個或者多個。
app.router("router", async (ctx, next) => { }); app.router(["router", "timer"], async (ctx, next) => { await next(); }, async (ctx, next) => { await next(); }, async (ctx, next) => { });
不過,無論是 use 還是 router,都只是將路由和中間件信息,通過 _addMiddleware 和 _addRoute 兩個方法,錄入到 _routerMiddlewares 該對象中,用于后續調用 serve 的時候,層層去執行中間件。
最重要的運行中間件邏輯,則是在 serve 和 compose 兩個方法里。
serve 里主要的作用是做路由的匹配以及將中間件組合好之后,通過 compose 進行下一步的操作。比如以下這段節選的代碼,其實是將匹配到的路由的中間件,以及 * 這個通配路由的中間件合并到一起,最后依次執行。
let middlewares = (_routerMiddlewares[url]) ? _routerMiddlewares[url].middlewares : []; // put * path middlewares on the queue head if (_routerMiddlewares["*"]) { middlewares = [].concat(_routerMiddlewares["*"].middlewares, middlewares); }
組合好中間件后,執行這一段,將中間件 compose 后并返回一個函數,傳入上下文 this 后,最后將 this.body 的值 resolve,即一般在最后一個中間件里,通過對 ctx.body 的賦值,實現云函數的對小程序端的返回:
const fn = compose(middlewares); return new Promise((resolve, reject) => { fn(this).then((res) => { resolve(this.body); }).catch(reject); });
那么 compose 是怎么組合好這些中間件的呢?這里截取部份代碼進行分析
function compose(middleware) { /** * ... 其它代碼 */ return function (context, next) { // 這里的 next,如果是在主流程里,一般 next 都是空。 let index = -1; // 在這里開始處理處理第一個中間件 return dispatch(0); // dispatch 是核心的方法,通過不斷地調用 dispatch 來處理所有的中間件 function dispatch(i) { if (i <= index) { return Promise.reject(new Error("next() called multiple times")); } index = i; // 獲取中間件函數 let handler = middleware[i]; // 處理完最后一個中間件,返回 Proimse.resolve if (i === middleware.length) { handler = next; } if (!handler) { return Promise.resolve(); } try { // 在這里不斷地調用 dispatch, 同時增加 i 的數值處理中間件 return Promise.resolve(handler(context, dispatch.bind(null, i + 1))); } catch (err) { return Promise.reject(err); } } } }
看完這里的代碼,其實有點疑惑,怎么通過 Promise.resolve(handler(xxxx)) 這樣的代碼邏輯可以推進中間件的調用呢?
首先,我們知道,handler 其實就是一個 async function,next,就是 dispatch.bind(null, i + 1) 比如這個:
async (ctx, next) => { await next(); }
而我們知道,dispatch 是返回一個 Promise.resolve 或者一個 Promise.reject,因此在 async function 里執行 await next(),就相當于觸發下一個中間件的調用。
當 compose 完成后,還是會返回一個 function (context, next),于是就走到下面這個邏輯,執行 fn 并傳入上下文 this 后,再將在中間件中賦值的 this.body resolve 出來,最終就成為云函數數要返回的值。
const fn = compose(middlewares); return new Promise((resolve, reject) => { fn(this).then((res) => { resolve(this.body); }).catch(reject); });
看到 Promise.resolve 一個 async function,許多人都會很困惑。其實撇除 next 這個往下調用中間件的邏輯,我們可以很好地將邏輯簡化成下面這段示例:
let a = async () => { console.log(1); }; let b = async () => { console.log(2); return 3; }; let fn = async () => { await a(); return b(); }; Promise.resolve(fn()).then((res) => { console.log(res); }); // 輸出 // 1 // 2 // 3
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/108540.html
摘要:年加入騰訊云云開發團隊。基于,云服務商發展出這類更高級的開發服務。小程序云開發說了這么多無服務開發的概念優點,在小程序無服務開發這一塊,騰訊云有什么樣的作品呢。這就是今天要重點介紹的,小程序云開發,這就是騰訊云與微信聯合研發后,交出的答卷。 李成熙,騰訊云高級工程師。2014年度畢業加入騰訊AlloyTeam,先后負責過QQ群、花樣直播、騰訊文檔等項目。2018年加入騰訊云云開發團隊。...
摘要:云函數是萬金油為實現用戶游戲數據存儲和每日任務分發,我們最先用了存儲服務和云引擎。不過我們并沒有用提供的來直接調用存儲服務,而是選擇用調用云引擎里面的云函數,然后通過云函數調用存儲服務來實現相應的邏輯。 【 玩轉 LeanCloud 】開發者投稿分享: 作者:趙天澤 作為一個通過 LeanCloud 入門后端開發的小白,一年多的開發歷程讓我收獲滿滿。多個項目也在 LeanCloud 可...
摘要:同城雙中心服務災備阿里云同地域下不同可用區的網絡電力設備等都是物理隔離的,但是通過內網連接。二創建伸縮配置,也就是自動添加的云服務器的配置,這個過程和購買的流程一致。 前言:云服務器的特性主要就是體現在橫向、縱向的彈性擴容上,縱向的話其實很好理解就是我們單臺 ECS 配置不夠用了馬上升級一下配置,不過這種場景比較適合于一個網站穩健的發展狀態,而且波動不大。或者說服務器有較長時間的資源占...
摘要:同城雙中心服務災備阿里云同地域下不同可用區的網絡電力設備等都是物理隔離的,但是通過內網連接。二創建伸縮配置,也就是自動添加的云服務器的配置,這個過程和購買的流程一致。 前言:云服務器的特性主要就是體現在橫向、縱向的彈性擴容上,縱向的話其實很好理解就是我們單臺 ECS 配置不夠用了馬上升級一下配置,不過這種場景比較適合于一個網站穩健的發展狀態,而且波動不大。或者說服務器有較長時間的資源占...
閱讀 3680·2021-11-23 09:51
閱讀 1044·2021-11-19 11:30
閱讀 3369·2019-08-29 14:16
閱讀 3375·2019-08-29 12:12
閱讀 2373·2019-08-26 13:40
閱讀 3483·2019-08-26 12:21
閱讀 3079·2019-08-26 11:55
閱讀 2229·2019-08-26 11:35