BFF架构

打印 上一主题 下一主题

主题 1584|帖子 1584|积分 4752

BFF架构

BFF架构演进


单体服务



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

微服务



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

    • 域名开销增长

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

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

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





BFF



  • BFF是Backend for Frontend的缩写,指的是专门为前端应用计划的后端服务
  • 主要用来为各个端提供代理数据聚合、裁剪、适配和鉴权服务,方便各个端接入后端服务
  • BFF可以把前端和微服务进行解耦,各自可以独立演进

网关



  • API 网关是一种用于在应用步伐和 API 之间提供安全访问的中间层
  • API 网关还可以用于监控 API 调用,路由哀求,以及在哀求和响应之间添加附加功能(比方身份验证,缓存,数据转换,压缩、流量控制、限流熔断、防爬虫等)
  • 网关和BFF可能合二为一

集群化



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

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

  • 这些问题可以通过采取服务器集群的方式来解决

创建用户微服务

微服务



  • 微服务是一种架构模式,它将单个应用步伐分别为小的服务,每个服务都独立运行并且可以使用不同的语言开辟。这种架构模式使得应用步伐变得更容易开辟、维护和扩展
  • 微服务架构通常会有许多不同的服务,这些服务可能位于不同的机器上,因此需要使用某种通讯协议来进行通讯
  • 由于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
  1. +dataDir=./data
复制代码
启动服务



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

  1. npm install mysql2 sofa-rpc-node --save
复制代码
user\package.json

user\package.json
  1. {
  2.     "name": "user",
  3.     "version": "1.0.0",
  4.     "description": "",
  5.     "main": "index.js",
  6. + "scripts": {
  7. +     "dev": "nodemon index.js"
  8. + },
  9.     "keywords": [],
  10.     "author": "",
  11.     "license": "MIT",
  12.     "dependencies": {
  13.         "sofa-rpc-node": "^2.8.0"
  14.     }
  15. }
复制代码
user\index.js

user\index.js
  1. // 引入rpc服务和注册中心
  2. const { server: { RpcServer }, registry: { ZookeeperRegistry } } = require('sofa-rpc-node');
  3. const mysql = require('mysql2/promise');
  4. let connection;
  5. // 引入 console 模块
  6. const logger = console;
  7. // 创建 Zookeeper 注册中心实例,传入地址为 '127.0.0.1:2181'
  8. const registry = new ZookeeperRegistry({
  9.     logger,
  10.     address: '127.0.0.1:2181',
  11.     connectTimeout: 1000 * 60 * 60 * 24,
  12. });
  13. // 创建 RPC 服务器实例,传入注册中心和端口号
  14. const server = new RpcServer({
  15.     logger,
  16.     registry,
  17.     port: 10000
  18. });
  19. // 添加服务接口,实现 getUserInfo 方法
  20. server.addService({
  21.     interfaceName: 'com.hs.user'
  22. }, {
  23.     async getUserInfo(userId) {
  24.         const [rows] = await connection.execute(`SELECT id,username,avatar,password,phone FROM user WHERE id=${userId} limit 1`);
  25.         return rows[0];
  26.     }
  27. });
  28. // 启动 RPC 服务器,并发布服务
  29. (async function () {
  30.     connection = await mysql.createConnection({
  31.         host: 'localhost',
  32.         user: 'root',
  33.         password: 'root',
  34.         database: 'bff'
  35.     });
  36.     await server.start();
  37.     await server.publish();
  38.      console.log(`用户微服务发布成功`);
  39. })();
复制代码
client.js

user\client.js
  1. const { client: { RpcClient }, registry: { ZookeeperRegistry } } = require('sofa-rpc-node');
  2. // 设置日志记录器
  3. const logger = console;
  4. // 创建 Zookeeper 注册中心
  5. const registry = new ZookeeperRegistry({
  6.     logger,
  7.     address: '127.0.0.1:2181',
  8. });
  9. (async function () {
  10.     // 创建 RPC 客户端
  11.     const client = new RpcClient({ logger, registry });
  12.     // 创建 RPC 服务消费者
  13.     const userConsumer = client.createConsumer({
  14.         // 指定服务接口名称
  15.         interfaceName: 'com.hs.user'
  16.     });
  17.     // 等待服务就绪
  18.     await userConsumer.ready();
  19.     // 调用服务方法
  20.     const result = await userConsumer.invoke('getUserInfo', [1], { responseTimeout: 3000 });
  21.     // 输出结果
  22.     console.log(result);
  23.     process.exit(0);
  24. })()
复制代码
创建文章微服务

安装

  1. npm install mysql2 sofa-rpc-node --save
复制代码
article\package.json

article\package.json
  1. {
  2.     "name": "user",
  3.     "version": "1.0.0",
  4.     "description": "",
  5.     "main": "index.js",
  6.     "scripts": {
  7. +    "dev": "nodemon index.js"
  8.     },
  9.     "keywords": [],
  10.     "author": "",
  11.     "license": "MIT",
  12.     "dependencies": {
  13.         "mysql2": "^2.3.3",
  14.         "sofa-rpc-node": "^2.8.0"
  15.     }
  16. }
复制代码
article\index.js

article\index.js
  1. const { server: { RpcServer }, registry: { ZookeeperRegistry } } = require('sofa-rpc-node');
  2. const mysql = require('mysql2/promise');
  3. let connection;
  4. // 引入 console 模块
  5. const logger = console;
  6. // 创建 Zookeeper 注册中心实例,传入地址为 '127.0.0.1:2181'
  7. const registry = new ZookeeperRegistry({
  8.     logger,
  9.     address: '127.0.0.1:2181',
  10.     connectTimeout: 1000 * 60 * 60 * 24,
  11. });
  12. // 创建 RPC 服务器实例,传入注册中心和端口号
  13. const server = new RpcServer({
  14.     logger,
  15.     registry,
  16.     port: 20000
  17. });
  18. // 添加服务接口,实现 getPostCount 方法
  19. server.addService({
  20.     interfaceName: 'com.hs.post'
  21. }, {
  22.     async getPostCount(userId) {
  23.         const [rows] = await connection.execute(`SELECT count(*) as postCount FROM post WHERE user_id=${userId} limit 1`);
  24.         return rows[0].postCount;
  25.     }
  26. });
  27. // 启动 RPC 服务器,并发布服务
  28. (async function () {
  29.     connection = await mysql.createConnection({
  30.         host: 'localhost',
  31.         user: 'root',
  32.         password: 'root',
  33.         database: 'bff'
  34.     });
  35.     await server.start();
  36.     await server.publish();
  37.     console.log(`文章微服务发布成功`);
  38. })();
复制代码
client.js

article\client.js
  1. const { client: { RpcClient }, registry: { ZookeeperRegistry } } = require('sofa-rpc-node');
  2. // 设置日志记录器
  3. const logger = console;
  4. // 创建 Zookeeper 注册中心
  5. const registry = new ZookeeperRegistry({
  6.     logger,
  7.     address: '127.0.0.1:2181',
  8. });
  9. (async function () {
  10.     // 创建 RPC 客户端
  11.     const client = new RpcClient({ logger, registry });
  12.     // 创建 RPC 服务消费者
  13.     const consumer = client.createConsumer({
  14.         // 指定服务接口名称
  15.         interfaceName: 'com.hs.post'
  16.     });
  17.     // 等待服务就绪
  18.     await consumer.ready();
  19.     // 调用服务方法
  20.     const result = await consumer.invoke('getPostCount', [1], { responseTimeout: 3000 });
  21.     // 输出结果
  22.     console.log(result);
  23.     process.exit(0);
  24. })()
复制代码
创建BFF

安装

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

bff\package.json
  1. {
  2.     "name": "bff",
  3.     "version": "1.0.0",
  4.     "description": "",
  5.     "main": "index.js",
  6.     "scripts": {
  7. +    "dev": "nodemon index.js",
  8. +    "start": "pm2 start index.js --name bff"
  9.     },
  10.     "keywords": [],
  11.     "author": "",
  12.     "license": "ISC",
  13.     "dependencies": {
  14.         "koa": "^2.14.1",
  15.         "koa-logger": "^3.2.1",
  16.         "koa-router": "^12.0.0",
  17.         "sofa-rpc-node": "^2.8.0"
  18.     }
  19. }
复制代码
bff\index.js

bff\index.js
  1. const Koa = require('koa');
  2. const router = require('koa-router')();
  3. const logger = require('koa-logger');
  4. const rpcMiddleware = require('./middleware/rpc');
  5. const app = new Koa();
  6. app.use(logger());
  7. app.use(rpcMiddleware({
  8.     //配置 rpc 中间件的参数,表示要调用的 rpc 接口名称
  9.     interfaceNames: [
  10.         'com.hs.user',
  11.         'com.hs.post'
  12.     ]
  13. }));
  14. router.get('/', async ctx => {
  15.     const userId = ctx.query.userId;
  16.     const { rpcConsumers: { user, post } } = ctx;
  17.     const [userInfo, postCount] = await Promise.all([
  18.         user.invoke('getUserInfo', [userId]),
  19.         post.invoke('getPostCount', [userId])
  20.     ]);
  21.     ctx.body = { userInfo, postCount }
  22. });
  23. app.use(router.routes()).use(router.allowedMethods());
  24. app.listen(3000, () => {
  25.     console.log('bff server is running at 3000');
  26. });
复制代码
rpc.js

bff\middleware\rpc.js
  1. const { client: { RpcClient }, registry: { ZookeeperRegistry } } = require('sofa-rpc-node');
  2. const rpcMiddleware = (options = {}) => {
  3.     return async function (ctx, next) {
  4.         const logger = options.logger || console;
  5.         //创建 ZookeeperRegistry 类的实例,用于管理服务发现和注册
  6.         const registry = new ZookeeperRegistry({
  7.             logger,
  8.             address: options.address || '127.0.0.1:2181',
  9.         });
  10.         //创建 RpcClient 类的实例,用于发送 rpc 请求
  11.         const client = new RpcClient({ logger, registry });
  12.         const interfaceNames = options.interfaceNames || [];
  13.         const rpcConsumers = {};
  14.         for (let i = 0; i < interfaceNames.length; i++) {
  15.             const interfaceName = interfaceNames[i];
  16.             //使用 RpcClient 的 createConsumer 方法创建 rpc 消费者
  17.             const consumer = client.createConsumer({
  18.                 interfaceName,
  19.             });
  20.             //等待 rpc 消费者准备完毕
  21.             await consumer.ready();
  22.             rpcConsumers[interfaceName.split('.').pop()] = consumer;
  23.         }
  24.         ctx.rpcConsumers = rpcConsumers;
  25.         await next();
  26.     }
  27. };
  28. module.exports = rpcMiddleware;
复制代码
bff\index.js

数据处理
  1. const Koa = require('koa');
  2. const router = require('koa-router')();
  3. const logger = require('koa-logger');
  4. const rpcMiddleware = require('./middleware/rpc');
  5. const app = new Koa();
  6. app.use(logger());
  7. app.use(rpcMiddleware({
  8.     interfaceNames: [
  9.         'com.hs.user',
  10.         'com.hs.post'
  11.     ]
  12. }));
  13. router.get('/', async ctx => {
  14.     const userId = ctx.query.userId;
  15.     const { rpcConsumers: { user, post } } = ctx;
  16.     const [userInfo, postCount] = await Promise.all([
  17.         user.invoke('getUserInfo', [userId]),
  18.         post.invoke('getPostCount', [userId])
  19.     ]);
  20. + // 裁剪数据
  21. + delete userInfo.password;
  22. + // 数据脱敏
  23. + userInfo.phone = userInfo.phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
  24. + // 数据适配
  25. + userInfo.avatar = "http://www.hspeixun.cn/"+userInfo.avatar,
  26.     ctx.body = { userInfo, postCount }
  27. });
  28. app.use(router.routes()).use(router.allowedMethods());
  29. app.listen(3000, () => {
  30.     console.log('bff server is running at 3000');
  31. });
复制代码
缓存



  • 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
  1. const Koa = require('koa');
  2. const router = require('koa-router')();
  3. const logger = require('koa-logger');
  4. const rpcMiddleware = require('./middleware/rpc');
  5. +const cacheMiddleware = require('./middleware/cache');
  6. const app = new Koa();
  7. app.use(logger());
  8. app.use(rpcMiddleware({
  9.     interfaceNames: [
  10.         'com.hs.user',
  11.         'com.hs.post'
  12.     ]
  13. }));
  14. +app.use(cacheMiddleware({}));
  15. router.get('/profile', async ctx => {
  16.     const userId = ctx.query.userId;
  17.     const { rpcConsumers: { user, post } } = ctx;
  18. + const cacheKey = `${ctx.method}#${ctx.path}#${userId}`;
  19. + let cacheData = await ctx.cache.get(cacheKey);
  20. + if (cacheData) {
  21. +     ctx.body = cacheData;
  22. +     return;
  23. + }
  24.     const [userInfo, postCount] = await Promise.all([
  25.         user.invoke('getUserInfo', [userId]),
  26.         post.invoke('getPostCount', [userId])
  27.     ]);
  28.   // 裁剪数据
  29.   delete userInfo.password;
  30.   // 数据脱敏
  31.   userInfo.phone = userInfo.phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
  32.   // 数据适配
  33.   userInfo.avatar = "http://www.hspeixun.cn/" + userInfo.avatar;
  34. + cacheData = { userInfo, postCount };
  35. + await ctx.cache.set(cacheKey, cacheData);// keys *
  36. + ctx.body = cacheData
  37. });
  38. app.use(router.routes()).use(router.allowedMethods());
  39. app.listen(3000, () => {
  40.     console.log('bff server is running at 3000');
  41. });
复制代码
cache.js

bff\middleware\cache.js
  1. const LRUCache = require('lru-cache');
  2. const Redis = require('ioredis');
  3. class CacheStore {
  4.     constructor() {
  5.         this.stores = [];
  6.     }
  7.     add(store) {
  8.         this.stores.push(store);
  9.         return this;
  10.     }
  11.     async get(key) {
  12.         for (const store of this.stores) {
  13.             const value = await store.get(key);
  14.             if (value !== undefined) {
  15.                 return value;
  16.             }
  17.         }
  18.     }
  19.     async set(key, value) {
  20.         for (const store of this.stores) {
  21.             await store.set(key, value);
  22.         }
  23.     }
  24. }
  25. class MemoryStore {
  26.     constructor() {
  27.         this.cache = new LRUCache({
  28.             max: 100,
  29.             ttl: 1000 * 60 * 60 * 24
  30.         });
  31.     }
  32.     async get(key) {
  33.         return this.cache.get(key);
  34.     }
  35.     async set(key, value) {
  36.         this.cache.set(key, value);
  37.     }
  38. }
  39. class RedisStore {
  40.     constructor(options) {
  41.         this.client = new Redis(options);
  42.     }
  43.     async get(key) {
  44.         let value = await this.client.get(key);
  45.         return value ? JSON.parse(value) : undefined;
  46.     }
  47.     async set(key, value) {
  48.         await this.client.set(key, JSON.stringify(value));
  49.     }
  50. }
  51. const cacheMiddleware = (options = {}) => {
  52.     return async function (ctx, next) {
  53.         const cacheStore = new CacheStore();
  54.         cacheStore.add(new MemoryStore());
  55.         const redisStore = new RedisStore(options);
  56.         cacheStore.add(redisStore);
  57.         ctx.cache = cacheStore;
  58.         await next();
  59.     };
  60. };
  61. 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
  1. const Koa = require('koa');
  2. const router = require('koa-router')();
  3. const logger = require('koa-logger');
  4. const rpcMiddleware = require('./middleware/rpc');
  5. const cacheMiddleware = require('./middleware/cache');
  6. +const mqMiddleware = require('./middleware/mq');
  7. const app = new Koa();
  8. app.use(logger());
  9. app.use(rpcMiddleware({
  10.     interfaceNames: [
  11.         'com.hs.user',
  12.         'com.hs.post'
  13.     ]
  14. }));
  15. app.use(cacheMiddleware({}));
  16. +app.use(mqMiddleware({ url: 'amqp://localhost' }));
  17. router.get('/profile', async ctx => {
  18.     const userId = ctx.query.userId;
  19. +    ctx.channels.logger.sendToQueue('logger', Buffer.from(JSON.stringify({
  20. +        method: ctx.method,
  21. +        path: ctx.path,
  22. +        userId
  23. +    })));
  24.     const { rpcConsumers: { user, post } } = ctx;
  25.     const cacheKey = `${ctx.method}#${ctx.path}#${userId}`;
  26.     let cacheData = await ctx.cache.get(cacheKey);
  27.     if (cacheData) {
  28.         ctx.body = cacheData;
  29.         return;
  30.     }
  31.     const [userInfo, postCount] = await Promise.all([
  32.         user.invoke('getUserInfo', [userId]),
  33.         post.invoke('getPostCount', [userId])
  34.     ]);
  35.       // 裁剪数据
  36.   delete userInfo.password;
  37.   // 数据脱敏
  38.   userInfo.phone = userInfo.phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
  39.   // 数据适配
  40.   userInfo.avatar = "http://www.hspeixun.cn/" + userInfo.avatar,
  41.     cacheData = { userInfo, postCount };
  42.   await ctx.cache.set(cacheKey, cacheData);// keys *
  43.   ctx.body = cacheData
  44. });
  45. app.use(router.routes()).use(router.allowedMethods());
  46. app.listen(3000, () => {
  47.     console.log('bff server is running at 3000');
  48. });
复制代码
mq.js

bff\middleware\mq.js
  1. const amqp = require('amqplib');
  2. const mqMiddleware = (options = {}) => {
  3.     return async (ctx, next) => {
  4.         //使用 amqp.connect 方法连接 RabbitMQ 服务器
  5.         const rabbitMQClient = await amqp.connect(options.url || 'amqp://localhost');
  6.         //使用 rabbitMQClient 的 createChannel 方法创建 RabbitMQ 通道
  7.         const logger = await rabbitMQClient.createChannel();
  8.         //使用 logger 的 assertQueue 方法创建名为 "logger" 的队列,如果队列已经存在则不会重复创建
  9.         await logger.assertQueue('logger');
  10.         ctx.channels = {
  11.             logger
  12.         };
  13.         await next();
  14.     };
  15. };
  16. module.exports = mqMiddleware;
复制代码
bff\logger.js

bff\logger.js
  1. const amqplib = require('amqplib');
  2. const fs = require('fs-extra');
  3. const path = require('path');
  4. (async () => {
  5.     const conn = await amqplib.connect('amqp://localhost');
  6.     const loggerChannel = await conn.createChannel();
  7.     await loggerChannel.assertQueue('logger');
  8.     loggerChannel.consume('logger', async (event) => {
  9.         const message = JSON.parse(event.content.toString());
  10.         await fs.appendFile(path.join(__dirname, 'logger.txt'), JSON.stringify(message) + '\n');
  11.     });
  12. })();
复制代码
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 的第三方服务,用于实现应用步伐中的焦点功能,包含常用的数据库、对象存储、消息队列、日志服务等等


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)、查询字段等

  1. const { ApolloServer, gql } = require('apollo-server');
  2. const typeDefs = gql`
  3.   type Query {
  4.     users: [User]
  5.     user(id: ID): User
  6.   }
  7.   type Mutation {
  8.     createUser(username: String, age: Int): User
  9.     updateUser(id: ID, username: String, age: Int): Boolean
  10.     deleteUser(id: ID): Boolean
  11.   }
  12.   type User {
  13.     id: ID
  14.     username: String
  15.     age: Int
  16.   }
  17. `;
  18. let users = [
  19.     { id: "1", username: "zhangsan", age: 25 },
  20.     { id: "2", username: "lisi", age: 30 },
  21. ];
  22. const resolvers = {
  23.     Query: {
  24.         users: (obj, args, context, info) => {
  25.             return users;
  26.         },
  27.         user: (obj, args, context, info) => {
  28.             return users.find(user => user.id === args.id);
  29.         }
  30.     },
  31.     Mutation: {
  32.         createUser: (obj, args, context, info) => {
  33.             const newUser = { id: users.length + 1, username: args.username, age: args.age };
  34.             users.push(newUser);
  35.             return newUser;
  36.         },
  37.         updateUser: (obj, args, context, info) => {
  38.             const updatedUser = { id: args.id, username: args.username, age: args.age };
  39.             users = users.map(user => {
  40.                 if (user.id === args.id) {
  41.                     return updatedUser;
  42.                 }
  43.                 return user;
  44.             });
  45.             return true;
  46.         },
  47.         deleteUser: (obj, args, context, info) => {
  48.             users = users.filter(user => user.id !== args.id);
  49.             return true;
  50.         },
  51.     },
  52. };
  53. const server = new ApolloServer({ typeDefs, resolvers });
  54. server.listen().then(({ url }) => {
  55.     console.log(`Server ready at ${url}`);
  56. });
  57. query {
  58.     users {
  59.         id
  60.         username
  61.         age
  62.     }
  63. }
  64. query {
  65.     user(id: "1") {
  66.         id
  67.         username
  68.         age
  69.     }
  70. }
  71. mutation {
  72.     createUser(username: "wangwu", age: 35) {
  73.         id
  74.         username
  75.         age
  76.     }
  77. }
  78. mutation {
  79.     updateUser(id: "1", username: "zhangsan2", age: 26)
  80. }
  81. mutation {
  82.     deleteUser(id: "1")
  83. }
复制代码
Apollo Server Koa



  • Apollo Server Koa 是一个基于 Koa 的中间件,可以将 GraphQL API 添加到 Koa 应用步伐中。它使用 Apollo Server 来执行 GraphQL 服务器逻辑,并答应使用 Koa 的良好特性(如路由和中间件)来构建应用步伐
[code]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(`

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

渣渣兔

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表