近两年来,Serverless 无疑是前端圈里最火热的话题之一,在各种技术峰会、各种技术文章里都能看到它的身影。如果你是一名前端开发者,一定很奇怪:
“我们这些前端切图仔,为什么要关注 Serverless 这种云计算的概念?”
我们就从这个话题开始聊起吧。
这个问题用一句话回答就是:
“Serverless 解放了端开发者(不仅仅是 Web 开发者)的生产力,让端开发者可以更快、更好、更灵活地开发各种端上应用,不需要投入太多精力关注于后端服务的实现。”
下面我们就来慢慢解释这句话。
在前端的史前时代,那个时候甚至还没有”前端工程师“这个职位分类,所有人都是后台开发,所有的网页和 Web 应用都是来自于后台渲染,CGI、PHP (甚至你可能都没听说过的 Perl)便是当时 Web 技术的代名词,那个时候的 JS 和 CSS,不过是“切图仔”在网页里写动效和弹窗的小玩具而已。
后来,AJAX 技术出现了,最早在 Outlook Web Access 中出现,随后随着 Google Map、Gmail 等大型 Web 应用的实践,逐步为人所知。这项技术让页面内的 JS 也能异步地向服务器发起各种请求,并且把数据渲染到页面上。这个时候,前端工程师们开始接管视图层逻辑:
再后来,Node.js 诞生,大大降低了前端开发者开发一个后台服务的难度,这也让前端开发者逐渐接管了接管了渲染逻辑,在这期间,各大前端框架(代表性的 React、Vue)的服务器端渲染也逐步成熟。
既然能用 Node.js 来做服务端渲染,那么拿 Node.js 来写后台业务逻辑、实现各种 HTTP API 当然也不在话下,所以在一些公司里,前端接管业务逻辑,后台只负责各种底层微服务的架构就出现了,这也是目前很多大公司在实行的架构:
细心的你可能已经注意到了,在这个时候,渲染、HTTP API、后台业务逻辑这些东西,从端的角度看是属于服务端的,但是从分工角度看却属于前端开发的范畴,这就是 BFF(Backend for Frontend)的概念,它的优势在于:
这就是为什么大公司的很多业务,都开始越来越多地招 Node.js 全栈工程师的原因。
但是 BFF 并不是银弹,它还是有一定问题的,比如在国内通晓前后端开发的全栈工程师太少,很难撑起大体量的业务开发。而且国内大多数前端工程师缺少服务端开发的经验,让他们运维上百台服务器和一整套海量服务,有点强人所难:
而 Serverless 正是帮助前端工程师解决运维开发 BFF 的良药。
Serverless 这个单词,直译成中文的话,应该是“无服务器”。当然,这里的“无服务器”绝对不是说不需要任何服务器资源了,而是说不需要关心服务器的具体运维和管理,只需要写代码然后发布即可。
它包含了 FaaS(函数即服务)和 BaaS(后端即服务)两大块功能,把各种基础设施进行了抽象,即使不熟悉后端的开发者,也能快速高质量地开发出海量、稳定的后端服务,而这对于前端工程师维护 BFF 服务来说,几乎是量身定做的。
举一个最简单的例子,你现在需要上线一个新的 HTTP API(比如 /getUser?id=123
),如果使用 Serverless 服务的话,有多快呢?
你只需要写下面这个云函数:
module.exports = async function(events, context) { const { id } = events.query.id const userInfo = await fetchUserInfo(id) // 调用后端微服务,拉取用户信息 return userInfo }
然后发布这个云函数(假设命名为 getUser
),并且为它设置一条路由:
cloudbase service:create -f getUser -p /getUser
然后你就可以通过 https://xxx.com/getUser
来拉取数据了,同时还附赠海量弹性伸缩、异地多活、日志监控等多方位的能力。
如果你觉得这样很酷炫,那么不如来试一试 云开发 Cloudbase 吧!
云开发(Cloudbase)是腾讯云 TCB 团队(Tencent Cloudbase)出品的云端一体化产品方案,为广大的小程序、Web、移动端开发者提供一站式的 Serverless 服务。
当然这是官方的说法,用人话讲就是:
用了云开发,各个端的开发者就可以一站式地解决后端服务问题了!
实际上早在 2018 年,云开发就联合微信团队推出了「小程序·云开发」,如果你对它还不怎么了解,可以看这两篇文章:
而现在,云开发 For Web 也正式上线了!
云开发提供了云数据库、云函数、云存储、用户登录体系等一系列的后端能力,并且提供了各端的 SDK,让各个端的开发者能够基于这些能力,快速、优质地开发出功能丰富的应用。
Talk is cheap, Show me the code!
口说无凭,我们还是来直接看代码吧!
云开发提供了一个文档型的 NoSQL 数据库,与传统的云上数据库不同的是,云开发的数据库可以在各种客户端内使用 SDK 直接进行读写,比如 Web 应用、小程序内、Flutter 客户端等等。
下面我们以 Web 应用为例,展示云数据库的一系列功能。
CURD是数据库最基础的功能,云开发 SDK 提供了一套链式调用接口,对数据库进行读写:
// 使用 Web 端 SDK const cloudbase = require('tcb-js-sdk') const app = cloudbase.init({ /* 初始化... */ }) const db = app.database() // 插入文档 await db.collection('messages').add({ author: 'stark', message: 'Hello World!' }) // 查询文档 const data = await db.collection('messages').where({ author: 'stark' }).get() // 更新文档 await db.collection('messages').where({ author: 'stark' }).update({ message: 'Hi, Cloudbase!' }) // 删除文档 await db.collection('messages').where({ author: 'stark' }).remove()
普通的查询可能无法满足一些复杂的需求,比如联表、group等等。
云开发针对这些复杂的查询场景,推出了聚合搜索的功能,把一系列操作抽象为一个管道(pipeline),单次执行即可,避免了多次读的性能问题,我们以 group 操作为例:
const $ = db.command.aggregate const result = await db .collection('message') .aggregate() .group({ // 以 author 字段作为 key,统计相同 author 的数量 _id: '$author' messagesCount: $.sum(1) }) .end() //=> { "_id": "stark", messagesCount: 12 }
更多的聚合搜索功能,可以参考:Aggregate | 云开发 Cloudbase
在订票、预约、转账等等场景下,开发者可能会要求数据库能够保证一连串读写的原子性,避免出现竞争条件,这就是数据库事务的使用场景。
云开发数据库当然也提供了事务功能:
// 启动事务 const transaction = await db.startTransaction() // 在事务内读 const data = await transaction.collection('messages') .where({ /* <query> */}) .get() // 在事务内写 await transaction.collection('messages') .where({ /* <query> */}) .update({ /* <data> */}) // 提交事务 await transaction.commit()
在实时聊天室、实时数据看板等等场景下,我们经常会需要订阅数据库的更新,从而实现实时数据推送。
云开发的数据库提供的 watch()
方法就是为此设计的,每当数据库符合条件的文档发生变化时,都会触发 onChange
回调,示例代码如下:
await db.collection('messages') .where({/* <query> */}) .watch({ onChange: snapshot => { console.log("收到snapshot!", snapshot) }, onError: error => { console.log("收到error!", error) } })
更多信息可以参考:数据库实时推送 | 云开发 Cloudbase
所谓的云函数,便是在云端运行的、事件驱动的一段代码,它可以被 SDK 调用,也可以直接通过 HTTP 调用,还可以设置定时器让它定期运行:
// sum.js module.exports = async function(events) { return events.a + events.b }
这一小段代码很简单,但是隐藏在它之下的却是一整套庞大的 FaaS(函数即服务)基础设施,提供了诸如弹性伸缩、日志、监控告警等多方面的能力。
使用云开发的客户端 SDK,可以轻而易举地在各个端上调用云函数,我们以 Web 应用为例:
const cloudbase = require("tcb-js-sdk"); const app = cloudbase.init({/* 初始化 */}); app.callFunction({ // 云函数名称 name: "sum", // 传给云函数的参数 data: { a: 1, b: 2 } }) .then(res => { console.log(res); // 输出 "3" }) .catch(error);
你也许会觉得 SDK 体积庞大,太沉重了,那么你也可以选择使用 HTTP 来调用云函数,响应 HTTP 请求。
// hello.js module.exports = function() { return 'Hello, World!' }
然后我们直接通过命令行发布这个函数,并为它创建一条路由:
$ cloudbase functions:deploy hello $ cloudbase service:create -f hello -p /hello
随后便可以通过 url 直接访问这个云函数:
$ curl https://xxx.service.tcloudbase.com/hello Hello, World!
具体可以参考:https://docs.cloudbase.net/service/quick-start.html
在 Cloudbase 的云函数内,你可以直接使用 Node.js SDK,无需在初始化的时候额外注入秘钥:
const cloudbase = require('@cloudbase/node-sdk') // 无需使用服务端秘钥 const app = cloudbase.init() const data = await app.database().where().get()
更详细的内容可以参考:https://docs.cloudbase.net/api-reference/server/node/initialization.html
我们在开发应用的过程中,经常会遇到图片、文件上传的需求,并且可能需要为这些文件设置 CDN 访问。传统的流程是下面这样的:
但如果使用云开发,只需要在客户端调用 uploadFile
,就可以一步完成上面的三件事情:
const tcb = require("tcb-js-sdk"); const app = tcb.init({ env: 'your-env-id' }) const { fileID } = await app.uploadFile({ // 云端路径 cloudPath: "/a/b/c/filename", // 需要上传的文件,File 类型 filePath: document.getElementById('file').files[0] })
uploadFile
会返回一个 fileID
,是云开发内文件的唯一标识符,我们可以使用 getTempFileURL
来获取文件 URL 访问链接:
const tcb = require("tcb-js-sdk"); const app = tcb.init({ env: 'your-env-id' }) const { fileList } = app.getTempFileURL({ fileList: [ 'cloud://a/b/c' ] }) // fileList 是一个有如下结构的对象数组 // [{ // fileID: 'cloud://a/b/c', // 文件 ID // tempFileURL: 'http://xxx/xxx/xxx', // 临时文件网络链接 // maxAge: 120 * 60 * 1000, // 有效期 // }]
更详细的内容,可以参考:https://docs.cloudbase.net/storage/introduce.html
云开发除了上述的基础功能之外,还提供了一系列的扩展能力,包括但不仅限于:
更详细的内容,可以参考:https://docs.cloudbase.net/extension/introduce.html
上面的能力是不是有些让你看花眼了,完全不知道要怎么搭配起来使用?
其实一张图就可以解决:
图中的客户端SDK包括:
服务端 SDK 包括:
光看示例代码当然没有什么意思,我们接下来就拿云开发的一些能力,来快速开发一个实时在线聊天室吧。
项目代码:https://github.com/TencentCloudBase/cloudbase-realtime-demo
这是一个由 create-react-app
快速生成的脚手架项目,所以大部分构建和工程化的细节这里就略过不谈了,我们直接来看代码实现,大致上实现了三个功能,括号中是使用的云开发能力:
首先是我们的初始化流程,先使用匿名登录,然后建立实时数据推送的连接:
async function init() { // 使用匿名登录 await auth.anonymousAuthProvider().signIn(); // 使用 refreshToken 的前 6 位作为 uid setUid(auth.hasLoginState().credential.refreshToken.slice(0, 6)); // 建立实时数据推送连接 await db .collection("messages") .where({}) .watch({ onChange(snapshot) { setList(snapshot.docs); setLoading(false); }, onError(err) { console.log(err); }, }); }
建立实时连接之后,集合中的任何变化,都会触发 onChange()
回调,然后我们使用 setList()
来更新界面上的消息数据。
当然只读消息是不够的,我们还需要发送消息,具体实现非常简单,可以看 sendMessage()
方法,直接使用 add()
方法向数据库写入数据就可以了:
// 发送消息 async function sendMessage() { const message = { timestamp: new Date().getTime(), text, uid, }; await db.collection("test").add(message); // 清空输入栏 setText(""); }
当然以上只是局部的代码片段,整体代码可以参考:
https://github.com/TencentCloudBase/cloudbase-realtime-demo/blob/master/src/App.js
开发完毕之后,我们便可以使用 云开发静态网站 来托管我们的这个聊天室 Web 应用。
首先我们构建我们的应用:
$ npm run build
构建产物会生成到 build
目录下。
然后我们发布到静态托管即可(托管前,请先开通静态网站):
$ cloudbase hosting:deploy ./build -e your-env-id
发布完成后,你便可以通过 https://xxxx.tcb.qcloud.la/xxxx
的来访问你的应用了。进一步,你还可以为它绑定自定义域名。
PS:实际上,云开发的主页和官方文档,就是这样托管的(毕竟做云服务的,最重要的就是 Eating your own dog food 嘛)。
当然,除了这个实战 Demo 以外,还可以看看一些真正业务的实践:
给 Web 开发者带来效率和质量上的提升,帮助他们开发更多更优质的应用,免去运维、后台开发的烦恼,是云开发不变的愿景。虽然我们目前已经有了很多强大又方便使用的能力,但这远不是我们的终点(实际上我们才刚刚起步),在未来我们将会持续完善下面的能力,进一步完善云开发的体系:
PS:如果你觉得这篇文章对你有帮助,不妨花几分钟时间,来试一试快速开始吧。
原创声明,本文系作者授权云+社区发表,未经许可,不得转载。
如有侵权,请联系 yunjia_community@tencent.com 删除。
我来说两句