基于 Serverless Component 的全棧解決方案

Serverless Fullstack

by yugasun from https://yugasun.com/post/serverless-fullstack-vue-practice.html
本文可全文轉(zhuǎn)載昌屉,但需要保留原作者和出處钙蒙。

什么是 Serverless Component

因為 Serverless Component 是基于無服務(wù)框架 (Serverless Framework)的,所以在閱讀這篇實踐文章之前间驮,建議先大概了解下 serverless 命令的使用躬厌,因為下面的案例會使用到。

Serverless Component 的目標(biāo)是磨平不同云服務(wù)平臺之間差異竞帽,你可以將它看作是可以更輕松地構(gòu)建應(yīng)用程序的依賴模塊扛施。目前 Serverless Component ,已經(jīng)形成一個由社區(qū)貢獻驅(qū)動的生態(tài)系統(tǒng)屹篓,你可以瀏覽和使用社區(qū)的所有組件疙渣,快速開發(fā)一款自己想要的應(yīng)用。

Serverless Component 工作原理

基于 Serverless Component 架構(gòu)堆巧,你可以將任何云服務(wù)打包成一個組件妄荔。這個組件將含有一份 serverless.yml 配置文件,并且通過簡單地進行配置就可以使用谍肤。我們拿 @serverless/tencent-express 來舉??啦租。

如果我們要使用它,只需要新建一個項目 express-demo荒揣,然后修改 serverless.yml 配置如下:

express:
  component: '@serverless/tencent-express'
  inputs:
    region: ap-shanghai

因為 serverless 框架部署到云的鑒權(quán)都是基于 dotenv 注入全局的變量來實現(xiàn)的篷角,所以還得在根目錄下新增 .env 文件,并配置對應(yīng)的鑒權(quán)參數(shù)系任。

之后我們就可以在 app.js 中輕松的編寫基于 express 的接口服務(wù)了:

const express = require('express')
const app = express()
app.get('/', function(req, res) {
  res.send('Hello Express')
})
// 不要忘了導(dǎo)出恳蹲,因為該組件會對它進行包裝,輸出成云函數(shù)
module.exports = app

這背后所有的流程邏輯都是組件內(nèi)部實現(xiàn)的,包括:云函數(shù)的部署,API網(wǎng)關(guān)的生成等。

下面是一張簡單的組件依賴圖:

Component Dependency Structure

通過此圖可以清晰地查看組件帶來的收益,借助社區(qū)現(xiàn)有的 @serverless/tencent-express@serverless/tencent-website 組件唉窃,我們就可以很快構(gòu)建想要的全棧應(yīng)用。

全棧應(yīng)用實戰(zhàn)

接下來將介紹如何借助 Serverless Component 快速開發(fā)全棧Web應(yīng)用软啼。

在開始所有步驟前榛瓮,需執(zhí)行 npm install -g serverless 命令,全局安裝 serverless cli喉悴。

準(zhǔn)備

新建項目目錄 fullstack-application-vue棱貌,在該項目目錄下新增 apidashboard 目錄。然后新增 serverless.yml.env 配置文件箕肃,項目目錄結(jié)構(gòu)如下:

├── README.md       // 項目說明文檔
├── api                   // Restful api 后端服務(wù)
├── dashboard           // 前端頁面
├── .env                    // 騰訊云相關(guān)鑒權(quán)參數(shù):TENCENT_APP_ID婚脱,TENCENT_SECRET_ID,TENCENT_SECRET_KEY
└── serverless.yml  // serverless 文件

后端服務(wù)開發(fā)

進入目錄 api,新增 app.js 文件障贸,編寫 express 服務(wù)代碼错森,這里先新增一個路由 /,并返回當(dāng)前服務(wù)器時間:

const express = require('express');
const cors = require('cors');
const app = express();

app.use(cors());
app.get('/', (req, res) => {
  res.send(JSON.stringfy({ message: `Server time: ${new Date().toString()}` }));
});
module.exports = app;

前端頁面開發(fā)

本案例使用的是 Vue.js + Parcel 的前端模板篮洁,當(dāng)然你可以使用任何前端項目腳手架涩维,比如 Vue.js 官方推薦的 Vue CLI 生成的項目。進入 dashboard 目錄袁波,靜態(tài)資源你可以直接復(fù)制我準(zhǔn)備好的 項目模板瓦阐,編寫入口文件 src/index.js:

// 這里初始是沒有 env.js 模塊的,第一次部署后會自動生成
require('../env');

const Vue = require('vue');

module.exports = new Vue({
  el: '#root',
  data: {
    message: 'Click me!',
    isVisible: true,
  },
  methods: {
    async queryServer() {
      const response = await fetch(window.env.apiUrl);
      const result = await response.json();
      this.message = result.message;
    },
  },
});

配置

前后端代碼都準(zhǔn)備好了篷牌,現(xiàn)在我們還需要簡單配置下 serverless.yml 文件了:

name: fullstack-application-vue

frontend:
  component: '@serverless/tencent-website'
  # inputs 為 @serverless/tencent-website 組件的輸入
  # 具體配置說明參考:https://github.com/serverless-components/tencent-website/blob/master/docs/configure.md
  inputs:
    code:
      src: dist
      root: frontend
      hook: npm run build
    env:
        # 下面的 API服務(wù)部署后睡蟋,獲取對應(yīng)的 api 請求路徑
      apiUrl: ${api.url}

api:
  component: '@serverless/tencent-express'
  # inputs 為 @serverless/tencent-express 組件的輸入
  # 具體配置說明參考:https://github.com/serverless-components/tencent-express/blob/master/docs/configure.md
  inputs:
    code: ./api
    functionName: fullstack-vue-api
    apigatewayConf:
      protocol: https

簡單的介紹下配置:首先,該文件定義了 frontendapi 兩個模塊枷颊,分別通過 component 屬性指定依賴的 Serverless Component 戳杀。對于一個標(biāo)準(zhǔn)的 Serverless Component ,都會接受一個 inputs 屬性參數(shù)夭苗,然后組件會根據(jù) inputs 的配置進行處理和部署豺瘤,具體有關(guān)配置的參數(shù)說明,請參考相關(guān)組件的官方配置說明听诸。

部署

以上所有的步驟都完成后坐求,接下來就是第一次部署了。

為什么不是直接聯(lián)調(diào)開發(fā)呢晌梨?因為后端服務(wù)是云函數(shù)桥嗤,但是到目前為止,所有代碼都是在本地編寫仔蝌,前端頁面接口請求鏈接還不存在泛领。所以需要先將云函數(shù)部署到云端,才能進行前后端調(diào)試敛惊。這個也是本人目前遇到的痛點渊鞋,因為每次修改后端服務(wù)后,都需要重新部署瞧挤,然后進行前端開發(fā)調(diào)試锡宋。如果你有更好的建議,歡迎評論指教~

部署時特恬,只需要運行 serverless 命令就行执俩,當(dāng)然如果你需要查看部署中的 DEBUG 信息,還需要加上 --debug 參數(shù)癌刽,如下:

$ serverless
# or
$ serverless --debug

然后終端會 balabalabala~, 輸出一大堆 DEBUG 信息役首,最后只需要看到綠色的 done 就行了:

Deploy Success Result

這樣一個基于 Serverless Component 的全棧應(yīng)用就開發(fā)好了尝丐。趕緊點擊你部署好的鏈接體驗一下吧~

在線 Demo

數(shù)據(jù)庫連接

既然是全棧,怎么少得了數(shù)據(jù)庫的讀寫呢衡奥?接下來介紹如何添加數(shù)據(jù)庫的讀寫操作爹袁。

準(zhǔn)備

想要操作數(shù)據(jù)庫,必須先擁有一臺數(shù)據(jù)庫實例矮固,騰訊云Mysql云數(shù)據(jù)庫 現(xiàn)在也很便宜呢簸,可以購買一個最基本按量計費 1核1G內(nèi)存 的 1小時收費不到 4 毛錢,是不是非常劃算乏屯。購買好之后初始化配置根时,然后新增一個 serverless 數(shù)據(jù)庫,同時新增一張 users 表:

CREATE TABLE if not exists `test` ( `name` varchar (32) NOT NULL ,`email` varchar (64) NOT NULL ,`site` varchar (128) NOT NULL ) ENGINE = innodb DEFAULT CHARACTER SET = "utf8mb4" COLLATE = "utf8mb4_general_ci"

前端修改

首先修改前端入口文件 frontend/src/index.js 新增相關(guān)函數(shù)操作:

require('../env');

const Vue = require('vue');
const axios = require('axios');
module.exports = new Vue({
  el: '#root',
  data: {
    // ...
    form: {
      name: '',
      email: '',
      site: '',
    },
    userList: [],
  },
  methods: {
    // ...
    // 獲取用戶列表
    async getUsers() {
      const res = await axios.get(window.env.apiUrl + 'users');
      this.userList = res.data && res.data.data || [];
    },
    // 新增一個用戶
    async addUser() {
      const data = this.form;
      const res = await axios.post(window.env.apiUrl + 'users', data);
      console.log(res);
      if (res.data) {
        this.getUsers();
      }
    },
  },
  mounted() {
    // 視圖掛在后辰晕,獲取用戶列表
    this.getUsers();
  }
});

當(dāng)然你還需要修改視圖模板文件 frontend/index.html蛤迎,在頁面模板中新增用戶列表和用戶表單:

<!-- user form -->
<section class="user-form" action="#">
  <div class="form-item">
    <label for="name">
      Name:
    </label>
    <input name="name" v-model="form.name" type="text" /><br />
  </div>
  <div class="form-item">
    <label for="email">
      Email:
    </label>
    <input name="email" v-model="form.email" type="email" /><br />
  </div>
  <div class="form-item">
    <label for="site">
      Site:
    </label>
    <input name="site" v-model="form.site" type="text" /><br />
  </div>
  <button @click="addUser">Submit</button>
</section>

<!-- user list -->
<section class="user-list">
  <ul v-if="userList.length > 0">
    <li v-for="item in userList" :key="item.id">
      <p>
        <b>Name: {{ item.name }}</b>
        <b>Email: {{ item.email }}</b>
        <b>Site: {{ item.site }}</b>
      </p>
    </li>
  </ul>
  <span v-else>No Data</span>
</section>

注意:如果還不熟悉 Vue.js 語法,請移至 官方文檔含友,當(dāng)然如果你想快速上手 Vue.js 開發(fā)替裆,也可以閱讀這份 Vue 從入門到精通 教程。

后端修改

這里使用 .env 來進行數(shù)據(jù)庫連接參數(shù)配置窘问,在 api 目錄下新增 .env 文件辆童,將之前的數(shù)據(jù)庫配置填入文件中,參考 api/.env.example 文件惠赫。然后添加并安裝 dotenv 依賴把鉴,同時添加 mysql2 模塊進行數(shù)據(jù)庫操作,body-parser 模塊進行 POST 請求時的 body 解析儿咱。

之后新增后端api庭砍,進行數(shù)據(jù)庫讀寫,修改后的 api/app.js 代碼如下:

'use strict';
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const mysql = require('mysql2');
const bodyParser = require('body-parser');

// init mysql connection
function initMysqlPool() {
  const { DB_HOST, DB_PORT, DB_DATABASE, DB_USER, DB_PASSWORD } = process.env;

  const promisePool = mysql
    .createPool({
      host: DB_HOST,
      user: DB_USER,
      port: DB_PORT,
      password: DB_PASSWORD,
      database: DB_DATABASE,
      connectionLimit: 1,
    })
    .promise();

  return promisePool;
}

const app = express();
app.use(bodyParser.json());
app.use(cors());

if (!app.promisePool) {
  app.promisePool = initMysqlPool();
}

app.get('/', (req, res) => {
  res.send(JSON.stringify({ message: `Server time: ${new Date().toString()}` }));
});

// get user list
app.get('/users', async (req, res) => {
  const [data] = await app.promisePool.query('select * from users');
  res.send(
    JSON.stringify({
      data: data,
    }),
  );
});

// add new user
app.post('/users', async (req, res) => {
  let result = '';
  try {
    const { name, email, site } = req.body;
    const [res] = await app.promisePool.query('INSERT into users SET ?', {
      name: name,
      email: email,
      site: site,
    });
    result = {
      data: res && res.insertId,
      message: 'Insert Success',
    };
  } catch (e) {
    result = {
      data: e,
      message: 'Insert Fail',
    };
  }

  res.send(JSON.stringify(result));
});

module.exports = app;

配置修改

這里數(shù)據(jù)庫訪問需要通過騰訊云私有網(wǎng)絡(luò)混埠,所以還需要為云函數(shù)配置私有網(wǎng)絡(luò)(VPC)怠缸,同時還需要配置能夠操作數(shù)據(jù)庫的角色(關(guān)于角色配置,可以直接到 角色管理頁面)钳宪,這里我新建了一個 QCS_SCFFull 的角色揭北,可以用來訪問數(shù)據(jù)庫。然后修改 serverless.yml 中的配置:

# ...
api:
  component: '@serverless/tencent-express'
  # more configuration for @serverless/tencent-website,
  # refer to: https://github.com/serverless-components/tencent-express/blob/master/docs/configure.md
  inputs:
    code: ./api
    functionName: fullstack-vue-api
    role: QCS_SCFFull # 此角色必須具備訪問數(shù)據(jù)庫權(quán)限
    functionConf:
      # 這個是用來訪問新創(chuàng)建數(shù)據(jù)庫的私有網(wǎng)絡(luò)吏颖,可以在你的數(shù)據(jù)庫實例管理頁面查看
      vpcConfig:
          vpcId: vpc-6n5x55kb
          subnetId: subnet-4cvr91js
    apigatewayConf:
      protocol: https

最后重新部署一下就行了搔体。

完整的模板倉庫

在線Demo

總結(jié)

當(dāng)然全棧方案,并沒有這么簡單侦高,這里只是簡單介紹嫉柴,如何使用 Serverless Component ,快速實現(xiàn)一個全棧應(yīng)用奉呛。如果要應(yīng)用到實際的業(yè)務(wù)場景计螺,我們還需考慮更多的問題。而且目前社區(qū)組件還不夠完善瞧壮,很多功能還需要我們自己去探索發(fā)現(xiàn)登馒。也希望更多牛人加入到 Serverless Component 社區(qū),貢獻更多的優(yōu)秀組件咆槽。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末陈轿,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子秦忿,更是在濱河造成了極大的恐慌麦射,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件灯谣,死亡現(xiàn)場離奇詭異潜秋,居然都是意外死亡,警方通過查閱死者的電腦和手機胎许,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門峻呛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人辜窑,你說我怎么就攤上這事钩述。” “怎么了穆碎?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵牙勘,是天一觀的道長。 經(jīng)常有香客問我所禀,道長谜悟,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任北秽,我火速辦了婚禮葡幸,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘贺氓。我一直安慰自己蔚叨,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布辙培。 她就那樣靜靜地躺著蔑水,像睡著了一般。 火紅的嫁衣襯著肌膚如雪扬蕊。 梳的紋絲不亂的頭發(fā)上搀别,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天,我揣著相機與錄音尾抑,去河邊找鬼歇父。 笑死蒂培,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的榜苫。 我是一名探鬼主播护戳,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼垂睬!你這毒婦竟也來了媳荒?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤驹饺,失蹤者是張志新(化名)和其女友劉穎钳枕,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體赏壹,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡鱼炒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了卡儒。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片田柔。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖骨望,靈堂內(nèi)的尸體忽然破棺而出硬爆,到底是詐尸還是另有隱情,我是刑警寧澤擎鸠,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布缀磕,位于F島的核電站,受9級特大地震影響劣光,放射性物質(zhì)發(fā)生泄漏袜蚕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一绢涡、第九天 我趴在偏房一處隱蔽的房頂上張望牲剃。 院中可真熱鬧,春花似錦雄可、人聲如沸凿傅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽聪舒。三九已至,卻和暖如春虐急,著一層夾襖步出監(jiān)牢的瞬間箱残,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工止吁, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留被辑,地道東北人燎悍。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像敷待,于是被迫代替她去往敵國和親间涵。 傳聞我的和親對象是個殘疾皇子仁热,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,713評論 2 354