近兩年來,Serverless 無疑是前端圈里最火熱的話題之一,在各種技術(shù)峰會(huì)、各種技術(shù)文章里都能看到它的身影。如果你是一名前端開發(fā)者,一定很奇怪:
“我們這些前端切圖仔,為什么要關(guān)注 Serverless 這種云計(jì)算的概念?”
我們就從這個(gè)話題開始聊起吧。
這個(gè)問題用一句話回答就是:
“Serverless 解放了端開發(fā)者(不僅僅是 Web 開發(fā)者)的生產(chǎn)力,讓端開發(fā)者可以更快、更好、更靈活地開發(fā)各種端上應(yīng)用,不需要投入太多精力關(guān)注于后端服務(wù)的實(shí)現(xiàn)?!?/strong>
下面我們就來慢慢解釋這句話。
在前端的史前時(shí)代,那個(gè)時(shí)候甚至還沒有”前端工程師“這個(gè)職位分類,所有人都是后臺開發(fā),所有的網(wǎng)頁和 Web 應(yīng)用都是來自于后臺渲染,CGI、PHP (甚至你可能都沒聽說過的 Perl)便是當(dāng)時(shí) Web 技術(shù)的代名詞,那個(gè)時(shí)候的 JS 和 CSS,不過是“切圖仔”在網(wǎng)頁里寫動(dòng)效和彈窗的小玩具而已。
后來,AJAX 技術(shù)出現(xiàn)了,最早在 Outlook Web Access 中出現(xiàn),隨后隨著 Google Map、Gmail 等大型 Web 應(yīng)用的實(shí)踐,逐步為人所知。這項(xiàng)技術(shù)讓頁面內(nèi)的 JS 也能異步地向服務(wù)器發(fā)起各種請求,并且把數(shù)據(jù)渲染到頁面上。這個(gè)時(shí)候,前端工程師們開始接管視圖層邏輯:
再后來,Node.js 誕生,大大降低了前端開發(fā)者開發(fā)一個(gè)后臺服務(wù)的難度,這也讓前端開發(fā)者逐漸接管了接管了渲染邏輯,在這期間,各大前端框架(代表性的 React、Vue)的服務(wù)器端渲染也逐步成熟。
既然能用 Node.js 來做服務(wù)端渲染,那么拿 Node.js 來寫后臺業(yè)務(wù)邏輯、實(shí)現(xiàn)各種 HTTP API 當(dāng)然也不在話下,所以在一些公司里,前端接管業(yè)務(wù)邏輯,后臺只負(fù)責(zé)各種底層微服務(wù)的架構(gòu)就出現(xiàn)了,這也是目前很多大公司在實(shí)行的架構(gòu):
細(xì)心的你可能已經(jīng)注意到了,在這個(gè)時(shí)候,渲染、HTTP API、后臺業(yè)務(wù)邏輯這些東西,從端的角度看是屬于服務(wù)端的,但是從分工角度看卻屬于前端開發(fā)的范疇,這就是 BFF(Backend for Frontend)的概念,它的優(yōu)勢在于:
把核心業(yè)務(wù)邏輯完全交給前端工程師,讓業(yè)務(wù)邏輯可以更加靈活快速地變更;
讓后端工程師可以更加關(guān)注于海量服務(wù)的穩(wěn)定性和可靠性,提升服務(wù)質(zhì)量。
這就是為什么大公司的很多業(yè)務(wù),都開始越來越多地招 Node.js 全棧工程師的原因。
但是 BFF 并不是銀彈,它還是有一定問題的,比如在國內(nèi)通曉前后端開發(fā)的全棧工程師太少,很難撐起大體量的業(yè)務(wù)開發(fā)。而且國內(nèi)大多數(shù)前端工程師缺少服務(wù)端開發(fā)的經(jīng)驗(yàn),讓他們運(yùn)維上百臺服務(wù)器和一整套海量服務(wù),有點(diǎn)強(qiáng)人所難:
每個(gè)服務(wù)器實(shí)例應(yīng)該有多少核?多少內(nèi)存?
怎么優(yōu)化 Linux 網(wǎng)絡(luò)棧的各種參數(shù)?
服務(wù)器宕機(jī)怎么辦?雙活、多活、同城、異地怎么搞?
而 Serverless 正是幫助前端工程師解決運(yùn)維開發(fā) BFF 的良藥。
Serverless 這個(gè)單詞,直譯成中文的話,應(yīng)該是“無服務(wù)器”。當(dāng)然,這里的“無服務(wù)器”絕對不是說不需要任何服務(wù)器資源了,而是說不需要關(guān)心服務(wù)器的具體運(yùn)維和管理,只需要寫代碼然后發(fā)布即可。
它包含了 FaaS(函數(shù)即服務(wù))和 BaaS(后端即服務(wù))兩大塊功能,把各種基礎(chǔ)設(shè)施進(jìn)行了抽象,即使不熟悉后端的開發(fā)者,也能快速高質(zhì)量地開發(fā)出海量、穩(wěn)定的后端服務(wù),而這對于前端工程師維護(hù) BFF 服務(wù)來說,幾乎是量身定做的。
舉一個(gè)最簡單的例子,你現(xiàn)在需要上線一個(gè)新的 HTTP API(比如 /getUser?id=123
),如果使用 Serverless 服務(wù)的話,有多快呢?
你只需要寫下面這個(gè)云函數(shù):
module.exports = async function(events, context) { const { id } = events.query.id const userInfo = await fetchUserInfo(id) // 調(diào)用后端微服務(wù),拉取用戶信息 return userInfo}復(fù)制代碼
然后發(fā)布這個(gè)云函數(shù)(假設(shè)命名為 getUser
),并且為它設(shè)置一條路由:
cloudbase service:create -f getUser -p /getUser復(fù)制代碼
然后你就可以通過 https://xxx.com/getUser
來拉取數(shù)據(jù)了,同時(shí)還附贈(zèng)海量彈性伸縮、異地多活、日志監(jiān)控等多方位的能力。
如果你覺得這樣很酷炫,那么不如來試一試 云開發(fā) Cloudbase 吧!
云開發(fā)(Cloudbase)是騰訊云 TCB 團(tuán)隊(duì)(Tencent Cloudbase)出品的云端一體化產(chǎn)品方案,為廣大的小程序、Web、移動(dòng)端開發(fā)者提供一站式的 Serverless 服務(wù)。
當(dāng)然這是官方的說法,用人話講就是:
用了云開發(fā),各個(gè)端的開發(fā)者就可以一站式地解決后端服務(wù)問題了!
實(shí)際上早在 2018 年,云開發(fā)就聯(lián)合微信團(tuán)隊(duì)推出了「小程序·云開發(fā)」,如果你對它還不怎么了解,可以看這兩篇文章:
而現(xiàn)在,云開發(fā) For Web 也正式上線了!
云開發(fā)提供了云數(shù)據(jù)庫、云函數(shù)、云存儲、用戶登錄體系等一系列的后端能力,并且提供了各端的 SDK,讓各個(gè)端的開發(fā)者能夠基于這些能力,快速、優(yōu)質(zhì)地開發(fā)出功能豐富的應(yīng)用。
Talk is cheap, Show me the code!
口說無憑,我們還是來直接看代碼吧!
云開發(fā)提供了一個(gè)文檔型的 NoSQL 數(shù)據(jù)庫,與傳統(tǒng)的云上數(shù)據(jù)庫不同的是,云開發(fā)的數(shù)據(jù)庫可以在各種客戶端內(nèi)使用 SDK 直接進(jìn)行讀寫,比如 Web 應(yīng)用、小程序內(nèi)、Flutter 客戶端等等。
下面我們以 Web 應(yīng)用為例,展示云數(shù)據(jù)庫的一系列功能。
CURD是數(shù)據(jù)庫最基礎(chǔ)的功能,云開發(fā) SDK 提供了一套鏈?zhǔn)秸{(diào)用接口,對數(shù)據(jù)庫進(jìn)行讀寫:
// 使用 Web 端 SDKconst 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()復(fù)制代碼
普通的查詢可能無法滿足一些復(fù)雜的需求,比如聯(lián)表、group等等。
云開發(fā)針對這些復(fù)雜的查詢場景,推出了聚合搜索的功能,把一系列操作抽象為一個(gè)管道(pipeline),單次執(zhí)行即可,避免了多次讀的性能問題,我們以 group 操作為例:
const $ = db.command.aggregateconst result = await db .collection('message') .aggregate() .group({ // 以 author 字段作為 key,統(tǒng)計(jì)相同 author 的數(shù)量 _id: '$author' messagesCount: $.sum(1) }) .end() //=> { "_id": "stark", messagesCount: 12 }復(fù)制代碼
更多的聚合搜索功能,可以參考:Aggregate | 云開發(fā) Cloudbase
在訂票、預(yù)約、轉(zhuǎn)賬等等場景下,開發(fā)者可能會(huì)要求數(shù)據(jù)庫能夠保證一連串讀寫的原子性,避免出現(xiàn)競爭條件,這就是數(shù)據(jù)庫事務(wù)的使用場景。
云開發(fā)數(shù)據(jù)庫當(dāng)然也提供了事務(wù)功能:
// 啟動(dòng)事務(wù)const transaction = await db.startTransaction()// 在事務(wù)內(nèi)讀const data = await transaction.collection('messages') .where({ /* <query> */}) .get()// 在事務(wù)內(nèi)寫await transaction.collection('messages') .where({ /* <query> */}) .update({ /* <data> */})// 提交事務(wù)await transaction.commit()復(fù)制代碼
在實(shí)時(shí)聊天室、實(shí)時(shí)數(shù)據(jù)看板等等場景下,我們經(jīng)常會(huì)需要訂閱數(shù)據(jù)庫的更新,從而實(shí)現(xiàn)實(shí)時(shí)數(shù)據(jù)推送。
云開發(fā)的數(shù)據(jù)庫提供的 watch()
方法就是為此設(shè)計(jì)的,每當(dāng)數(shù)據(jù)庫符合條件的文檔發(fā)生變化時(shí),都會(huì)觸發(fā) onChange
回調(diào),示例代碼如下:
await db.collection('messages') .where({/* <query> */}) .watch({ onChange: snapshot => { console.log("收到snapshot!", snapshot) }, onError: error => { console.log("收到error!", error) } })復(fù)制代碼
更多信息可以參考:數(shù)據(jù)庫實(shí)時(shí)推送 | 云開發(fā) Cloudbase
所謂的云函數(shù),便是在云端運(yùn)行的、事件驅(qū)動(dòng)的一段代碼,它可以被 SDK 調(diào)用,也可以直接通過 HTTP 調(diào)用,還可以設(shè)置定時(shí)器讓它定期運(yùn)行:
// sum.jsmodule.exports = async function(events) { return events.a + events.b}復(fù)制代碼
這一小段代碼很簡單,但是隱藏在它之下的卻是一整套龐大的 FaaS(函數(shù)即服務(wù))基礎(chǔ)設(shè)施,提供了諸如彈性伸縮、日志、監(jiān)控告警等多方面的能力。
使用云開發(fā)的客戶端 SDK,可以輕而易舉地在各個(gè)端上調(diào)用云函數(shù),我們以 Web 應(yīng)用為例:
const cloudbase = require("tcb-js-sdk");const app = cloudbase.init({/* 初始化 */});app.callFunction({ // 云函數(shù)名稱 name: "sum", // 傳給云函數(shù)的參數(shù) data: { a: 1, b: 2 } }) .then(res => { console.log(res); // 輸出 "3" }) .catch(error);復(fù)制代碼
你也許會(huì)覺得 SDK 體積龐大,太沉重了,那么你也可以選擇使用 HTTP 來調(diào)用云函數(shù),響應(yīng) HTTP 請求。
// hello.jsmodule.exports = function() { return 'Hello, World!'}復(fù)制代碼
然后我們直接通過命令行發(fā)布這個(gè)函數(shù),并為它創(chuàng)建一條路由:
$ cloudbase functions:deploy hello$ cloudbase service:create -f hello -p /hello復(fù)制代碼
隨后便可以通過 url 直接訪問這個(gè)云函數(shù):
$ curl https://xxx.service.tcloudbase.com/helloHello, World!復(fù)制代碼
具體可以參考:docs.cloudbase.net/service/qui…
在 Cloudbase 的云函數(shù)內(nèi),你可以直接使用 Node.js SDK,無需在初始化的時(shí)候額外注入秘鑰:
const cloudbase = require('@cloudbase/node-sdk')// 無需使用服務(wù)端秘鑰const app = cloudbase.init()const data = await app.database().where().get()復(fù)制代碼
更詳細(xì)的內(nèi)容可以參考:docs.cloudbase.net/api-referen…
我們在開發(fā)應(yīng)用的過程中,經(jīng)常會(huì)遇到圖片、文件上傳的需求,并且可能需要為這些文件設(shè)置 CDN 訪問。傳統(tǒng)的流程是下面這樣的:
前端應(yīng)用調(diào)用上傳接口;
后臺接收文件,把文件放置到文件存儲服務(wù)內(nèi)(例如 騰訊云 COS);
如果需要 CDN 加速文件訪問,需要為 CDN 設(shè)置回源路徑到后端 COS 上。
但如果使用云開發(fā),只需要在客戶端調(diào)用 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", // 需要上傳的文件,F(xiàn)ile 類型 filePath: document.getElementById('file').files[0]})復(fù)制代碼
uploadFile
會(huì)返回一個(gè) fileID
,是云開發(fā)內(nèi)文件的唯一標(biāo)識符,我們可以使用 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 是一個(gè)有如下結(jié)構(gòu)的對象數(shù)組// [{// fileID: 'cloud://a/b/c', // 文件 ID// tempFileURL: 'http://xxx/xxx/xxx', // 臨時(shí)文件網(wǎng)絡(luò)鏈接// maxAge: 120 * 60 * 1000, // 有效期// }]復(fù)制代碼
更詳細(xì)的內(nèi)容,可以參考:docs.cloudbase.net/storage/int…
云開發(fā)除了上述的基礎(chǔ)功能之外,還提供了一系列的擴(kuò)展能力,包括但不僅限于:
更詳細(xì)的內(nèi)容,可以參考:docs.cloudbase.net/extension/i…
上面的能力是不是有些讓你看花眼了,完全不知道要怎么搭配起來使用?
其實(shí)一張圖就可以解決:
圖中的客戶端SDK包括:
服務(wù)端 SDK 包括:
PHP
Golang
光看示例代碼當(dāng)然沒有什么意思,我們接下來就拿云開發(fā)的一些能力,來快速開發(fā)一個(gè)實(shí)時(shí)在線聊天室吧。
項(xiàng)目代碼:github.com/TencentClou…
這是一個(gè)由 create-react-app
快速生成的腳手架項(xiàng)目,所以大部分構(gòu)建和工程化的細(xì)節(jié)這里就略過不談了,我們直接來看代碼實(shí)現(xiàn),大致上實(shí)現(xiàn)了三個(gè)功能,括號中是使用的云開發(fā)能力:
登錄(匿名登錄)
實(shí)時(shí)同步消息(數(shù)據(jù)庫實(shí)時(shí)推送)
發(fā)送消息(數(shù)據(jù)庫寫)
首先是我們的初始化流程,先使用匿名登錄,然后建立實(shí)時(shí)數(shù)據(jù)推送的連接:
async function init() { // 使用匿名登錄 await auth.anonymousAuthProvider().signIn(); // 使用 refreshToken 的前 6 位作為 uid setUid(auth.hasLoginState().credential.refreshToken.slice(0, 6)); // 建立實(shí)時(shí)數(shù)據(jù)推送連接 await db .collection("messages") .where({}) .watch({ onChange(snapshot) { setList(snapshot.docs); setLoading(false); }, onError(err) { console.log(err); }, });}復(fù)制代碼
建立實(shí)時(shí)連接之后,集合中的任何變化,都會(huì)觸發(fā) onChange()
回調(diào),然后我們使用 setList()
來更新界面上的消息數(shù)據(jù)。
當(dāng)然只讀消息是不夠的,我們還需要發(fā)送消息,具體實(shí)現(xiàn)非常簡單,可以看 sendMessage()
方法,直接使用 add()
方法向數(shù)據(jù)庫寫入數(shù)據(jù)就可以了:
// 發(fā)送消息async function sendMessage() { const message = { timestamp: new Date().getTime(), text, uid, }; await db.collection("test").add(message); // 清空輸入欄 setText("");}復(fù)制代碼
當(dāng)然以上只是局部的代碼片段,整體代碼可以參考:
開發(fā)完畢之后,我們便可以使用 云開發(fā)靜態(tài)網(wǎng)站 來托管我們的這個(gè)聊天室 Web 應(yīng)用。
首先我們構(gòu)建我們的應(yīng)用:
$ npm run build復(fù)制代碼
構(gòu)建產(chǎn)物會(huì)生成到 build
目錄下。
然后我們發(fā)布到靜態(tài)托管即可(托管前,請先開通靜態(tài)網(wǎng)站):
$ cloudbase hosting:deploy ./build -e your-env-id復(fù)制代碼
發(fā)布完成后,你便可以通過 https://xxxx.tcb.qcloud.la/xxxx
的來訪問你的應(yīng)用了。進(jìn)一步,你還可以為它綁定自定義域名。
PS:實(shí)際上,云開發(fā)的主頁和官方文檔,就是這樣托管的(畢竟做云服務(wù)的,最重要的就是 Eating your own dog food 嘛)。
當(dāng)然,除了這個(gè)實(shí)戰(zhàn) Demo 以外,還可以看看一些真正業(yè)務(wù)的實(shí)踐:
給 Web 開發(fā)者帶來效率和質(zhì)量上的提升,幫助他們開發(fā)更多更優(yōu)質(zhì)的應(yīng)用,免去運(yùn)維、后臺開發(fā)的煩惱,是云開發(fā)不變的愿景。雖然我們目前已經(jīng)有了很多強(qiáng)大又方便使用的能力,但這遠(yuǎn)不是我們的終點(diǎn)(實(shí)際上我們才剛剛起步),在未來我們將會(huì)持續(xù)完善下面的能力,進(jìn)一步完善云開發(fā)的體系:
更多的支持平臺
更完整的用戶登錄鑒權(quán)體系
對 Vue、React 等主流框架的深度集成
對 Vue、React SSR 的一站式支持
更好的開發(fā)者工具、CLI、腳手架
更加強(qiáng)大的日志、監(jiān)控告警能力
PS:如果你覺得這篇文章對你有幫助,不妨花幾分鐘時(shí)間,來試一試快速開始吧。
云開發(fā)(CloudBase)是一款云端一體化的產(chǎn)品方案 ,采用 serverless 架構(gòu),免環(huán)境搭建等運(yùn)維事務(wù) ,支持一云多端,助力快速構(gòu)建小程序、Web應(yīng)用、移動(dòng)應(yīng)用。
技術(shù)文檔:www.cloudbase.net/