JackAtlas

Command Palette

Search for a command to run...

记一次 Node 中间层优化
5 days ago
前端开发

记一次 Node 中间层优化

明哥的朋友介绍的一单,一个创业团队想优化一下他们的架构设计,于是明哥拉上我一起去瞧了瞧。

客户的架构简单地可分为三层:前端是 React SPA、中间层是 Node.js BFF、后端是 Java。

最引人注目的是这个庞大的 BFF 中间层:

src/
├── routes/          # 87 个路由文件
├── controllers/     # 87 个 controller(一一对应)
├── services/        # 92 个 service
├── middleware/      # 15 个中间件
├── utils/           # 各种 helper
└── config/          # 3 套环境配置

这 87 个路由中,大部分是形如这样的:

// 伪代码
router.get(`/api/users/:id`, async (req, res) => {
    try {
        const result = await javaService.get(`/user-service/users/${req.params.id}`)
    } catch (err) {
        res.json({ code: -1, message: err.message })
    }
})

就是透传模式,把前端的请求原封不动地转发给后端,再把后端的响应原封不动地返回给前端(参数校验后端做了)。

简单计算一下成本

服务器成本

BFF 中间层跑了 3 台 2C4G 云服务器,每台 280 元/月;监控告警服务 200 元/月。合计 1040 元/月。

人力成本

员工每周花在 BFF 上的时间约 10 ~ 15 小时:

  • 后端新增接口后,BFF 需同步增加路由,约 4 小时;
  • Bug 修复,约 3 小时;
  • 升级依赖/安全补丁:约 3 小时;
  • 写测试:约 3 小时

如果按时薪 100 来算,每月有六千多元是花费在这个“反向代理器”上。

隐形成本

后端改接口要及时通知维护中间层的工友,人的自觉性或记忆力有时候是不可靠的。

诞生的缘由

和技术负责人沟通了一下这个 BFF 中间层存在的原因:

  1. 后端接口返回格式不统一
  2. 跨域问题,后端微服务分别是不同的域名
  3. 一个页面要调数个接口拼数据,代替前端做接口聚合

如何调整

透传相关

使用 nginx 反向代理

location /api/user-service/ {
    proxy_pass http://java-user-service:8080/;
    proxy_set_header X-Real-IP $remote_addr;
}

location /api/order-service/ {
    proxy_pass http://java-order-service:8081/;
    proxy_set_header X-Real-IP $remote_addr;
}

# ... 6 个微服务全部直连

另外,Nginx 加几行 header 解决跨域问题,认证则交给 Java 后端本来就有的鉴权中间件。

接口聚合

可以简单地在前端使用 Promise.all,甚至 React Query/SWR 已经有这个功能,另外还有缓存和错误重试等,比自己在 BFF 中间层手写更加健壮。

接口格式不统一

在前端拦截器(以 axios 为例)中进行格式统一。

api.interceptors.response.use(response => {
    const data = response.data

    return {
        success: (data.code === 0 || data.status === 'ok'),
        data: data.data || data.result,
        message: data.message || data.error
    }
})

但其实推动后端统一接口格式,是不是一个更好的解决方案?

不变的部分

除了透传模式外,还有一些涉及到条件分支、错误回退、事务补偿等的中间层逻辑,所以并不能将整个 BFF 中间层去掉。

总结

有些问题确实可以在 BFF 中间层一时解决,但随着项目发展,其维护成本远超收益。