阅读 479

用 Serverless 开发一个 React+Koa+PostgreSQL 全栈项目

关于 Serverless

Serverless 即无服务器运算(英语:Serverless computing),又被称为功能即服务(Function-as-a-Service,缩写为 FaaS),是云计算的一种模型。以平台即服务(PaaS)为基础,无服务器运算提供一个微型的架构,终端客户不需要部署、配置或管理服务器服务,代码运行所需要的服务器服务皆由云端平台来提供。

狭义上的全栈工程,主要分为三个方面:前端、后端、数据库。但是这对于一个完整的项目工程来说,无疑是不够的,完整的项目还包括数据的管理、服务器运维、安全等内容。那么这些对于前端工程师来说,学习成本无疑是非常巨大的。那么在技术体系逐渐丰满的今天,是否有哪种工具可以帮助我们来达到这样的目的呢?

那么就不得不提到 serverless。serverless 可以视为一个云服务平台提供的黑盒,开发者只需要专心于核心业务的研发即可,大大降低项目的成本,我们可以把前端的页面、后端的内容作为一个云函数、数据库全部放到 serverless 应用中,只需要通过编写业务部分的代码,即前端 + 后端 + 数据库部分的内容,其他都由 serverless 来帮助完成。

目前来说,有着比较成熟的 serverless 解决方案有三家:腾讯云、阿里云、亚马逊云。个人在选择下选择腾讯云的 Serverless Framework 框架来进行项目的的搭建。

一、相关内容下载

  • Node 下载,版本需要 > 10,以及 npm
node -v
v12.19.0

$ npm -v
7.0.10
复制代码
  • 全局安装 Serverless
npm install -g serverless

serverless -v // 查看 serverless 版本,确保安装成功
复制代码

安装完成之后,就可以开始部署你的项目啦!

二、项目基本结构介绍

  • 作为一个全栈项目,主要包括前端、后端、数据库三部分的内容,那么项目的基本结构应该包括:

  • 其中 api 文件夹下是云函数的内容,db 文件夹对应的是数据库,需要在其中写 serverless 云数据库的配置,front 文件夹中是前端的代码。

三、serverless 的基本配置

在技术栈的选择上,这里会选择 React 作为前端的开发语言,Koa 作为后端的语言,数据库选择是 PostgreSQL。

  1. 首先是在项目的根目录中添加 serverless.yml 文件
app: fullstack-demo-1  //项目名称
复制代码
  1. 在 api 目录下,创建 serverless.yml 文件
component: koa
name: fullstack-demo-1-api
app: fullstack-demo-1-db

inputs:
  entryFile: sls.js #以您实际入口文件名为准
  src:
    src: ./
    exclude:
      - .env
  region: ap-guangzhou
  functionName: fullstack-demo-1-api # 云函数名称
  runtime: Nodejs10.15
  serviceName: fullstackDemo1Service # api网关服务名称
  apigatewayConf:
    protocols:
      - http
      - https
    environment: release
    enableCORS: true #  允许跨域
复制代码

更多配置可以参考:github.com/serverless-…

同时,在 api 目录下创建项目入口文件 sls.js,并安装相关依赖

const Koa = require('koa')
const app = new Koa()

module.exports = app;
复制代码
  1. 在 db 目录下添加 serverless.yml
component: postgresql #(必填) 引用 component 的名称,当前用到的是 postgresql 组件
name: fullstack-demo-1-db
app: fullstack-demo-1-db # (可选) 该 sql 应用名称

stage: dev # (可选) 用于区分环境信息,默认值是 dev
inputs:
  region: ap-guangzhou # 可选 ap-guangzhou, ap-shanghai, ap-beijing
  zone: ap-guangzhou-2 # 可选 ap-guangzhou-2, ap-shanghai-2, ap-beijing-3
  dBInstanceName: serverlessDB
  vpcConfig:
    vpcId: vpc-xxx
    subnetId: subnet-xxx
  extranetAccess: false
复制代码

注意:配置中的 vpcConfig 中的 vpcId 和 subnetId 需要自己去腾讯云中创建

在腾讯云产品中搜索:私有网络,然后点击新建

复制生成的 vpcID,然后点击进入后,点击下方的子网,拿到 subnetID,并复制到 db 的 serverless.yml 的对应位置

  1. 在 api 目录下新建一个 controller 文件夹,在其中创建一个 user.js 的文件,其中是数据库操作的相关语句
'use strict';

const { Pool } = require('pg');

function ApiError(code, msg) {
  const e = new Error(msg);
  e.code = code;
  return e;
}

let pgPool;

module.exports = {
  async getPool() {
    if (!pgPool) {
      pgPool = new Pool({
        connectionString: // 使用创建成功后返回的 postgresql://xxx 格式的内容,
      });
      // init table
      await pgPool.query(`CREATE TABLE IF NOT EXISTS users (
        ID serial NOT NULL,
        NAME           TEXT         NOT NULL,
        EMAIL          CHAR(50)     NOT NULL,
        SITE          CHAR(50)     NOT NULL
      );`);
      return pgPool;
    } else {
      return pgPool;
    }
  },
  async getUserList() {
    const pool = await this.getPool();
    const client = await pool.connect();
    const { rows } = await client.query({
      text: 'select * from users',
    });
    await client.end();
    return rows;
  },
  async createUser(user) {
    const pool = await this.getPool();
    const { name, email, site } = user;
    const existUser = await this.getUserByName(name);
    if (existUser) {
      throw new ApiError(1000, `Name ${name} exist.`);
    }
    const client = await pool.connect();
    const { rowCount } = await client.query({
      text: 'INSERT INTO users(name, email, site) VALUES($1, $2, $3)',
      values: [name, email, site],
    });
    await client.end();
    return rowCount === 1;
  },
  async getUserByName(name) {
    try {
      const pool = await this.getPool();
      const client = await pool.connect();
      const { rows } = await client.query({
        text: 'SELECT * FROM users WHERE name = $1',
        values: [name],
      });
      await client.end();
      if (rows.length > 0) {
        return rows;
      }
      return false;
    } catch (e) {
      throw new ApiError(1001, e);
    }
  },
  async deleteUserByName(name) {
    const pool = await this.getPool();
    const client = await pool.connect();
    const { rows } = await client.query({
      text: 'DELETE FROM users WHERE name = $1',
      values: [name],
    });
    await client.end();
    return rows;
  },
};


复制代码
  1. 在 front 文件夹下新建 serverless.yml(这里以 create-react-app 创建的 react 应用为例)
component: website
name: fullstack-demo-1-front
app: fullstack-demo-1-front

inputs:
  region: ap-guangzhou
  bucketName: fullstack-demo-1-front
  protocol: https
  src:
    src: ./
    hook: yarn run build
    dist: ./build
    envPath: ./
    index: index.html
    error: index.html
复制代码
  1. 完成这些基本配置后,在根目录下运行 sls deploy,需要扫描控制台出现的二维码进行授权。

四、生成情况

  • 此时会看到在 serverless 控制台界面生成一个 website 模块和一个 koa 模块

  • 此时,可以打开 website 应用,点击生成的访问地址,就可以打开自己的前端页面啦!
  • 同时,打开控制台可以看到,website 应用中所需要加载的 js、css 文件都是配置了 cdn 加速的

五、数据库连接

  1. 在 user.js 文件中,修改 connectionString 字段为 sls deploy部署时,控制台返回的字段。
  2. 此时,在 sls.js 编写相关代码并部署运行后,会发现连接不上数据库的情况。此时有两个解决方法
  • 可以在数据库的详情页面,开启 外网IPv4地址,替换第一步中获取的字段中的 10.0.0.xx:5432 部分内容
  • 打开部署的 koa 应用,点击打开下方的云函数 -> 函数配置 -> 编辑 -> 启用私有网络,并选择配置的 vpc 和 subnet -> 点击保存
  1. 此时就可以在 sls.js 调用 user.js 中提供的相关函数了

六、总结

目前来说,Serverless 还没有足够开始流行开来,目前网上还没有足够多的文档和例子,遇到问题全靠自己测试。但是作为如此一项全能、强大的云服务工具,未来的趋势个人认为,有非常大的可能朝这个方向去发展。进一步的拓展前端开发工程师所设计的领域,让后端能够去开发更深层次的算法层面的内容。相信,配备了 Serverless 后,前端工程师发展成为全栈工程师,不再是那么难的事!

文章分类
前端