文章目錄
- 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 層架構,為前端提供更好的開發體驗,同時優化后端服務的調用效率。