资讯专栏INFORMATION COLUMN

小程序·云开发的云函数路由高级玩法

ASCH / 3346人阅读

摘要:甚至如果你的小程序的后台逻辑不复杂,请求量不是特别大,完全可以在云函数里面做一个单一的微服务,根据路由来处理任务。介绍及用法为了方便大家试用,咱们腾讯云团队开发了,云函数路由管理库方便大家使用。

李成熙,腾讯云高级工程师。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 的中间件实现机制。

从上面的一些例子我们可以看出,主要是通过 userouter 两种方法传入路由以及相关处理的中间件。

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 的时候,层层去执行中间件。

最重要的运行中间件逻辑,则是在 servecompose 两个方法里。

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 functionnext,就是 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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/108540.html

相关文章

  • 开发初探 —— 更简便的程序开发模式

    摘要:年加入腾讯云云开发团队。基于,云服务商发展出这类更高级的开发服务。小程序云开发说了这么多无服务开发的概念优点,在小程序无服务开发这一块,腾讯云有什么样的作品呢。这就是今天要重点介绍的,小程序云开发,这就是腾讯云与微信联合研发后,交出的答卷。 李成熙,腾讯云高级工程师。2014年度毕业加入腾讯AlloyTeam,先后负责过QQ群、花样直播、腾讯文档等项目。2018年加入腾讯云云开发团队。...

    iKcamp 评论0 收藏0
  • 手游开发如何选择后端服务

    摘要:云函数是万金油为实现用户游戏数据存储和每日任务分发,我们最先用了存储服务和云引擎。不过我们并没有用提供的来直接调用存储服务,而是选择用调用云引擎里面的云函数,然后通过云函数调用存储服务来实现相应的逻辑。 【 玩转 LeanCloud 】开发者投稿分享: 作者:赵天泽 作为一个通过 LeanCloud 入门后端开发的小白,一年多的开发历程让我收获满满。多个项目也在 LeanCloud 可...

    codecook 评论0 收藏0
  • 计算的1024种玩法】轻松搭建可自动横向扩展的同城双中心服务集群

    摘要:同城双中心服务灾备阿里云同地域下不同可用区的网络电力设备等都是物理隔离的,但是通过内网连接。二创建伸缩配置,也就是自动添加的云服务器的配置,这个过程和购买的流程一致。 前言:云服务器的特性主要就是体现在横向、纵向的弹性扩容上,纵向的话其实很好理解就是我们单台 ECS 配置不够用了马上升级一下配置,不过这种场景比较适合于一个网站稳健的发展状态,而且波动不大。或者说服务器有较长时间的资源占...

    ChristmasBoy 评论0 收藏0
  • 计算的1024种玩法】轻松搭建可自动横向扩展的同城双中心服务集群

    摘要:同城双中心服务灾备阿里云同地域下不同可用区的网络电力设备等都是物理隔离的,但是通过内网连接。二创建伸缩配置,也就是自动添加的云服务器的配置,这个过程和购买的流程一致。 前言:云服务器的特性主要就是体现在横向、纵向的弹性扩容上,纵向的话其实很好理解就是我们单台 ECS 配置不够用了马上升级一下配置,不过这种场景比较适合于一个网站稳健的发展状态,而且波动不大。或者说服务器有较长时间的资源占...

    testHs 评论0 收藏0

发表评论

0条评论

ASCH

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<