文章目录
- 1. BFF 层的定位
- 2. 技术选型
- 3. 架构设计
- 3.1 分层设计
- 3.2 示例架构
- 4. 核心功能实现
- 4.1 数据聚合
- 4.2 权限校验
- 4.3 缓存优化
- 5、实战示例
- 1. 场景说明
- 2. ECharts 数据格式要求
- 3. BFF 层实现步骤
- 3.1 接收前端参数
- 3.2 调用后端服务获取数据
- 4. 前端使用
- 总结
在使用 Node.js 构建 BFF(Backend for Frontend)层架构时,核心思想是为前端应用(如 Web、移动端或桌面应用)提供定制化的 API 接口,将后端复杂的服务逻辑抽象化、聚合化,从而优化前端开发体验并提升系统整体性能。
以下是基于 Node.js 构建 BFF 层架构的一些关键点和实践方法:
1. BFF 层的定位
-
职责:
- 为前端提供专用的接口,屏蔽后端服务的复杂性。
- 聚合多个微服务的数据,减少前端的请求次数。
- 处理与前端相关的逻辑(如权限校验、数据格式转换、多端适配等)。
-
位置:
- 位于前端与后端服务(如微服务、数据库等)之间,作为中间层。
2. 技术选型
-
Node.js 框架:
- Express.js:轻量级、灵活,适合快速开发。
- Koa.js:基于 async/await 的现代框架,代码更简洁。
- NestJS:基于 TypeScript 的企业级框架,适合大型项目。
-
其他工具:
- GraphQL:如果需要更灵活的数据查询,可以用 Apollo Server 或 GraphQL.js。
- 微服务通信:使用
axios
或node-fetch
调用后端服务。 - 缓存:使用 Redis 或内存缓存(如
node-cache
)优化性能。 - 监控与日志:集成
winston
或pino
等日志库,以及Prometheus
等监控工具。
3. 架构设计
3.1 分层设计
- 路由层:处理 HTTP 请求,定义 API 接口。
- 服务层:封装业务逻辑,调用后端服务或数据库。
- 数据聚合层:从多个数据源获取数据并整合成前端需要的格式。
- 适配器层:处理与前端相关的逻辑(如格式转换、权限校验等)。
3.2 示例架构
plaintextFrontend (Web/Mobile)↓BFF Layer (Node.js)├── Router (定义 API 接口)├── Service (业务逻辑)├── Aggregator (数据聚合)├── Adapter (前端适配)└── External Services (调用后端微服务/数据库)
4. 核心功能实现
4.1 数据聚合
-
假设有一个电商应用,前端需要展示商品详情,包括商品信息、库存、用户评价等。
-
BFF 层可以调用多个微服务:
- 商品服务:获取商品基本信息。
- 库存服务:获取商品库存。
- 评价服务:获取用户评价。
-
BFF 层将这些数据整合后返回给前端。
示例代码:
const express = require('express');const axios = require('axios');const app = express();app.get('/product/:id', async (req, res) => {const productId = req.params.id;try {// 调用多个微服务const [product, inventory, reviews] = await Promise.all([axios.get(`https://product-service/api/products/${productId}`),axios.get(`https://inventory-service/api/inventory/${productId}`),axios.get(`https://review-service/api/reviews/${productId}`),]);// 聚合数据const result = {product: product.data,inventory: inventory.data,reviews: reviews.data,};res.json(result);} catch (error) {res.status(500).send('Error fetching product data');}});app.listen(3000, () => {console.log('BFF server running on port 3000');});
4.2 权限校验
- 在 BFF 层统一处理用户认证和权限校验。
- 使用 JWT 或 OAuth2.0 验证用户身份。
示例代码:
const jwt = require('jsonwebtoken');function authenticateToken(req, res, next) {const authHeader = req.headers['authorization'];const token = authHeader && authHeader.split(' ')[1];if (token == null) return res.sendStatus(401);jwt.verify(token, 'your-secret-key', (err, user) => {if (err) return res.sendStatus(403);req.user = user;next();});}app.get('/secure-data', authenticateToken, (req, res) => {res.json({ message: 'This is secure data', user: req.user });});
4.3 缓存优化
- 使用 Redis 缓存热点数据,减少对后端服务的调用。
示例代码:
const redis = require('redis');const client = redis.createClient();app.get('/cached-data/:id', async (req, res) => {const id = req.params.id;const cacheKey = `data:${id}`;client.get(cacheKey, async (err, data) => {if (data) {res.json(JSON.parse(data));} else {const result = await axios.get(`https://some-service/api/data/${id}`);client.setex(cacheKey, 3600, JSON.stringify(result.data)); // 缓存 1 小时res.json(result.data);}});});
5、实战示例
在基于 ECharts 图表的数据展示场景中,BFF(Backend for Frontend)层可以承担数据聚合和格式化的任务,从而让前端专注于图表的渲染逻辑,同时减少前端与多个后端服务的交互复杂度。
1. 场景说明
假设需要展示一个包含以下信息的 ECharts 图表:
- 销售数据(从
sales-service
获取)。 - 用户增长数据(从
user-service
获取)。 - 时间范围由前端传递(如最近 7 天、30 天等)。
BFF 层的目标是:
- 接收前端的时间范围参数。
- 调用多个后端服务获取数据。
- 聚合数据并格式化为 ECharts 所需的格式。
- 返回给前端。
2. ECharts 数据格式要求
ECharts 图表通常需要以下数据结构:
- X 轴数据:时间、分类等。
- Y 轴数据:数值(如销售额、用户数等)。
- 系列(series) :不同数据类型的集合。
示例 ECharts 配置:
option = {xAxis: {type: 'category',data: ['2023-10-01', '2023-10-02', '2023-10-03'], // X 轴数据},yAxis: {type: 'value',},series: [{name: '销售额',type: 'line',data: [120, 200, 150], // Y 轴数据},{name: '用户数',type: 'line',data: [50, 80, 70],},],};
3. BFF 层实现步骤
3.1 接收前端参数
前端通过查询参数传递时间范围,例如:
GET /chart-data?startDate=2023-10-01&endDate=2023-10-07
3.2 调用后端服务获取数据
BFF 层调用多个服务获取数据,例如:
sales-service
:返回指定时间范围内的销售数据。user-service
:返回指定时间范围内的用户增长数据。
示例代码:
const express = require('express');const axios = require('axios');const app = express();app.get('/chart-data', async (req, res) => {const { startDate, endDate } = req.query;try {// 调用销售服务const salesResponse = await axios.get(`https://sales-service/api/sales`, {params: { startDate, endDate },});const salesData = salesResponse.data; // 假设返回 [{ date: '2023-10-01', amount: 120 }, ...]// 调用用户服务const userResponse = await axios.get(`https://user-service/api/users`, {params: { startDate, endDate },});const userData = userResponse.data; // 假设返回 [{ date: '2023-10-01', count: 50 }, ...]// 数据聚合与格式化const dateSet = new Set();const salesMap = {};const userMap = {};// 收集所有日期salesData.forEach(item => dateSet.add(item.date));userData.forEach(item => dateSet.add(item.date));// 构建日期数组const xAxisData = Array.from(dateSet).sort();// 填充销售数据salesData.forEach(item => {salesMap[item.date] = item.amount;});// 填充用户数据userData.forEach(item => {userMap[item.date] = item.count;});// 构建 Y 轴数据const salesSeries = xAxisData.map(date => salesMap[date] || 0);const userSeries = xAxisData.map(date => userMap[date] || 0);// 返回 ECharts 所需格式res.json({xAxis: xAxisData,series: [{ name: '销售额', type: 'line', data: salesSeries },{ name: '用户数', type: 'line', data: userSeries },],});} catch (error) {res.status(500).send('Error fetching chart data');}});app.listen(3000, () => {console.log('BFF server running on port 3000');});
4. 前端使用
前端只需要调用 BFF 层提供的接口,并直接将返回的数据传递给 ECharts:
fetch('/chart-data?startDate=2023-10-01&endDate=2023-10-07').then(response => response.json()).then(data => {const option = {xAxis: {type: 'category',data: data.xAxis,},yAxis: {type: 'value',},series: data.series,};const chart = echarts.init(document.getElementById('main'));chart.setOption(option);});
总结
通过以上方法,你可以使用 Node.js 构建一个高效、灵活的 BFF 层架构,为前端提供更好的开发体验,同时优化后端服务的调用效率。