【译】对比GraphQL与REST——两种HTTP API的差异

原文标题:GraphQL vs. REST Two ways to send data over HTTP: What’s the difference?
原文地址:https://dev-blog.apollodata.com/graphql-vs-rest-5d425123e34b
作者:Sashko Stubailo
翻译:Fanny

GraphQL目前被认为是革命性的API工具,因为它可以让客户端在请求中指定希望得到的数据,而不像传统的REST那样只能呆板地在服务端进行预定义。这样它就让前、后端团队的协作变得比以往更加的通畅,从而能够让组织更好地运作。而实际上,GraphQL与REST都是基于HTTP进行数据的请求与接收,而且GraphQL也内置了很多REST模型的元素在里面。

那么在技术层面上,GraphQL和REST这两种API模型到底有什么异同呢?我的观点是,他们归根到底其实没多大区别,只不过GraphQL做了一些小改进,使得开发体验产生了较大的改变。

我会从API的各个组件分别来讨论GraphQL和REST都分别是如何处理的。

资源(Resources)

REST的核心思想就是资源,每个资源都能用一个URL来表示,你能通过一个GET请求访问该URL从而获取该资源。根据当今大多数API的定义,你很有可能会得到一份JSON格式的数据响应,整个过程大概是这样:

GET /books/1
{
  "title": "Black Hole Blues",
  "author": { 
    "firstName": "Janna",
    "lastName": "Levin"
  }
  // ... more fields here
}

注:上面的例子里的"author"也会作为一个单独的资源在其他REST API中被用到

需要注意的是,在REST中,一个资源的种类与你获取它的方式是耦合的,比如上面这个例子中的API就可以称之为“book端点”(book endpoint)。

在这一点上GraphQL就大为不同,因为在GraphQL里这两个概念是完全分离的。比如说在你的schema定义中,你可能会有BookAuthor两个类型(type):

type Book {
  id: ID
  title: String
  published: Date
  price: String
  author: Author
}
type Author {
  id: ID
  firstName: String
  lastName: String
  books: [Book]
}

注意这里我们虽然定义了数据类型,但却不知道该如何获取这些数据。这是REST与GraphQL的一个核心差异:资源的描述信息与其获取方式相分离。

如果要去访问某个特定的book或者author资源,我们需要在schema中创建一个Query类型:

type Query {
  book(id: ID!): Book
  author(id: ID!): Author
}

然后我们就可以像REST那样发送请求了:

GET /graphql?query={ book(id: "1") { title, author { firstName } } }
{
  "title": "Black Hole Blues",
  "author": {
    "firstName": "Janna",
  }
}

虽然都是通过请求某个URL来得到相同的响应,但这里我们已经看到GraphQL与REST的差异之处了。

首先,我们看到GraphQL的URL请求里面指定了我们所需要的资源以及在该资源中我们所关心的字段。另外,我们是主动请求得到与book相关的author数据的,而不是服务端替我们决定的。

最重要的是,在请求中我们不需要关心资源的主键和资源之间的关系定义,我们可以通过除id以外的其他字段来获取到相同的Book资源。

小结

现在我们知道的异同点有:
相同点:都有资源这个概念,而且都能通过ID去获取资源。
相同点:都可以通过HTTP GET方式来获取资源
相同点:都可以使用JSON作为响应格式
差异点:在REST中,你所访问的路径就是该资源的唯一标识(ID);在GraphQL中,该标识与访问方式并不相关
差异点:在REST中,资源的返回结构与返回数量是由服务端决定;在GraphQL,服务端只负责定义哪些资源是可用的,由客户端自己决定需要得到什么资源

如果你已经用过GraphQL和REST,以上这些对你来说肯定相当简单。如果你之前没有用过GraphQL,你可以在到这里来实际体验一下。

路由(URL Route) vs. GraphQL Schema

一个具有可预见性的API才是好的API。因为你通常会把一个API当做程序的一部分来使用,所以你必须要知道它需要接收什么参数并预期能够获取到什么样的结果。

这时候,对API的访问描述信息就显得很重要。通常我们会通过阅读API文档来获取信息,但通过GraphQL的Introspection机制、以及Swagger这样的REST API工具,这些信息就能可以自动获取到。

如今的REST API通常会由一系列的URL端点组成:

GET /books/:id
GET /authors/:id
GET /books/:id/comments
POST /books/:id/comments

你可以把这种API的形态称之为线性结构——因为这就是一个列表嘛。当你要获取数据时,第一个事情就是搞清楚你要访问的是哪个端点。

而在GraphQL中——其实在上一节里你也看到了——可以通过查看GraphQL schema获得相关信息:

type Query {
  book(id: ID!): Book
  author(id: ID!): Author
}
type Mutation {
  addComment(input: AddCommentInput): Comment
}
type Book { ... }
type Author { ... }
type Comment { ... }
input AddCommentInput { ... }

REST会使用类似GET、POST这样的动词去请求相同的URL来表示这到底是一个读操作还是写操作,而GraphQL会使用不同的预定义类型:Mutation和Query。在GraphQL请求中,你可以通过不同的关键字进行不同的操作:

query { ... }
mutation { ... }

如果你想知道更多关于query的用法,请看我之前写的文章“The Anatomy of a GraphQL Query”.

这里的Query类型定义与上面的REST路由是完全契合的,同样表示了数据的访问入口,因此这是GraphQL中最能与REST的URL端点所对应的概念。

如果是对资源的简单查询,GraphQL与REST是类似的,都是通过指定资源的名称以及相关参数来取得,但不同的是,在GraphQL中,你可以根据资源之间的关联关系来发起一个复杂请求,而在REST中你只能定义一些特殊的URL参数来获取到特殊的响应,或者是通过发起多个请求、再自行把响应得到的数据进行组装才行。

小结

REST对数据的描述形式是一连串的URL端点,而GraphQL则是由相互之间有所关联的schema组成。
相同点:REST API的URL端点列表与GraphQL的Query/Mutation中的字段类似,都表示数据的访问入口。
相同点:都能用不同的方式描述一个API请求到底是读操作还是写操作。
差异点:GraphQL让你可以通过一个资源入口访问到关联的其他资源,只要事先在schema中定义好资源之间的关系即可;而REST则提供了多个URL端点来获取相关的资源。
差异点:在GraphQL中,Query类型可以在一个请求的根节点中被访问,除此以外它跟其他类型没有区别,比如你也可以对一个query中的字段添加参数。而在REST中,即使响应结果是嵌套关系,但在请求中并没有嵌套的概念。
差异点:REST使用POST这样的HTTP方法名称来定义写操作,GraphQL则是查询结构中的关键字。

正因为上述的第一个点,人们通常会把Query类型中的字段称为GraphQL中的“端点”或“查询条件”。虽然这是一个合理的解释,但同时也会对其他人造成误导,让人以为Query类型是一个非常特殊的类型。

路由处理器(Route Handlers)vs. 解析器(Resolvers)

想象一下,当你调用一个API的时候,实际上会发生什么事情?嗯,应该是在服务器上面执行了一些代码来处理这个请求,可能是进行了一些计算,可能从数据库中加载了一些数据,也可能是再次调用了一个别的API。虽然总的来说,作为调用方你并不需要知道内部发生了什么事情,不过由于REST和GraphQL都提供了标准的API实现方法,我们可以通过对比来感受一下两者之间的差异。

因为我比较熟悉JavaScript语言,所以在这个章节中我会使用它来做例子,但你也可以使用其他主流编程语言来实现REST或者GraphQL的API。为了突出重点,我会忽略掉一些构建服务用的过程代码。

首先使用Express实现一个hello world:

app.get('/hello', function (req, res) {
  res.send('Hello World!')
})

这里我们得到了一个可以返回“Hello World!”这个字符串的/hello端点。从这个例子我们可以看到一个REST API请求的的生命周期:

  1. 服务器收到请求并提取出HTTP方法名(比如这里就是GET方法)与URL路径
  2. API框架找到提前注册好的、请求路径与请求方法都匹配的代码
  3. 该段代码被执行,并得到相应结果
  4. API框架对结果进行序列化,添加上适当的状态码与响应头后,返回给客户端

GraphQL差不多也是这样工作的,我们来看下这个对应的hello world例子

const resolvers = {
  Query: {
    hello: () => {
      return 'Hello world!';
    },
  },
};

我们看到,这里并没有针对某个URL路径提供函数,而是把Query类型中的hello字段映射到一个函数上了。在GraphQL中这样的函数我们称之为解析器(Resolver)

然后我们就可以这样发起一个查询:

query {
  hello
}

至此,总结一下服务器对一个GraphQL请求的执行过程:

  1. 服务器收到HTTP请求,取出其中的GraphQL查询
  2. 遍历查询语句,调用里面每个字段所对应的Resolver。在这个例子里,只有Query这个类型中的一个字段hello
  3. Resolver函数被执行并返回相应结果
  4. GraphQL框架把结果根据查询语句的要求进行组装

因此我们将会得到如下响应:

{ "hello": "Hello, world!" }

这里有个小技巧:我们其实可以多次调用同一个Resolver:

query {
  hello
  secondHello: hello
}

在这个例子中的生命周期跟上面的是类似的,但因为我们通过别名来两次请求了同一个字段,所以对应Resolver函数hello也会被执行两次。虽然这个例子举得不是很好,不过这里主要想表达的是在一个请求中可以解析多个字段,即使是相同的字段也可以在查询的不同地方被多次访问。

再来看下“嵌套”解析器是怎样的:

{
  Query: {
    author: (root, { id }) => find(authors, { id: id }),
  },
  Author: {
    posts: (author) => filter(posts, { authorId: author.id }),
  },
}

这样的解析器可以处理如下查询请求:

query {
  author(id: 1) {
    firstName
    posts {
      title
    }
  }
}

即使解析器的结构是扁平的,但由于它们被不同的类型所引用,所以你还是可以利用它们来实现嵌套查询。想知道GraphQL如何执行请求,请进一步阅读这篇文章:“GraphQL Explained”

点击这里可以查看完整的例子并体验不同的查询效果


上图形象地说明了使用REST和GraphQL进行多种资源获取的方式的差异

小结

总的来说,REST和GraphQL都提供了很好的API调用方式。如果你对如何构建一个REST API足够熟悉,使用GraphQL来实现同样的API功能对你来说并不是一件难事。但GraphQL的一大优势是让你可以在不需要发起多次请求的情况下调用多个函数来获取资源数据。

相同点:REST的端点与GraphQL查询字段都在服务端调起函数执行。
相同点:REST和GraphQL都使用框架和类库来进行一些通用的网络协议处理。
差异点:一个REST请求对应一个路由处理器(Route Handler),而一个GraphQL的请求可以唤起多个解析器(Resolver)在一次响应中访问多种资源。
差异点:REST需要你自己构建整个请求的响应,而GraphQL的请求响应是由查询方指定结构、并由GraphQL进行构建组装的。

你可以把GraphQL理解为一个可以在一次请求中进行多个端点调用的系统,差不多算是REST的多路复用版。

综上所述

GraphQL里面还有很多东西由于篇幅限制这里并没有涉及,像对象识别、超媒体,以及缓存。这些话题以后有机会我们再来介绍。但我希望你通过本文对GraphQL有一个基本认识,知道它跟REST实际上是有很多概念上的相通。

我个人认为,GraphQL是有一些独特的优势的。特别是使用一系列小的解析器函数来构建一个完整的API这一点,实在是非常酷。这精简了不同场景下形态各异的API数量,并避免让API消费者取到对它来说并没有用的冗余数据。

但在另一方面,GraphQL还并不像REST那样有那么丰富的工具体系。比方说,你就不能像REST那样轻易地对HTTP结果进行缓存。不过目前GraphQL社区正在努力地丰富和完善这些工具和基础建设,就缓存这个例子,其实你也可以通过Apollo ClientRelay这样的工具去缓存GraphQL结果。

如果你对REST和GraphQL有更多的想法,请通过评论来告诉我。

声明:
本译文仅供个人研习、欣赏语言之用,谢绝任何转载及用于任何商业用途。本译文所涉法律后果均由本人承担。本人同意简书平台在接获有关著作权人的通知后,删除文章。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,087评论 18 139
  • REST API 可以让你用任何支持发送 HTTP 请求的设备来与 Parse 进行交互,你可以使用 REST A...
    Caroline嗯哼阅读 1,933评论 0 0
  • API定义规范 本规范设计基于如下使用场景: 请求频率不是非常高:如果产品的使用周期内请求频率非常高,建议使用双通...
    有涯逐无涯阅读 2,441评论 0 6
  • 作者: 一字马胡 转载标志 【2017-11-13】 更新日志 导入 作为一种强大的DSQL,学习GraphQL...
    一字马胡阅读 10,902评论 0 13
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,301评论 6 344