BFF初探
一、当前开发遇到的常见“痛点”
在前后端联调时,有些麻烦出现的频率不低而且可能会较大程度影响开发效率,其中就包括前后端对接口数据格式设计的差异。两者一是基于领域模型,一是基于用户交互,因此设计出来的数据结构经常有差异,使得前端从接口取到数据后还需要多做一层“数据规格化”(我自己的称呼…)的工作。举两个例子:
命名习惯不一致,例如有这样的一个列表数组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,可以对请求的流程做一定的控制。