一、当前开发遇到的常见“痛点”
在前后端联调时,有些麻烦出现的频率不低而且可能会较大程度影响开发效率,其中就包括前后端对接口数据格式设计的差异。两者一是基于领域模型,一是基于用户交互,因此设计出来的数据结构经常有差异,使得前端从接口取到数据后还需要多做一层“数据规格化”(我自己的称呼…)的工作。举两个例子:
命名习惯不一致,例如有这样的一个列表数组 ranks:
[
{
id: 1,
value: ‘DUCHY’
},
{
id: 2,
value: ‘KINGDOM’
},
{
id: 3,
value: ‘EMPIRE’
}
] 页面的渲染组件需要 value 这一属性名,但接口数据使用的是 name,那么就需要做一个遍历,手动修改属性名。
类似的情况还有(null、’’)/([]、{})的转换等等,这都是为了数据格式所做的额外操作,与业务逻辑并没有太大关联。
为复用某些接口,需要做一些接口数据额外处理:
数据对象 info:
{
countries: [
{
name: ‘Austria’,
cities: [
‘Vienna’,
‘Tirol’
]
},
{
name: ‘Persia’,
cities: [
‘Isfahan’,
‘Shiraz’
]
},
{
name: ‘United States’,
cities: [
‘San Francisco’,
‘Mountain View’
]
}
]
} 现在有一个场景,我只想要 countries 数组的第一项(或者说,在特定场景下只有第一项是有意义的),那么我如果复用这一接口拿到的数据,每次就都要做一个 let specifiedCountry = countries[0] 的默认赋值,在更复杂的场景下这种赋值可能嵌套更深、重复次数更多。
显然,处理数据格式与处理交互时的数据变化应该分离,这样前端会有更多精力去处理交互的业务逻辑。
二、对 GraphQL 的探索及其应用
要应对这一需求,当下的 GraphQL 是一个不错的方案,用它可以做到指定一个请求格式,然后获取所需的数据,同时它也支持一些逻辑判断和抽象,如 directive、Fragment、Variable 等等,以下取这三个作为例子,演示一下对于上述例子的解决方案:
对于(一)中的第一个例子:
考虑 GraphQL 的 alias 解决方案:GraphQL 的别名 alias 设计目的是在同一个 Type 下可以返回多个对象而不发生命名冲突,不过我们也可以用它做一下 name -> value 的重命名:
ranks {
id
value: name
}* 嵌套的别名是否可行未知,还需要做一下验证
对于(一)中的第二个例子:
使用 variable 和 directive 做一些逻辑处理:
query Country($isFirst: Boolean!) {
info(episode: $episode) {
countries @include(if: $isFirst) {
name
cities
}
}
} 数据模型中,第一个元素包含 isFirst: true 即可(这里可能还要深究一下,isFirst 如何设置才能真正解决原来的问题,或者说需要别的判断方式)
三、BFF 的定位及 node.js 在 BFF 层能做的事情
BFF 的应用场景有很多,聚合后端接口,提供给第三方 api 都是它可以负责的工作。聚合后端接口在上文已经有了类似的操作,不过做的不是聚合几个接口而是对某个接口做了额外处理。
BFF 层的设计一般来说可以更好地满足产品快速迭代的需求,因为它将 UI 交互与部分服务都交给了一个 team(可以是 Frontend)负责,这样可以大大减少不同 team 的沟通协调成本。
node.js 也可以做一部分在 BFF 层的数据加密(放 BFF 层合适吗?)、请求转发(需要和 nginx 做一下对比)
也可以做一些性能优化的工作(依然要对比以往服务端的解决方案)
性能优化高并发与负载均衡:常见的情况下,高并发的性能制约包括了大量的 I / O 操作时 CPU 利用率较低,而 node.js 在处理 I / O 密集型操作时有自己的优势。
在负载均衡方面,nginx 有几套常用的请求分配方案,也有 shared memory 的解决方案,并且在保证会话一致性上有较好的表现。
node.js 的 clientRequest 对象也会维护一个 header queue,可以对请求的流程做一定的控制。