GraphQL,下一代REST?

Posted 内文的后花园

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了GraphQL,下一代REST?相关的知识,希望对你有一定的参考价值。


值此新春佳节来临之际,祝各位朋友、网友新春快乐,阖家安康。记忆中从未有一个春节像今年这么特别,疫情的发生使得每一个人都足不出户、战战兢兢。即便是2020年了,人类在疾病面前依然显得那么渺小。大年初三一觉醒来,科比早逝的消息更是犹如一声闷棍,让这个春节难上加难,很多人的青春就此结束。唯一值得庆幸的是,在此艰难之际,有一群最美“逆行者”为我们负重前行,整个社会都在一起共度时艰。困难总会过去,希望不会遥远,让我们一起静待春暖花开时。



三年前,当时我还在老东家,在与小伙伴们谈论GraphQL的时候,我们都觉得这是个很新的东西,鲜有人提及,还是等等看Facebook之外的成功案例再看要不要跟进,毕竟Facebook也才在2015年将GraphQL开源。去年,GraphQL一词开始在不同场合出现,更没想到的是我司已有两个项目使用上了GraphQL,种种迹象显示是时候重新审视下这个并不新的概念了。


1

GraphQL的应对之道


在认识GraphQL之前,我们先看下生活中很熟悉的一个场景——有一个Blog页面,页面上需要展示当前用户的名称、该用户最近发表的博文标题以及最新关注该用户的三位粉丝名称。

https://www.howtographql.com/basics/1-graphql-is-the-better-rest/


简单明了的需求,如果交给我们去实现的话,我们大抵会这么做。在REST的世界里,用户、博文和粉丝都是一种资源(Resource),所以我们会创建以下三个REST接口:

  • 通过/users/<id>获取用户名

  • 通过/users/<id>/posts获取该用户的博文

  • 通过/users/<id>/followers获取关注该用户的粉丝



计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决。

David Wheeler


为了解决Overfetching和Underfetching的问题,同时也为了简化前端的开发和减少不必要的数据传输,人们一般会在前端与后端之间引入一个聚合层。聚合层提供了一个新的API供前端调用,该API聚合了后端多个API的功能并只返回前端所需要的数据,前端只需要与该API进行交互。


聚合层看似解决了问题,但本来前后端的直接对话中间无端多了一层新的依赖,既复杂化了方案也增加了开发的工作量。有没有其它方法?想想我们在数据库查询中是如何做的?我们通过编写SQL语句,语句中指定要查询的字段以及每个字段从哪个表获得,通过一条select语句外加join便可搞定复杂的多表查询,并且只返回我们所需要的字段。如果前端也能通过类似SQL这样的形式来向后端请求数据,那么问题也就迎刃而解。


GraphQL的解决之道正是与SQL有异曲同工之妙,Client端与Server端直接对话,没有中间商赚差价。Client端在请求时告诉Server端需要哪些对象的哪些数据,Server端解析Client端请求并按需返回结果。前面提到的例子可以通过下面的请求和响应来实现。


GraphQL,下一代REST?

GraphQL Request


GraphQL,下一代REST?

GraphQL Response


2

GraphQL定义


通过前面的例子大家对GraphQL有了感官的认识后,我们还是要正式的来介绍下它。


GraphQL is a data query language and specification developed internally by Facebook in 2012 before being publicly open sourced in 2015.

graphql.org


GraphQL官网对GraphQL的介绍十分简短,但个中含义却不少,值得仔细琢磨一番。首先,它身世显赫,出身于名门Facebook,血统高贵,背靠大山支持其发展。背靠大山意味着一方面它有丰富的使用场景可以让其落地、验证和演进,另一方面也有源源不断的资源帮助其包装和推广。


GraphQL,下一代REST?


第二,它不是一个新鲜事物,已有一定的历史,2012年起就已经在Facebook内部使用,久经考验。从2015年开源至今也有四年多,它并不是一个闭门造车的产物,已经在Github、Pinterest、Coursera、Shopify等网站使用。


第三,它是一门数据查询语言,主要使用在数据查询的场景。但它又不仅仅是一门数据查询语言,它还支持创建、修改和删除(统称Mutation)以及数据订阅和推送(Subscription)。


第四,它是一个规范而不是具体实现。规范意味着它是一份纲领、指导意见,它只是告诉人们GraphQL要做什么,具体怎么做并不与任何一门开发语言绑定,也不与任何一种数据持久化设施绑定,而是交由具体的服务端和客户端去实现。所以我们会看到不同的GraphQL实现之间有些许差异。例如有些GraphQL的实现一律把请求作为POST的请求体发送,有些则允许GET操作把查询内容作为查询参数传输。


由于GraphQL只是一个规范,理论上GraphQL不限于在HTTP/HTTPS协议使用,它也可以基于TCP协议作为数据传输载体。只是在现有的服务端和客户端实现中,GraphQL几乎都是基于HTTP/HTTPS来传输的,并通过统一的端点/graphql暴露,毕竟Web才是GraphQL的主要使用场景。


3

GraphQL基本概念


GraphQL支持Query(查询)、Mutation(修改)和Subscription(订阅)三种操作,但鉴于它主要解决的问题在查询,而且Mutation的实现过于冗长,至少graphql-java的实现需要通过人工写代码去映射请求体参数和对象,个人并不建议通过GraphQL去做数据的修改,所以这里只简单介绍一下查询相关的基本概念。


无论是服务端还是客户端,GraphQL都始于schema。Schema定义了接口支持的操作以及请求和响应的数据类型。


type Query {
   bookById(id: ID!): Book
}

type Book {
   id: ID
   name: String
   pageCount: Int
   author: Author
}

type Author {
   id: ID
   firstName: String
   lastName: String
}


以上面的schema为例,schema里面的所有定义都是类型(Type)。最顶层定义了一个Query type,Query是每个schema都必须有的,Mutation和Subscription则是可选。上述例子的Query里面有一个Field,名称为bookById,查询的参数(Argument)id是一个变量(Variable),根据请求时输入而定,id的类型是ID,ID是一个唯一标识符,感叹号表示该查询参数为必填项,查询的结果是Book。


Book是对象类型(Object Type),它包含有四个属性,其中author又是一个对象类型,而其它三个属性属于原子属性,不包含子属性,无法再被分隔,这些原子属性在GraphQL中称为标量类型(Scalar Type)。GraphQL内置了Int、Float、String、Boolean和ID五种标量类型,用户也可以通过scalar关键字自定义新的标量类型。


scalar Date


有时候,我们需要通过定义枚举类型(Enumeration Type)来限定某种属性的取值范围,在GraphQL中可以通过enum关键字来定义。枚举类型属于特殊的标量类型。


enum Category{
  SCIENCE
  FINANCE
  HISTORY
}



更多的schema定义和概念可以参考官网https://graphql.org/learn/schema/。


4

GraphQL与REST


在初步认识了GraphQL之后,我想很多人应该跟我有同样的疑问,我已经在用REST了,需要转到GraphQL么?GraphQL会取代REST么?毕竟你去Google搜索GraphQL vs REST,搜索结果前几页中就有好几篇文章声称GraphQL将会取代REST。


先说结论,不会。从结果倒推,GraphQL成立至今也已经7、8年了,要是它真是REST的终结者,今天很多API就不会依然还是REST,毕竟做IT的人都不傻。从理性分析,两者也各有其优劣。


GraphQL在解决Overfetching和Underfetching的问题同时,也有其它的优点,例如作为服务端它可以精确地统计哪个字段被客户端使用哪个没有,以便日后可以清楚知道删掉某个字段是否会影响客户端。而对于REST,我们无法得知哪个字段是否被客户端用了,为了不影响客户端只能一律通过添加版本的方式来避免影响客户端。


此外,GraphQL通过schema从协议角度定义了请求和响应的格式和类型,作为前后端交互的契约(Contract),是一种强类型的约束,相比起来REST的约束则要弱很多。


但GraphQL相比起REST也有其不足之处。例如GraphQL的多数实现都是通过POST来提交请求,无法利用HTTP的缓存机制来缓存响应结果,只能通过GraphQL的客户端类库来实现客户端缓存。而且GraphQL只支持JSON格式的请求和响应,对于XML或者binary的请求则无能为力。


对于REST API,客户端可以通过HTTP状态码来判断请求的响应结果成功与否,以及具体是哪种错误,例如404代表查询的对象不存在。而GraphQL只能通过判断该字段对应的值是否为null来得知结果存不存在。


更糟糕的是,当查询的结果有部分成功部分失败时,判断起来更加困难。例如先前的例子中,当book查询成功而author失败时,客户端需要去解析errors结果去判断是哪个地方出错以及出错的原因是什么。


像这些细节上的差异网上还能找出很多,在对比中,我突然意识到GraphQL和REST的最大不同是在理念上的差异。REST是以服务端为中心,API反映的是服务端业务的建模;而GraphQL是以客户端为中心,API反映的是客户端的查询需要。如果你不知道谁可能使用你的API以及如何使用你的API,那么REST会是一个合适的选择。反之,如果客户端在服务端控制之下,你很清楚知道有什么客户端使用你的API,而且API的查询组合比较稳定,那么GraphQL不失为一个选择。


5

总结


本文通过一个例子引出了GraphQL能为人们解决什么问题——Overfetching和Underfetching,并简单介绍了GraphQL的定义——它是一个规范以及核心概念;最后对比了GraphQL与REST的一些差异,它们的最大不同在于设计理念,REST以服务端为中心,GraphQL以客户端为中心。



参考资料:

  1. https://graphql.org/

  2. REST vs. GraphQL: A Critical Review,  https://goodapi.co/blog/rest-vs-graphql

  3. GraphQL vs REST: putting REST to rest,  https://www.imaginarycloud.com/blog/graphql-vs-rest/



以上是关于GraphQL,下一代REST?的主要内容,如果未能解决你的问题,请参考以下文章

分分钟将 REST 转换为 GraphQL

REST 2.0 来了,它的新名字叫GRAPHQL

sofa graphql 2 rest api 试用

在 Java 的 GraphQL 查询中添加片段

将 REST API 获取请求转换为 GraphQL

GraphQL 片段的片段