基于RESTful API的GraphQL服务构建实践

RestTemplate或Feign都比较简单,在之前的章节中也有介绍,这里主要介绍非阻塞式的基于WebFlux的远程调用方式。

基于RESTful的BFF

基于RESTful的BFF并不复杂,如果我们的数据不需要额外处理,一般API网关的路由就可以满足,但如果要额外转换或接口组合,就需要在BFF去转换数据或调用不同的接口组合成新的接口。例如,现在前端要展示订单的详情页面,其中包括了订单和商品的信息,后端服务提供了订单详情的查询接口/orders/{id}和物流记录的查询接口/logistics/records?orderId={orderId},那么在BFF中可以组合这两个接口,并且按照前端想要的格式返回数据给前端。例如,可以开发一个新的接口,如/orders-details/{id},然后在接口的实现中可以通过RestTemplate或Feign等工具调用后端的基础服务再进行组合。

微服务背景下的数据交互:基于RESTful API的GraphQL服务构建实践

RestTemplate或Feign都比较简单,在之前的章节中也有介绍,这里主要介绍非阻塞式的基于WebFlux的远程调用方式。由于BFF处于前后端交互的中枢,几乎所有的请求都需要经过BFF,因此它承载着比后端服务更多的请求并发量,在BFF中更推荐使用非阻塞的方式来实现,这也是BFF选择使用Node.js的原因,Node.js的单线程、异步IO、事件驱动等特性使它拥有了更高的并发能力,当然,在Java中也可以通过WebFlux的方式来实现。

WebFlux提供了WebClient客户端来完成异步的远程调用,用法与RestTemplate类似。如果使用RestTemplate或Feign,在代码逻辑上也可以参考该例,首先在配置文件中定义订单和物流服务的地址,定义application.yml,代码如下。

微服务背景下的数据交互:基于RESTful API的GraphQL服务构建实践

如上述配置,直接写服务的地址,如http://localhost:8080,然后需要定义两个WebClient.Buidler的Bean,代码如下。

微服务背景下的数据交互:基于RESTful API的GraphQL服务构建实践

如上述代码,创建订单服务和物流服务客户端的Buidler,如果没有集成注册中心可以不加@LoadBalanced注解,如果集成了注册中心要在启动类上添加@EnableDiscoveryClient注解。

假设可知两个服务的接口都是根据订单的ID来进行数据查询,本身没有依赖,更加适合使用非阻塞式的方式进行接口的组合,代码如下。

微服务背景下的数据交互:基于RESTful API的GraphQL服务构建实践

在上述代码中,首先注入两个基础服务的WebClient的Buidler,然后发送请求,得到Mono和Flux对象(Mono表示单数的对象,Flux表示集合类的对象),通过Mono.zip的方式将两个响应式的对象合并,然后将这个方法添加到新的接口即可,代码如下。

微服务背景下的数据交互:基于RESTful API的GraphQL服务构建实践

对应的Controller代码如下。

微服务背景下的数据交互:基于RESTful API的GraphQL服务构建实践

这样就完成了一个非阻塞式的接口组合功能,接口过滤和转换比较简单,无非是对象的转换,这里不再演示。

不难发现,在使用了BFF之后,其实弱化了API网关的路由功能,业务接口都由BFF来调用或组合,简单的路由已经不能满足我们的需求,所以BFF更像是API网关的一个进化版本。

基于GraphQL的BFF

由之前的例子可以看出,如果是后端负责开发RESTful的BFF,前端虽然方便了,但会增加很多后端的工作量,任何不同页面需求的接口,哪怕只是一个字段不一样都需要后端编写代码来开发新的接口,那么有没有一种更好的方式能够动态地组合和过滤接口数据呢?GraphQL就是这样一个框架。

微服务背景下的数据交互:基于RESTful API的GraphQL服务构建实践

GraphQL的概念

GraphQL是Facebook开源的一款用来替代REST的新型API方案,官方的解释是一种用于API的查询语言。GraphQL对API中的数据提供了一套易于理解的完整描述,使得客户端能够准确地获得它需要的数据,而且没有任何冗余,让API更容易地随着时间的推移而演进,还能用于构建强大的开发者工具。

例如,现在有一个查询用户信息的接口,假设接口返回的完整数据如下。

微服务背景下的数据交互:基于RESTful API的GraphQL服务构建实践

由上述JSON可以得知,接口返回了一些用户的基本信息和角色信息,使用RESTful API时请求如下。

微服务背景下的数据交互:基于RESTful API的GraphQL服务构建实践

GraphQL可以像写查询语句一样由消费者自己来决定想要查询的数据,而不需要服务端编 写任何代码 , 假设 GraphQL 的接口路径是/graphql,那么请求代码如下。

微服务背景下的数据交互:基于RESTful API的GraphQL服务构建实践

先不讨论GraphQL的请求语法,大致上可以看出这里的查询定义了想要接口返回的字段,从请求的难易程度上,似乎GraphQL要差很多,之前不需要任何参数,只需一个URL就可以解决接口调用,现在要写很多代码。

一个优秀的框架,复杂的代码一般是为了更多的灵活性,加入现在前端的页面上不需要显示全部的用户信息,只需用户的ID和username。使用RESTful的接口一般会有两种做法:一是修改原先的/users/{id}接口,让它只返回需要的这两个字段;二是在BFF重新写一个接口,如/short-users/{id},这时在BFF端就需要对应编写额外的数据过滤的代码。

如果使用了GraphQL,则不需要在服务端进行任何代码的更改或编写,只需在请求的查询中输入如下代码。

微服务背景下的数据交互:基于RESTful API的GraphQL服务构建实践

从上述代码可以看出,在只需要修改请求Body的查询语句,即修改user(id: “1”){…}中定义的字段时,GraphQL就会自动过滤需要的数据,而不需要在后端修改或定义新的接口。

当然,在这个简单的例子中可以不去在乎多余的数据,但如果这里的用户信息还包括一个富文本格式的自我介绍,那么多余字段所带来的网络消耗是不容忽视的。如今移动互联网盛行,一款好的App,其流量管理必然是更加精细的,如果你的App比其他人的要消耗更多的流量,那么流失的可能就不仅是手机的流量了,还有你的用户。

微服务背景下的数据交互:基于RESTful API的GraphQL服务构建实践

当然,除了数据的浪费,多余的查询还可能带来性能问题,先不说数据量带来的网络传输消耗,如接口还返回用户的角色信息,在BFF层的接口组合调用逻辑如图6.7所示。

微服务背景下的数据交互:基于RESTful API的GraphQL服务构建实践

由图6.7可知,后端服务一般会提供用户和角色等基础服务的接口,然后当客户端同时需要用户和角色信息时,会在BFF进行接口的组装。如果在查询用户信息时并不需要角色数据,这里多一次查询显然不值得,使用RESTful时要分别开发两个接口,如果使用GraphQL就可以根据我们定义的数据和查询语句,去动态组合需要查询的数据,GraphQL动态组合接口示意如图6.8所示。

微服务背景下的数据交互:基于RESTful API的GraphQL服务构建实践

图6.8清晰地表示GraphQL在组合接口过程中的作用,当客户端发起的请求中并没有包含角色的字段定义时,GraphQL就不会调用角色的接口。反之,当客户端发起的请求中包含了角色字段时,GraphQL就会自动地查询角色接口,当然这需要后端在一开始就定义好数据的获取策略,在本章后面会详细介绍具体的用法。这样我们就能灵活地组合和过滤接口的数据,而不需要后端进行额外的开发了,同时前端也不用等待后端的新接口,直接就可以根据自己的需求查询想要的数据,使用GraphQL的前后对比如图6.9所示。

微服务背景下的数据交互:基于RESTful API的GraphQL服务构建实践

GraphQL在客户端的基本用法

GraphQL的整体用法大概分为3个部分:描述数据、请求数据、解析请求并执行查询。其中,描述数据和解析请求都是在BFF端需要完成的工作,请求数据是客户端需要关心的,当然,编写请求语句需要了解数据的描述,所以前后端的开发者需要共同来讨论数据应该如何描述,而且大部分时候都由前端来驱动设计。

本节在讲解客户端的用法时,会给大家介绍GraphQL中数据是如何描述的,以及如何根据描述来编写查询语句。首先来看什么是数据的描述。GraphQL中通过.graphql文件来描述数据,并且将数据的描述称为schema,基础结构如下。

微服务背景下的数据交互:基于RESTful API的GraphQL服务构建实践

在上述描述信息中,GraphQL通过不用的type的关系来描述数据,文件的根节点是schema,schema有两个子节点:Query和Mutation,Query用来表示查询的数据,Mutaiton用来表示增删改的数据,这些格式都是固定的。

可以自定义两个type:Query和Mutation来添加业务数据的描述,如增加用户信息的描述,可以通过定义type和type中的字段来描述,代码如下。

微服务背景下的数据交互:基于RESTful API的GraphQL服务构建实践

这样就定义了一个User的类型并描述了User的字段信息和字段类型。GraphQL内置了几种基础数据类型用于支持大部分的数据场景,类型如下。

(1)Int:有符号32位整数。

(2)Float:有符号双精度浮点值。

(3)String:UTF-8字符序列。

(4)Boolean:true或false。

(5)ID:表示一个唯一标识符,通常用以重新获取对象或者作为缓存中的键。

除了基础数据类型,还可以通过type来灵活地自定义各种类型。例如,可以定义一个角色类型,代码如下。

微服务背景下的数据交互:基于RESTful API的GraphQL服务构建实践

然后可以在User中使用这个类型,代码如下。

微服务背景下的数据交互:基于RESTful API的GraphQL服务构建实践

当然,有时用户存在多个角色,可以将role的类型修改为集合,方式很简单,使用type即可,代码如下。

微服务背景下的数据交互:基于RESTful API的GraphQL服务构建实践

除了使用type来自定义新的类型,还可以使用enum来定义一些枚举类型,如用户的性别可以使用枚举,代码如下。

微服务背景下的数据交互:基于RESTful API的GraphQL服务构建实践

描述好了User的数据,现在写一个查询的接口,如何实现与Query的结合呢?方式如下。

微服务背景下的数据交互:基于RESTful API的GraphQL服务构建实践

通过根schema我们声明了Query类型,该类型可以用来描述接口,如查询用户的方法,通过“user(id: String!): User”来定义,其中user表示查询的方法名,参数是id,类型是String,在类型后面加上“!”表示参数是必需的,然后“: User”表示方法返回类型是User类型,这样就将User类型与查询关联上了。那么,定义完数据后,我们如何使用呢?

如果要查询用户的数据,GraphQL的查询语句应该怎么写呢?代码如下。

微服务背景下的数据交互:基于RESTful API的GraphQL服务构建实践

query Query{…}是固定的写法,表示user方法的根,和我们最开始定义schema的命名相同即可,之前介绍过关于数据过滤的用法,即通过修改user(){…}中的字段来调整需要查询的数据,如想查询id和roles,代码如下。

微服务背景下的数据交互:基于RESTful API的GraphQL服务构建实践

需要注意的是,只要我们查询的字段是一个type,必须指明该type内需要的字段,如roles是Role类型的,下面这样写会报错。

微服务背景下的数据交互:基于RESTful API的GraphQL服务构建实践

错误如下。

微服务背景下的数据交互:基于RESTful API的GraphQL服务构建实践

很明显,意思是说子类型的字段是必需的,即需要指明roles{…}中的字段需要哪些。

可 以 看 出 , GraphQL 放 弃 了 像 RESTful 一 样 通 过 “URL+HTTP Method” 来 定 义 接 口 的 方 式 , 通 常 所 有 请 求 的 URL 都 一 样 , 如“/graphql”,然后使用POST方法,将查询语句通过Body传递到后端,假设后端的接口地址是“POST – /graphql”,那么请求数据格式如下。

微服务背景下的数据交互:基于RESTful API的GraphQL服务构建实践

查询用户的请求如下。

微服务背景下的数据交互:基于RESTful API的GraphQL服务构建实践

由于GraphQL的Query并不是JSON格式,通常使用字符串来传递,参数ID是定义的String,因此需要使用“”来转义,表示当前参数是字符串,这里的请求参数比较简单,一旦参数复杂起来,比如是一个对象,那么前端在定义查询时就需要大量的字符串拼接和转移的工作,有没有更好的传递参数的方式呢?

通过设置参数变量,我们可以方便地定义JSON格式的参数,还是刚才的例子,改造成如下写法。

微服务背景下的数据交互:基于RESTful API的GraphQL服务构建实践

通过在父节点Query上增加参数的定义($id: String!)来表示在变量variables中读取key为id的值,并将值赋予变量$id,原先user(id:”1″)改为user(id: $id),需要注意的是,Query(…)定义的参数$id的类型必须与user的一致,包括类型的“!”(非空)属性。

GraphQL会将返回的JSON数据包裹在data中,并且以user为JSON的key,代码如下。

微服务背景下的数据交互:基于RESTful API的GraphQL服务构建实践

当然,我们可以更改这个名称,比如有时方法名不是user,而是findUserById,那么它作为data的key值显然不合适,GraphQL提供了别名的机制帮助我们在请求时便捷地修改返回的key值,而不需要修改数据的schema,代码如下。

微服务背景下的数据交互:基于RESTful API的GraphQL服务构建实践

通过“a: user(…)”就可以将最终返回的JSON数据的key值改为a,结果如下。

微服务背景下的数据交互:基于RESTful API的GraphQL服务构建实践

同样,可以修改自动的名称,如修改username为name,请求的代码如下。

微服务背景下的数据交互:基于RESTful API的GraphQL服务构建实践

得到的结果如下。

微服务背景下的数据交互:基于RESTful API的GraphQL服务构建实践

除了变量和别名的用法,GraphQL还提供了更多的高级查询用法。

微服务背景下的数据交互:基于RESTful API的GraphQL服务构建实践

例如,可以通过指令(Directives)来为数据字段添加一些动态的条件,或者定义一些内联片段(Inline Fragments)来减少重复的语句编写,这里不再一一列举。目前,本书中介绍的用法可以满足大部分项目的需求,感兴趣的读者可以到GraphQL的官网学习详细的查询和变更的用法,网址是https://www.graphql.org/。此外,GraphQL还提供了可视化的开发工具GraphiQL来帮助我们快速地编写请求的语句,并验证请求的结果,大家也可以通过它的在线版本练习GraphQL的写法,如图6.10所示。

微服务背景下的数据交互:基于RESTful API的GraphQL服务构建实践

除了查询(Query),我们还可以定义增删改(Mutation)的接口,如创建一个用户,它的schema写法如下。

微服务背景下的数据交互:基于RESTful API的GraphQL服务构建实践

大致上和Query的写法类似,需要注意的是,这里出现了一个新的关键字input,GraphQL中的另一个设计理念是将请求的参数类型和返回类型分开,这里无法复用User类型作为创建用户的输入参数,需要重新定义一个input类型的UserInput,同样的UserInput无法作为方法的返回类型,GraphQL会做语法的校验。

微服务背景下的数据交互:基于RESTful API的GraphQL服务构建实践

因为随着项目的推进,我们的数据类型不断变化,返回类型会类似于后端的数据模型,而参数类型会更加和用户界面贴近,虽然可能会有一定的冗余,但分离之后两边的模型不会相互影响,反观之前项目的代码,无论是值对象还是模型都混乱不堪,并夹杂着请求和后端模型的数据。根据定义好的schema,创建用户的请求数据如下。

微服务背景下的数据交互:基于RESTful API的GraphQL服务构建实践

这时后端已经实现,返回结果如下。

微服务背景下的数据交互:基于RESTful API的GraphQL服务构建实践

本文给大家讲解的内容是基于RESTful的BFF

内容出处:,

声明:本网站所收集的部分公开资料来源于互联网,转载的目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。如果您发现网站上有侵犯您的知识产权的作品,请与我们取得联系,我们会及时修改或删除。文章链接:http://www.yixao.net/tech/25302.html

发表评论

登录后才能评论