⚡️编排的艺术:BFF 的核心职能——数据聚合与 HTTP 请求

大家好,我是大布布将军。

在传统的 Web 开发中,前端只管展示,后端只管提供数据。但在 BFF 模式中,我们的 Node.js 服务肩负了更重的使命:它是一个**"数据中介" "服务编舞者" 。它不拥有核心业务数据(比如用户账号、库存数量),而是负责从各种核心后端服务**那里"取货"并进行"重新包装"。

本篇,我们将学习如何将业务逻辑剥离到 Service 层 ,并掌握 Node.js 中最常用的 HTTP 请求库

1. 业务逻辑分离:引入 Service 层

在 NestJS 或任何规范的后端框架中,我们遵循 MVC (Model-View-Controller) 或类似的分层结构。

  • Controller (控制器): 仅负责接收 HTTP 请求、调用 Service、并返回 HTTP 响应。它不应包含复杂的业务逻辑。
  • Service (服务层): 包含所有的业务逻辑、数据处理、以及与外部服务(数据库、其他微服务)的交互。这是我们 BFF 逻辑的核心所在。

这种分离是通过 NestJS 内置的依赖注入(Dependency Injection, DI) 实现的。

TypeScript

复制代码
// 📁 src/user/user.service.ts
import { Injectable } from '@nestjs/common';
import { User, ApiResponse } from '../interfaces';

@Injectable() // ① 声明这是一个可注入的服务
export class UserService {
    
    // 假设这是从数据库获取用户列表的方法
    getUsersFromDB(): User[] {
        // 实际场景:调用数据库 ORM 或其他核心微服务
        console.log('正在从核心数据库服务获取用户列表...');
        return [{ 
            id: 1, 
            username: '测试用户', 
            email: 'test@example.com', 
            role: 'customer' 
        }];
    }
}

现在,我们在 Controller 中安全地调用它:

TypeScript

复制代码
// 📁 src/user/user.controller.ts

import { Controller, Get } from '@nestjs/common';
import { UserService } from './user.service'; // 导入 Service

@Controller('users')
export class UserController {
    // ② 依赖注入:通过构造函数,NestJS 会自动创建并注入 UserService 实例
    constructor(private readonly userService: UserService) {}

    @Get('list') 
    getUsersList(): ApiResponse<User[]> {
        const users = this.userService.getUsersFromDB(); // ③ 调用 Service 逻辑
        return { 
            code: 200, 
            message: '列表获取成功', 
            data: users 
        };
    }
}

2. BFF 的核心:数据聚合与外部 HTTP 请求

BFF 的主要任务是聚合数据。这意味着我们的 Service 层需要向其他微服务 发起 HTTP 请求。在 Node.js 生态中,最主流的 HTTP 客户端是 axios 或 Node.js 原生的 fetch 。我们以更常用的 axios 为例。

场景:一个商品的详情页聚合

假设前端请求一个商品详情页 /product/detail?id=123,但这个数据分散在三个核心微服务中:

  1. 商品信息服务: 提供基本信息(名称、描述)。
  2. 库存服务: 提供当前库存量。
  3. 评价服务: 提供最近 5 条用户评价。

BFF Service 必须并行调用这三个服务,然后将数据整合。

TypeScript

复制代码
// 📁 src/product/product.service.ts
import { Injectable } from '@nestjs/common';
import axios from 'axios'; 
import { ProductDetailDTO } from '../interfaces'; // 假设这是最终返回给前端的结构

@Injectable()
export class ProductService {
    
    // 定义核心服务的基地址
    private readonly PRODUCT_CORE_URL = 'http://core-product-service:3001';
    private readonly INVENTORY_CORE_URL = 'http://core-inventory-service:3002';
    private readonly REVIEW_CORE_URL = 'http://core-review-service:3003';

    async getProductDetail(productId: number): Promise<ProductDetailDTO> {
        
        // ① 使用 Promise.all 并行发起三个独立的请求
        const [productRes, inventoryRes, reviewRes] = await Promise.all([
            axios.get(`${this.PRODUCT_CORE_URL}/products/${productId}`),
            axios.get(`${this.INVENTORY_CORE_URL}/inventory/${productId}`),
            axios.get(`${this.REVIEW_CORE_URL}/reviews/latest/${productId}`)
        ]);

        // ② 数据编排/转换
        const productInfo = productRes.data;
        const inventory = inventoryRes.data.quantity;
        const latestReviews = reviewRes.data.reviews.map(r => ({
            author: r.user,
            content: r.text
        }));

        // ③ 构建最终的、为前端定制的 DTO (Data Transfer Object)
        return {
            name: productInfo.name,
            description: productInfo.desc,
            currentStock: inventory,
            price: productInfo.price,
            latestReviews: latestReviews,
            // 注意:核心服务可能返回 userId,但 BFF 隐藏了它,只返回前端需要的 user 别名
        };
    }
}

3. Node.js 异步并发的威力

请注意上面代码中的 await Promise.all([...])

这是利用 Node.js 非阻塞 I/O 机制实现高性能的关键。当 axios 发起请求时,Node.js 主线程不会等待(非阻塞 )。它将这三个请求都扔给底层去处理,然后继续接收其他用户的请求。只有当所有请求都完成后,Promise.all 才会 resolve,我们才进行下一步的编排。

  • 如果使用同步请求: 总耗时 = 服务 A 耗时 + 服务 B 耗时 + 服务 C 耗时。
  • 使用异步并发: 总耗时 ≈ 三个服务中最慢那个的耗时。

这种对 I/O 效率的提升,是 BFF 层必须掌握的核心技能。

总结

至此,我们完成了 BFF 的核心逻辑:

  1. 将业务逻辑放在 Service 层,并利用依赖注入将其与 Controller 解耦。
  2. 使用 axiosfetch 向外部微服务发起请求。
  3. 利用 Promise.all 实现数据的并行获取和聚合 ,最大限度地发挥 Node.js 的异步优势,提高 API 响应速度。

下一篇,我们将进入后端数据持久化的世界------学习 关系型数据库(SQL)基础 ,以及如何在 Node.js 中使用 ORM (对象关系映射) 来操作数据。

相关推荐
冒冒菜菜2 小时前
RSAR的前端可视化界面
前端
_F_y2 小时前
Socket编程UDP
网络·网络协议·udp
roman_日积跬步-终至千里2 小时前
【源码分析】StarRocks EditLog 写入与 Replay 完整流程分析
java·网络·python
车载测试工程师2 小时前
CAPL学习-AVB交互层-媒体函数1-回调&基本函数
网络·学习·tcp/ip·媒体·capl·canoe
asdfg12589632 小时前
数组去重(JS)
java·前端·javascript
鹏多多2 小时前
前端大数字精度解决:big.js的教程和原理解析
前端·javascript·vue.js
爱尔兰极光2 小时前
计算机网络--TCP传输
网络·tcp/ip·计算机网络
恋猫de小郭2 小时前
八年开源,GSY 用五种技术开发了同一个 Github 客户端,这次轮到 AI + Compose
android·前端·flutter
醉舞经阁半卷书12 小时前
zookeeper服务端配置
网络·分布式·zookeeper