微服务化带来的最大问题是服务边界的划分。可以从两个维度看:纵向职能和横向业务的划分。
纵向划分,BFF在网关与领域服务之间比较容易确定,起到桥梁的作用,一来可以快速支持前端的迭代;二来可以让下游的领域服务更加纯粹。
横向的划分是最大的难点,后面会有分析。
同一个团队内可以更高效沟通和协调资源,快速响应迭代,同时也可以减少协作流程和依赖。
不负责业务实现,只负责最基本的参数校验、数据调用和组装。其它的逻辑可以向上浮到网关层(比如鉴权或用户会话)或向下下沉到后台服务。后台服务以领域设计模式DDD的话还可以有效防止乱窜,让服务更规整。
每个BFF层创建时,必须很明确它的上游是哪些(一个或多个)终端,android / ios / web / h5 / 小程序…明确的服务终端,可以更好的设计BFF层的功能。让BFF层更小更容易维护,也更容易提供特定的能力,不需要考虑兼容太过广泛的设备。
当然,还有其它一些比较基础的原则,比如应该暴露出去的是尽量通用的接口,比如RESTful接口、JSON结构数据等。
纵向划分非常容易,但是横向划分就没这么容易了。可以先了解下下面三种划分方式:
每个产品一个BFF层,这个是在一些刚做前后端分离时喜欢用的模式。
优点:
缺点:
如果一个产品规模很大,由多个团队共同完成时,不太可能按一个BFF服务来实现整个产品功能,这时候,更可能会按产品的模块来划分BFF服务。
一个模块使用一个BFF服务去支撑一个产品不同的端。这样做的好处是可以缓解一个大BFF服务带来的复杂性,但实际上也没有太大的变化,只是将BFF的规模变小了而已。
优点:
缺点:
BFF最初的理念其实是按终端来划分。但是又涉及到一个BFF服务对应一个还是多个终端的问题。这个暂且不要纠结,后面会说到一些BFF划分的推荐原则问题。
优点:
缺点:
上面的BFF服务划分仅仅是从不同的维度进行的讲解,但实际工作中通常可没有这么简单直接。往往,一个产品终端太多,模块太多时,仅仅是按产品、模块或终端来划分会显得很不协调。但又无法提供一个更明确的划分方法,所以最终还是将划分的重任落在“经验丰富”的开发者身上,这明显是不靠谱的一件事(不同的人可能会有不同的划分方法)。
架构设计没有银弹,只有适合的。所以在代码复用和资源利用率上,往往会与灵活、自主性相违背,就像CAP原则一样,只能取其二而舍其一。
BFF层为了保持它的独立性,禁止BFF之间调用(即是平级调用)。BFF只对自己明确的终端负责,不关心周边的产品/终端。特别是按终端划分时,往往不同端的页面是比较接近的,开发人员有时为了省事,会直接调用“拿”其它端的接口来用,这应该是明令禁止的!
以下是我们公司制定的一些BFF拆分原则,供大家参考:
如果UI直接调用底层服务(领域服务或基础服务),会造成领域服务或基础服务无法稳定,经常需要跟随UI变动。所以每个前端必须有BFF层,这时条红线。BFF存在的目的,除了快速响应前端迭代外,还有一个目的是为了隔离前端后端,让后端更稳定,有沉淀。
一个产品比较大时,需要按终端划分,同时还需要按模块划分。这个还取决于各自公司使用的技术栈,如果使用容器,分服务的成本会显示小很多,次之是虚拟机,再次之是直接在物理机上。
这应该是最重要的一条拆分原则了。按终端拆分并不是每一个端都需要一个BFF服务,这样会有太多的服务,不利于维护。BFF原则上是跟着UI走的,无论是哪个端的UI,如果差异性不大,大可对接同一个BFF服务。UI差异性体现在:
APP、小程序的更新需要发版,有的还需要审核,所以更改成本很高。如果在BFF层提供足够保真的数据(比如UI上显示的金额,小数点保留几位;比如一个列表的排序等,直接在BFF计算好直接吐给APP),能够快速的修改BFF的数据而快速更新APP、小程序上的数据,不用发版。
这其实是微服务的设计原则。无状态即是可以自由水平扩展,而不需要去同步、维护一些状态,比如用户的登录态;用户的会话等。
用户的登录态、会话,应该抽取到网关层。而BFF层只保留与底层用户服务的注册、登录、查询通道(通用网关甚至可以不需要)。
只有一种情况可以抽取公共包在不同的BFF服务间共用,那就是抽取的功能应该是完全没有业务逻辑的,比如Utils包。注意,==公共库通常是耦合的主要原因。==所以要抽取公共库时,必须非常小心,更多时候应该考虑开源社区是否已经有类似的包了。
公共的业务代码不能向上或平行抽取,最好的解决方案是向底层的领域服务下沉业务能力。向下沉,直接面对的是领域服务,所以下沉到哪个地方,怎样下沉直接遵循DDD原则。这样不会把底层服务搞乱,也会越来越规整。
它定义了:
所有的数据都是由一个统一的http接口提供。使用gql语法,将页面需要的数据一次性查询出来。不多不少,一来减少http请求;二来不会有多余数据;三者可以UI的变换可以快速修改查询脚本实现。
领域:
在BFF层几乎看不到领域的概念,所有的数据都是按UI所需进行拼装。所以GraphQL的出现算是眼前一亮,简直就是BFF的一股清流呀。
领域对象很重要,它是对象的定义,在哪里出现含义都是一样的。特别是配合GraphQL的关联特性,可以将需要的数据都关联出来,这也是GraphQL的魔力所在,拼数据在GraphQL里面是不存在的!
成本
GraphQL不需要考虑拆分的问题,因为它就是集中管理的。另外,对于开发人员来说,如果底层服务也是使用DDD,那用GraphQL简直就是爽得不要不要的,因为它不需要再去定义领域了,拿来即用…
从运维角度来说,也是一种高效的部署方式,还节约网络成本。
监控
GraphQL的特色之一,监控粒度可以细到字段。字段取值耗时、字段是否有被使用等一目了然。这对于需要下线的字段来说,是个非常有用的工具。
把GraphQL吹上天了,那么GraphQL是否就真的是个灵丹妙药呢?为什么现在用的企业并不是很多呢?
确实,GraphQL的初衷很好,但是在具体实施的时候却有很多的问题,主要是:
维护图的成本很高
定义图,需要对业务、对UI的把握非常到位,不然再好的东西,没有设计好,最后也会变成一团糟。图的定义说白了就是领域模型、领域能力的定义。
另外,多个团队维护同一个产品时,分工协作也是一个很大的问题。
维护数据逻辑成本很高
图中的每个类型、字段取值都需要指定,比如从API中取…
GraphQL在定义之初是无法知道会被怎样使用的,而是由GraphQL引擎自动完成,虽然一些GraphQL引擎的实现中帮你准备了很多性能优化策略,但是最终还是需要人工去定义。很容易出现循环调用PRC、N+1等性能问题。
即便是当前已经商业化了的Apollo GraphQL,也躲不开这两大块问题。
把GraphQL吹上天,接着又泼了盆冷水,还让不让人愉快地玩耍了?
这里介绍一个Java的GraphQL实现:,它是github上的一个开源项目,基于apache-2.0协议开源,是个很有意思的项目。
看看它提供的主要功能:
Duo-GraphQL保留了GraphQL的所有优势,自动化完成了GraphQL的需要人工维护的工作。同时又以传统的微服务的理念协作,保留了微服务团队敏捷协作的特点。最大程度匹配现代的互联网开发模式。
它,会是BFF的破局者吗?