文章目录
在使用 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 示例架构
plaintext
Frontend (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 层架构,为前端提供更好的开发体验,同时优化后端服务的调用效率。