4.3.1 GraphQL与RESTful API
假设正在开发一个Web应用程序,让我们先来回想日常开发过程中的真实场景:服务端开发人员通过HTTP暴露了一个RESTful API,然后前端开发人员尝试对这个HTTP端点发起调用。这个HTTP端点一开始如代码清单4-56所示。
代码清单4-56 普通HTTP端点示例代码
请求: GET https://api.example.com/user/1 响应: { "id": "1", "name": "tianyalan", "age": "38", "address": "shanghai" }
看上去非常简单,对不对?刚开始前后端联调一切正常。不知什么时候,前端开发人员发现响应结果中原来的address字段不见了,而是出现了一个location字段,原来是后端开发人员觉得address这个字段名不合适,偷偷把它改成了location,但并没有告诉前端开发人员。图4-5展示了这一过程。这时候,前后端之间就需要重新明确API定义,并再一次进行联调。显然,这个过程实际上是非常浪费时间的。
图4-5 Web API中字段名变更示意图
相信你对上面这个场景非常熟悉,因为我们可能每天都在反复经历着类似的场景。从这些场景中,前后端开发人员已经意识到,传统的RESTful API并不能非常好地满足前后端分离场景下的交互需求。我们可以进一步把RESTful API存在的问题做一些梳理。
1. RESTful API存在的问题
RESTful API的第一个典型问题就是前端无法预判响应的数据格式,正如图4-5所展示的那样,一旦服务端对数据结构做了任何改变,前端都只能被动接收,而无法在发起请求之前感知到这种改变。
RESTful API的第二个典型问题是无法根据请求控制对应的返回结果。例如在图4-5的场景中,前端请求可能只想获取User对象中的name和age字段,而不需要address字段。显然,RESTful API无法满足这种诉求,除非另外开发一个HTTP端点。我们知道,数据在网络中的传输是需要成本的,无法按需获取数据同样导致了资源的不必要浪费。
RESTful API的第三个典型问题就是多次请求。再次回到上述场景中,假设User对象中包含了一组家庭成员信息。那么基于RESTful API,如果想要获取这些数据,就只能再发起一个专门的请求来根据User的id获取对应的家庭成员列表,例如图4-6中所展示的https://api.example.com/user/family/1。
图4-6 RESTful API的多次请求示意图
当然,我们也可以针对该需求专门设计一个能够同时返回用户信息和家庭成员信息的接口。但这又会引出RESTful API的第四个典型问题,即请求地址过多的问题。如果针对各个具体场景我们都需要一一暴露专门的HTTP端点,那么在一个系统中HTTP端点数量会非常庞大,难以维护和管理。
RESTful API的问题已经暴露得非常清楚了。如何有效解决这些问题呢?可以引入一个新技术,即GraphQL。
相比于REST,GraphQL可以说是一个比较新的技术,它于2012年诞生在Facebook内部,并于2015年正式开源。顾名思义,GraphQL是一种基于图(Graph)的查询语言(Query Language,QL),从根本上改变了前后端交互API的定义和实现方式。接下来,我们详细分析如何通过GraphQL解决RESTful API所面临的一系列问题。
2. GraphQL的解决方案
要想使用GraphQL,我们首先需要关注它发送请求的方式。针对获取用户信息这个场景,一个典型的请求示例如代码清单4-57所示。
代码清单4-57 基于GraphQL的请求示例代码
{ user (id: "1") { name age } }
可以看到基于GraphQL的请求方式与使用RESTful API有很大的不同。除了在请求体中指定了目标User对象的参数id值之外,我们还额外指定了name和age这两个参数,也就是告诉服务器端这次请求所希望获取的数据字段。
显然,这种请求方式完美解决了RESTful API中无法根据请求控制对应返回结果的问题。同时,这种请求方式也解决了前端无法预判响应的数据的格式问题,因为前端在请求的同时已经知道从服务端返回的数据字段就是请求中指定的字段,因此就不需要再对响应结果进行专门的判断和处理。
针对RESTful API存在的多次请求问题,GraphQL可以把多次请求合并成一次。例如,我们可以发送如代码清单4-58所示的请求。
代码清单4-58 基于GraphQL的合并多次请求示例代码
{ users { name age members { name } } }
在该请求中,我们指定了想要获取的User对象中的name和age字段,同时也指定了该获取用户对应的家庭成员列表字段members以及它的子字段name。这样,通过一次请求,我们就可以同时获取用户信息和家庭成员信息,而不需要像RESTful API那样发送两次请求。
讲到这里,你可能已经注意到,通过GraphQL发起请求实际上只需要指定一个HTTP端点地址即可,因为我们可以基于同一个端点传入不同的参数而获取不同的结果,也就不需要专门设计一批HTTP端点来分别处理不同的请求了。
总结一下,RESTful API所存在的核心问题通过GraphQL都可以得到解决。