基于 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。

準備

新建項目目錄 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ù)制我準備好的 項目模板,編寫入口文件 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;
    },
  },
});

配置

前后端代碼都準備好了,現(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)準的 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ù)庫的讀寫操作。

準備

想要操作數(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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容