渣渣兔 发表于 2024-11-26 00:39:35

BFF架构

BFF架构

BFF架构演进

https://img-blog.csdnimg.cn/img_convert/28efe7b338334550b4d399ab30a65619.png
单体服务



[*]单体服务是指一个独立的应用步伐,包含了全部的功能和业务逻辑。这种架构方式在小型应用步伐中很常见
[*]随着应用步伐的功能越来越多,代码库也会越来越大,维护起来也会变得更加困难。此外,单体服务的团体复杂度也会增长,这可能导致软件开辟周期变长,质量降落,并且系统的扩展性也会受到限制
https://img-blog.csdnimg.cn/img_convert/43096ee228fa8c6a076841876f47bc71.jpeg
微服务



[*]为了应对这些问题,许多公司开始使用微服务架构。微服务是指将一个大型应用步伐拆分成若干个小型服务,每个服务负责执行特定的任务。这种架构方式可以帮助公司更快地开辟和部署新功能,并提高系统的可扩展性和可维护性
[*]这种方式会有以下问题

[*]域名开销增长

[*]内部服务器暴露在公网,有安全隐患
[*]各个端有大量的个性化需求

[*]数据聚合 某些功能可能需要调用多个微服务进行组合

[*]数据裁剪 后端服务返回的数据可能需要过滤掉一些敏感数据
[*]数据适配 后端返回的数据可能需要针对不同端进行数据结构的适配,后端返回XML,但前端需要JSON
[*]数据鉴权 不同的客户端有不同的权限要求




https://img-blog.csdnimg.cn/img_convert/6bf93de251bb91af0cbd2bf8018e7b7a.jpeg
BFF



[*]BFF是Backend for Frontend的缩写,指的是专门为前端应用计划的后端服务
[*]主要用来为各个端提供代理数据聚合、裁剪、适配和鉴权服务,方便各个端接入后端服务
[*]BFF可以把前端和微服务进行解耦,各自可以独立演进
https://img-blog.csdnimg.cn/img_convert/1e5e9222d237bfdd5196bced6389b1a8.jpeg
网关



[*]API 网关是一种用于在应用步伐和 API 之间提供安全访问的中间层
[*]API 网关还可以用于监控 API 调用,路由哀求,以及在哀求和响应之间添加附加功能(比方身份验证,缓存,数据转换,压缩、流量控制、限流熔断、防爬虫等)
[*]网关和BFF可能合二为一
https://img-blog.csdnimg.cn/img_convert/7dee465d4167947b113440c3ef43b6b2.jpeg
集群化



[*]单点服务器可能会存在以下几个问题:

[*]单点故障:单点服务器只有一台,如果这台服务器出现故障,整个系统都会克制工作,这会导致服务中断
[*]计算能力有限:单点服务器的计算能力是有限的,无法应对大规模的计算需求
[*]可扩展性差:单点服务器的扩展能力有限,如果想要提拔计算能力,就必须改造或者更换现有的服务器

[*]这些问题可以通过采取服务器集群的方式来解决
https://img-blog.csdnimg.cn/img_convert/1f09e3129533dfe32699224cf879bd61.jpeg
创建用户微服务

微服务



[*]微服务是一种架构模式,它将单个应用步伐分别为小的服务,每个服务都独立运行并且可以使用不同的语言开辟。这种架构模式使得应用步伐变得更容易开辟、维护和扩展
[*]微服务架构通常会有许多不同的服务,这些服务可能位于不同的机器上,因此需要使用某种通讯协议来进行通讯
[*]由于RPC协议比HTTP协议具有更低的耽误和更高的性能,所以用的更多
RPC



[*]RPC(Remote Procedure Call) 是远程过程调用的缩写,是一种通讯协议,答应步伐在不同的计算机上相互调用远程过程,就像调用本地过程一样
sofa-rpc-node



[*]sofa-rpc-node 是基于 Node.js 的一个 RPC 框架,支持多种协议
Protocol Buffers



[*]Protocol Buffers(简称 protobuf)是 Google 开辟的一种数据序列化格式,可以将结构化数据序列化成二进制格式,并可以或许跨语言使用
Zookeeper

简介



[*]ZooKeeper 是一个分布式协调服务,提供了一些简朴的分布式服务,如配置维护、名字服务、组服务等。它可以用于管理分布式系统中的数据
[*]Apache Zookeeper 官网
安装启动



[*]
[*]下载 Zookeeper 安装包,可以从Apache Zookeeper 官网下载最新版本的安装包

[*]
[*]解压安装包,将下载的压缩包解压到指定的目次。

[*]
[*]配置环境变量,将 Zookeeper 安装目次添加到环境变量中

[*]
[*]修改配置文件,在安装目次下的 conf 目次中找到 zookeeper.properties 文件,修改相干配置

[*]
[*]启动 Zookeeper,在安装目次下运行命令 bin\zkServer.cmd 即可启动 Zookeeper

zookeeper\conf\zoo.cfg
+dataDir=./data
启动服务



[*]logger 是日志记录器,用于记录服务器运行时的日志信息
[*]registry 是一个注册中央,用于维护服务的注册信息,帮助服务节点和客户端找到对方。
[*]server 表示服务端。服务端是提供服务的节点,它会将本身所提供的服务注册到注册中央,并等待客户端的调用。服务端通常会实现详细的业务逻辑,并使用 RPC 或其他通讯协议与客户端进行通讯
[*]server 的 addService 方法担当两个参数:服务接口和服务实现。服务接口是一个对象,此中包含了服务的名称信息。服务实现是一个对象,此中包含了详细实现服务方法的函数
[*]RPC 服务器的 start 方法,用于启动服务器
[*]RPC 服务器的 publish 方法,用于向注册中央注册服务。这样,客户端就可以通过注册中央获取服务的地址和端口,并直接向服务器发起调用
安装

npm install mysql2 sofa-rpc-node --save

user\package.json

user\package.json
{
    "name": "user",
    "version": "1.0.0",
    "description": "",
    "main": "index.js",
+ "scripts": {
+   "dev": "nodemon index.js"
+ },
    "keywords": [],
    "author": "",
    "license": "MIT",
    "dependencies": {
      "sofa-rpc-node": "^2.8.0"
    }
}
user\index.js

user\index.js
// 引入rpc服务和注册中心
const { server: { RpcServer }, registry: { ZookeeperRegistry } } = require('sofa-rpc-node');
const mysql = require('mysql2/promise');
let connection;
// 引入 console 模块
const logger = console;
// 创建 Zookeeper 注册中心实例,传入地址为 '127.0.0.1:2181'
const registry = new ZookeeperRegistry({
    logger,
    address: '127.0.0.1:2181',
    connectTimeout: 1000 * 60 * 60 * 24,
});
// 创建 RPC 服务器实例,传入注册中心和端口号
const server = new RpcServer({
    logger,
    registry,
    port: 10000
});
// 添加服务接口,实现 getUserInfo 方法
server.addService({
    interfaceName: 'com.hs.user'
}, {
    async getUserInfo(userId) {
      const = await connection.execute(`SELECT id,username,avatar,password,phone FROM user WHERE id=${userId} limit 1`);
      return rows;
    }
});
// 启动 RPC 服务器,并发布服务
(async function () {
    connection = await mysql.createConnection({
      host: 'localhost',
      user: 'root',
      password: 'root',
      database: 'bff'
    });
    await server.start();
    await server.publish();
   console.log(`用户微服务发布成功`);
})();
client.js

user\client.js
const { client: { RpcClient }, registry: { ZookeeperRegistry } } = require('sofa-rpc-node');
// 设置日志记录器
const logger = console;
// 创建 Zookeeper 注册中心
const registry = new ZookeeperRegistry({
    logger,
    address: '127.0.0.1:2181',
});
(async function () {
    // 创建 RPC 客户端
    const client = new RpcClient({ logger, registry });
    // 创建 RPC 服务消费者
    const userConsumer = client.createConsumer({
      // 指定服务接口名称
      interfaceName: 'com.hs.user'
    });
    // 等待服务就绪
    await userConsumer.ready();
    // 调用服务方法
    const result = await userConsumer.invoke('getUserInfo', , { responseTimeout: 3000 });
    // 输出结果
    console.log(result);
    process.exit(0);
})()
创建文章微服务

安装

npm install mysql2 sofa-rpc-node --save

article\package.json

article\package.json
{
    "name": "user",
    "version": "1.0.0",
    "description": "",
    "main": "index.js",
    "scripts": {
+    "dev": "nodemon index.js"
    },
    "keywords": [],
    "author": "",
    "license": "MIT",
    "dependencies": {
      "mysql2": "^2.3.3",
      "sofa-rpc-node": "^2.8.0"
    }
}
article\index.js

article\index.js
const { server: { RpcServer }, registry: { ZookeeperRegistry } } = require('sofa-rpc-node');
const mysql = require('mysql2/promise');
let connection;
// 引入 console 模块
const logger = console;
// 创建 Zookeeper 注册中心实例,传入地址为 '127.0.0.1:2181'
const registry = new ZookeeperRegistry({
    logger,
    address: '127.0.0.1:2181',
    connectTimeout: 1000 * 60 * 60 * 24,
});
// 创建 RPC 服务器实例,传入注册中心和端口号
const server = new RpcServer({
    logger,
    registry,
    port: 20000
});
// 添加服务接口,实现 getPostCount 方法
server.addService({
    interfaceName: 'com.hs.post'
}, {
    async getPostCount(userId) {
      const = await connection.execute(`SELECT count(*) as postCount FROM post WHERE user_id=${userId} limit 1`);
      return rows.postCount;
    }
});
// 启动 RPC 服务器,并发布服务
(async function () {
    connection = await mysql.createConnection({
      host: 'localhost',
      user: 'root',
      password: 'root',
      database: 'bff'
    });
    await server.start();
    await server.publish();
    console.log(`文章微服务发布成功`);
})();
client.js

article\client.js
const { client: { RpcClient }, registry: { ZookeeperRegistry } } = require('sofa-rpc-node');
// 设置日志记录器
const logger = console;
// 创建 Zookeeper 注册中心
const registry = new ZookeeperRegistry({
    logger,
    address: '127.0.0.1:2181',
});
(async function () {
    // 创建 RPC 客户端
    const client = new RpcClient({ logger, registry });
    // 创建 RPC 服务消费者
    const consumer = client.createConsumer({
      // 指定服务接口名称
      interfaceName: 'com.hs.post'
    });
    // 等待服务就绪
    await consumer.ready();
    // 调用服务方法
    const result = await consumer.invoke('getPostCount', , { responseTimeout: 3000 });
    // 输出结果
    console.log(result);
    process.exit(0);
})()
创建BFF

安装

npm install koa koa-router koa-logger sofa-rpc-node lru-cache ioredis amqplib fs-extra --save
访问地址
http://localhost:3000/?userId=1
bff\package.json

bff\package.json
{
    "name": "bff",
    "version": "1.0.0",
    "description": "",
    "main": "index.js",
    "scripts": {
+    "dev": "nodemon index.js",
+    "start": "pm2 start index.js --name bff"
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "dependencies": {
      "koa": "^2.14.1",
      "koa-logger": "^3.2.1",
      "koa-router": "^12.0.0",
      "sofa-rpc-node": "^2.8.0"
    }
}
bff\index.js

bff\index.js
const Koa = require('koa');
const router = require('koa-router')();
const logger = require('koa-logger');
const rpcMiddleware = require('./middleware/rpc');
const app = new Koa();
app.use(logger());
app.use(rpcMiddleware({
    //配置 rpc 中间件的参数,表示要调用的 rpc 接口名称
    interfaceNames: [
      'com.hs.user',
      'com.hs.post'
    ]
}));
router.get('/', async ctx => {
    const userId = ctx.query.userId;
    const { rpcConsumers: { user, post } } = ctx;
    const = await Promise.all([
      user.invoke('getUserInfo', ),
      post.invoke('getPostCount', )
    ]);
    ctx.body = { userInfo, postCount }
});
app.use(router.routes()).use(router.allowedMethods());
app.listen(3000, () => {
    console.log('bff server is running at 3000');
});
rpc.js

bff\middleware\rpc.js
const { client: { RpcClient }, registry: { ZookeeperRegistry } } = require('sofa-rpc-node');
const rpcMiddleware = (options = {}) => {
    return async function (ctx, next) {
      const logger = options.logger || console;
      //创建 ZookeeperRegistry 类的实例,用于管理服务发现和注册
      const registry = new ZookeeperRegistry({
            logger,
            address: options.address || '127.0.0.1:2181',
      });
      //创建 RpcClient 类的实例,用于发送 rpc 请求
      const client = new RpcClient({ logger, registry });
      const interfaceNames = options.interfaceNames || [];
      const rpcConsumers = {};
      for (let i = 0; i < interfaceNames.length; i++) {
            const interfaceName = interfaceNames;
            //使用 RpcClient 的 createConsumer 方法创建 rpc 消费者
            const consumer = client.createConsumer({
                interfaceName,
            });
            //等待 rpc 消费者准备完毕
            await consumer.ready();
            rpcConsumers = consumer;
      }
      ctx.rpcConsumers = rpcConsumers;
      await next();
    }
};
module.exports = rpcMiddleware;
bff\index.js

数据处理
const Koa = require('koa');
const router = require('koa-router')();
const logger = require('koa-logger');
const rpcMiddleware = require('./middleware/rpc');
const app = new Koa();
app.use(logger());
app.use(rpcMiddleware({
    interfaceNames: [
      'com.hs.user',
      'com.hs.post'
    ]
}));
router.get('/', async ctx => {
    const userId = ctx.query.userId;
    const { rpcConsumers: { user, post } } = ctx;
    const = await Promise.all([
      user.invoke('getUserInfo', ),
      post.invoke('getPostCount', )
    ]);
+ // 裁剪数据
+ delete userInfo.password;
+ // 数据脱敏
+ userInfo.phone = userInfo.phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
+ // 数据适配
+ userInfo.avatar = "http://www.hspeixun.cn/"+userInfo.avatar,
    ctx.body = { userInfo, postCount }
});
app.use(router.routes()).use(router.allowedMethods());
app.listen(3000, () => {
    console.log('bff server is running at 3000');
});
缓存



[*]BFF 作为前端应用和后端系统之间的抽象层,负担了大量的哀求转发和数据转换工作。使用多级缓存可以帮助 BFF 淘汰对后端系统的访问,从而提高应用的响应速度
[*]当 BFF 收到一个哀求时,首先会查抄内存缓存中是否存在对应的数据,如果有就直接返回数据。如果内存缓存中没有数据,就会查抄Redis缓存,如果Redis缓存中有数据就返回数据,并将数据写入内存缓存。如果本地缓存中也没有数据,就会向后端系统发起哀求,并将数据写入Redis缓存和内存缓存
多级缓存



[*]多级缓存(multi-level cache)是指系统中使用了多个缓存层来存储数据的技术。这些缓存层的优先级通常是依次递减的,即最快的缓存层位于最顶层,最慢的缓存层位于最底层
LRU



[*]LRU(Least Recently Used)是一种常用的高速缓存镌汰算法,它的原理是将最近使用过的数据或页面保留在缓存中,而最少使用的数据或页面将被镌汰。这样做的目标是为了最大化缓存的掷中率,即使用缓存尽可能多地满足用户的哀求
redis



[*]Redis 是一种开源的内存数据存储系统,可以作为数据库、缓存和消息中间件使用
[*]Redis 运行在内存中,因此它的读写速度非常快
[*]ioredis 是一个基于 Node.js 的 Redis 客户端,提供了对 Redis 命令的高度封装和支持
[*]redis
[*]Redis-x64-5.0.14.1
使用缓存

bff\index.js

bff\index.js
const Koa = require('koa');
const router = require('koa-router')();
const logger = require('koa-logger');
const rpcMiddleware = require('./middleware/rpc');
+const cacheMiddleware = require('./middleware/cache');
const app = new Koa();
app.use(logger());
app.use(rpcMiddleware({
    interfaceNames: [
      'com.hs.user',
      'com.hs.post'
    ]
}));
+app.use(cacheMiddleware({}));
router.get('/profile', async ctx => {
    const userId = ctx.query.userId;
    const { rpcConsumers: { user, post } } = ctx;
+ const cacheKey = `${ctx.method}#${ctx.path}#${userId}`;
+ let cacheData = await ctx.cache.get(cacheKey);
+ if (cacheData) {
+   ctx.body = cacheData;
+   return;
+ }
    const = await Promise.all([
      user.invoke('getUserInfo', ),
      post.invoke('getPostCount', )
    ]);
// 裁剪数据
delete userInfo.password;
// 数据脱敏
userInfo.phone = userInfo.phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
// 数据适配
userInfo.avatar = "http://www.hspeixun.cn/" + userInfo.avatar;
+ cacheData = { userInfo, postCount };
+ await ctx.cache.set(cacheKey, cacheData);// keys *
+ ctx.body = cacheData
});
app.use(router.routes()).use(router.allowedMethods());
app.listen(3000, () => {
    console.log('bff server is running at 3000');
});
cache.js

bff\middleware\cache.js
const LRUCache = require('lru-cache');
const Redis = require('ioredis');
class CacheStore {
    constructor() {
      this.stores = [];
    }
    add(store) {
      this.stores.push(store);
      return this;
    }
    async get(key) {
      for (const store of this.stores) {
            const value = await store.get(key);
            if (value !== undefined) {
                return value;
            }
      }
    }
    async set(key, value) {
      for (const store of this.stores) {
            await store.set(key, value);
      }
    }
}
class MemoryStore {
    constructor() {
      this.cache = new LRUCache({
            max: 100,
            ttl: 1000 * 60 * 60 * 24
      });
    }
    async get(key) {
      return this.cache.get(key);
    }
    async set(key, value) {
      this.cache.set(key, value);
    }
}
class RedisStore {
    constructor(options) {
      this.client = new Redis(options);
    }
    async get(key) {
      let value = await this.client.get(key);
      return value ? JSON.parse(value) : undefined;
    }
    async set(key, value) {
      await this.client.set(key, JSON.stringify(value));
    }
}
const cacheMiddleware = (options = {}) => {
    return async function (ctx, next) {
      const cacheStore = new CacheStore();
      cacheStore.add(new MemoryStore());
      const redisStore = new RedisStore(options);
      cacheStore.add(redisStore);
      ctx.cache = cacheStore;
      await next();
    };
};
module.exports = cacheMiddleware;
消息队列



[*]消息队列(Message Queue)用于在分布式系统中传递数据。它的特点是可以将消息发送者和吸取者解耦,使得消息生产者和消息消费者可以独立的开辟和部署
引入原因



[*]在 BFF 中使用消息队列(message queue)有几个原因:

[*]大并发:消息队列可以帮助应对大并发的哀求,BFF 可以将哀求写入消息队列,然后后端服务可以从消息队列中读取哀求并处理
[*]解耦:消息队列可以帮助解耦 BFF 和后端服务,BFF 不需要关心后端服务的详细实现,只需要将哀求写入消息队列,后端服务负责从消息队列中读取哀求并处理
[*]异步:消息队列可以帮助实现异步调用,BFF 可以将哀求写入消息队列,然后立刻返反响应给前端应用,后端服务在背景处理哀求

[*]流量削峰:消息队列可以帮助流量削峰,BFF 可以将哀求写入消息队列,然后后端服务可以在合适的时间处理哀求,从而缓解瞬时高峰流量带来的压力


RabbitMQ



[*]RabbitMQ是一个消息代理,它可以用来在消息生产者和消息消费者之间传递消息
[*]RabbitMQ的工作流程如下:

[*]消息生产者将消息发送到RabbitMQ服务器
[*]RabbitMQ服务器将消息保存到队列中
[*]消息消费者从队列中读取消息
[*]当消息消费者处理完消息后RabbitMQ服务器将消息删除

[*]安装启动

[*]在RabbitMQ下载官网安装包或镜像安装包
[*]双击安装包,按照提示进行安装,直接就可以启动

[*]安装前还要安装Erlang,Erlang是一个结构化,动态类型编程语言,内建并行计算支持


实现

bff\index.js

bff\index.js
const Koa = require('koa');
const router = require('koa-router')();
const logger = require('koa-logger');
const rpcMiddleware = require('./middleware/rpc');
const cacheMiddleware = require('./middleware/cache');
+const mqMiddleware = require('./middleware/mq');
const app = new Koa();
app.use(logger());
app.use(rpcMiddleware({
    interfaceNames: [
      'com.hs.user',
      'com.hs.post'
    ]
}));
app.use(cacheMiddleware({}));
+app.use(mqMiddleware({ url: 'amqp://localhost' }));
router.get('/profile', async ctx => {
    const userId = ctx.query.userId;
+    ctx.channels.logger.sendToQueue('logger', Buffer.from(JSON.stringify({
+      method: ctx.method,
+      path: ctx.path,
+      userId
+    })));
    const { rpcConsumers: { user, post } } = ctx;
    const cacheKey = `${ctx.method}#${ctx.path}#${userId}`;
    let cacheData = await ctx.cache.get(cacheKey);
    if (cacheData) {
      ctx.body = cacheData;
      return;
    }
    const = await Promise.all([
      user.invoke('getUserInfo', ),
      post.invoke('getPostCount', )
    ]);
      // 裁剪数据
delete userInfo.password;
// 数据脱敏
userInfo.phone = userInfo.phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
// 数据适配
userInfo.avatar = "http://www.hspeixun.cn/" + userInfo.avatar,
    cacheData = { userInfo, postCount };
await ctx.cache.set(cacheKey, cacheData);// keys *
ctx.body = cacheData
});
app.use(router.routes()).use(router.allowedMethods());
app.listen(3000, () => {
    console.log('bff server is running at 3000');
});
mq.js

bff\middleware\mq.js
const amqp = require('amqplib');
const mqMiddleware = (options = {}) => {
    return async (ctx, next) => {
      //使用 amqp.connect 方法连接 RabbitMQ 服务器
      const rabbitMQClient = await amqp.connect(options.url || 'amqp://localhost');
      //使用 rabbitMQClient 的 createChannel 方法创建 RabbitMQ 通道
      const logger = await rabbitMQClient.createChannel();
      //使用 logger 的 assertQueue 方法创建名为 "logger" 的队列,如果队列已经存在则不会重复创建
      await logger.assertQueue('logger');
      ctx.channels = {
            logger
      };
      await next();
    };
};
module.exports = mqMiddleware;
bff\logger.js

bff\logger.js
const amqplib = require('amqplib');
const fs = require('fs-extra');
const path = require('path');
(async () => {
    const conn = await amqplib.connect('amqp://localhost');
    const loggerChannel = await conn.createChannel();
    await loggerChannel.assertQueue('logger');
    loggerChannel.consume('logger', async (event) => {
      const message = JSON.parse(event.content.toString());
      await fs.appendFile(path.join(__dirname, 'logger.txt'), JSON.stringify(message) + '\n');
    });
})();
Serverless

BFF问题



[*]复杂性增长:添加 BFF 层会增长系统的复杂性,由于它需要在后端 API 和前端应用步伐之间处理哀求和响应
[*]性能问题:如果 BFF 层的实现不当,可能会导致性能问题,由于它需要在后端 API 和前端应用步伐之间传输大量数据
[*]安全风险:如果 BFF 层未得到正确保护,可能会导致安全风险,由于它可能会暴露敏感数据
[*]维护本钱:BFF 层需要维护和更新,这会增长维护本钱
[*]测试复杂性:由于 BFF 层需要在后端 API 和前端应用步伐之间进行测试,因此测试可能会变得更加复杂
[*]运维问题 要求有强大的日志、服务器监控、性能监控、负载均衡、备份冗灾、监控报警和弹性伸缩扩容等
Serverless



[*]这些问题可以通过Serverless来解决
[*]Serverless = Faas (Function as a service) + Baas (Backend as a service)
[*]FaaS(Function-as-a-Service)是服务商提供一个平台、提供给用户开辟、运行管理这些函数的功能,而无需搭建和维护基础框架,是一种事件驱动由消息触发的函数服务
[*]BaaS(Backend-as-a-Service)后端即服务,包含了后端服务组件,它是基于 API 的第三方服务,用于实现应用步伐中的焦点功能,包含常用的数据库、对象存储、消息队列、日志服务等等
https://img-blog.csdnimg.cn/img_convert/396c5a80189aab9a89b726e1c7fcd76b.png
https://img-blog.csdnimg.cn/img_convert/6b14d30b982e956dc12d6940b863b20b.png
Serverless的优势



[*]节流本钱:在传统架构中,你需要为应用步伐所使用的服务器付费,纵然它们没有被使用。在 Serverless 架构中,你仅需为现实使用的资源付费,这可以节流大量本钱
[*]更快的开辟周期:Serverless 架构答应开辟人员更快地构建和部署应用步伐,由于它们可以更快地得到所需的资源
[*]更好的可伸缩性:Serverless 架构可以自动扩展来满足增长的流量需求,无需人工干预
[*]更好的可维护性:在 Serverless 架构中,你无需担心底层基础架构的维护,由于这些工作由云服务提供商负责
[*]更高的可用性:由于 Serverless 架构具有自动扩展功能,因此它可以更好地应对突发流量,从而提高应用步伐的可用性
Serverless的缺点



[*]复杂性:Serverless 架构可能会使应用步伐的体系结构变得更加复杂,由于它需要将应用步伐拆分为许多小型函数
[*]性能问题:在某些环境下,Serverless 架构可能会导致性能问题,由于函数执行需要额外的时间来启动和终止
[*]限制:每个函数都有资源限制,因此需要仔细规划应用步伐的体系结构,以免超出这些限制
[*]依靠云服务提供商:使用 Serverless 架构需要依靠云服务提供商,因此如果这些服务出现故障,可能会对应用步伐造成影响
[*]调试困难:由于 Serverless 架构使用许多小型函数,因此调试可能会变得更加困难
GraphQL



[*]GraphQL是一种用于API的查询语言,它答应客户端向服务端哀求特定的数据,而不是服务端将全部可能的数据全部返回。这样,客户端可以更准确地获取所需的数据,并且服务端可以更有效地满足哀求
[*]GraphQL可以让客户端本身定义所需的数据结构,可以机动地获取所需的数据。这对于多端应用来说非常方便,由于每一个客户端可能有不同的数据需求,使用GraphQL可以让每个客户端本身定义所需的数据结构
[*]GraphQL可以让BFF服务层从不同的数据源获取数据,并将它们组合起来返回给客户端。这对于在BFF架构中更好地组织数据是很有帮助的,由于你可以在BFF层中组合来自不同数据源的数据,而不消在客户端中再做一次组合
Apollo Server



[*]Apollo Server是一个用于构建GraphQL API的开源服务器框架。它支持多种编程语言,答应你使用同一种方式来访问不同的后端数据源,并且具有良好的扩展性
[*]Apollo Server是一种实现GraphQL服务端的方法,它提供了一些工具和功能,帮助你更轻松地构建和部署GraphQL API。它还提供了一些额外的功能,如缓存、身份验证和模拟数据,帮助你更快速地开辟和测试你的API
GraphQL schema language



[*]schema
[*]GraphQL schema language是一种用来定义GraphQL API的语言。它可以用来描述API中可用的数据和操作,包括支持的查询、变更、订阅等功能
[*]GraphQL schema由一系列的类型组成,每种类型都有一个名称和一些字段。每个字段都有一个名称和类型,并可能有一些额外的限制,比如是否是必填的或者有默认值
resolvers



[*]resolvers
[*]在GraphQL中,resolvers是负责剖析每个字段的函数。在Apollo Server中,你可以使用resolvers对象来定义这些函数
[*]resolvers对象是一个包含了全部剖析器函数的对象。它的结构与你在schema中定义的类型的结构是一样的
[*]除了定义剖析器函数以外,你还可以在resolvers对象中定义自定义操作,比方查询、变更、订阅等。这些操作的剖析器函数与字段的剖析器函数的定义方式是一样的,只是函数名称不同而已
ApolloServer示例



[*]gql函数是一个template tag,你可以将它放在模板字符串的前面,然后在模板字符串中编写GraphQL schema language的代码,可以定义查询和变更操作
[*]resolvers函数参数

[*]obj:表示当前查询的父对象。比方,如果你在查询"user"类型的对象,那么obj就表示当前查询的"user"对象
[*]args:表示当前查询的参数。比方,如果你在查询带有参数的字段,那么args就表示这些参数
[*]context:表示当前的上下文对象,可以在整个查询中传递给全部的resolver
[*]info:表示当前的查询信息,包括查询字符串、查询操作(query/mutation)、查询字段等

const { ApolloServer, gql } = require('apollo-server');
const typeDefs = gql`
type Query {
    users:
    user(id: ID): User
}
type Mutation {
    createUser(username: String, age: Int): User
    updateUser(id: ID, username: String, age: Int): Boolean
    deleteUser(id: ID): Boolean
}
type User {
    id: ID
    username: String
    age: Int
}
`;
let users = [
    { id: "1", username: "zhangsan", age: 25 },
    { id: "2", username: "lisi", age: 30 },
];
const resolvers = {
    Query: {
      users: (obj, args, context, info) => {
            return users;
      },
      user: (obj, args, context, info) => {
            return users.find(user => user.id === args.id);
      }
    },
    Mutation: {
      createUser: (obj, args, context, info) => {
            const newUser = { id: users.length + 1, username: args.username, age: args.age };
            users.push(newUser);
            return newUser;
      },
      updateUser: (obj, args, context, info) => {
            const updatedUser = { id: args.id, username: args.username, age: args.age };
            users = users.map(user => {
                if (user.id === args.id) {
                  return updatedUser;
                }
                return user;
            });
            return true;
      },
      deleteUser: (obj, args, context, info) => {
            users = users.filter(user => user.id !== args.id);
            return true;
      },
    },
};
const server = new ApolloServer({ typeDefs, resolvers });
server.listen().then(({ url }) => {
    console.log(`Server ready at ${url}`);
});
query {
    users {
      id
      username
      age
    }
}
query {
    user(id: "1") {
      id
      username
      age
    }
}
mutation {
    createUser(username: "wangwu", age: 35) {
      id
      username
      age
    }
}
mutation {
    updateUser(id: "1", username: "zhangsan2", age: 26)
}
mutation {
    deleteUser(id: "1")
}
Apollo Server Koa



[*]Apollo Server Koa 是一个基于 Koa 的中间件,可以将 GraphQL API 添加到 Koa 应用步伐中。它使用 Apollo Server 来执行 GraphQL 服务器逻辑,并答应使用 Koa 的良好特性(如路由和中间件)来构建应用步伐
const Koa = require('koa');
const { ApolloServer } = require('apollo-server-koa');

const typeDefs = `
type Query {
    hello: String
}
`;

const resolvers = {
    Query: {
      hello: () => 'Hello, world!',
    },
};

(async function () {
    const server = new ApolloServer({ typeDefs, resolvers });
    await server.start()
    const app = new Koa();
    server.applyMiddleware({ app });
    app.listen({ port: 4000 }, () =>
      console.log(`
页: [1]
查看完整版本: BFF架构