为什么说GraphQL可以取代REST API?

几年前,我在DocuSign带领了一个开发团队,任务是重写一个有数千万个用户在使用的Web应用程序。当时还没有可以支持前端的API,因为从一开始,Web应用程序就是一个.NET大单体。西雅图的API团队在将拆分单体,并逐步暴露出RESTful API。这个API团队由两名工程师组成,发布周期为一个月,而我们在旧金山的前端团队每周都会发布新版本。

API团队的发布周期太长,因为很多(几乎所有)功能都必须进行手动测试,这是可以理解的。它毕竟是一个单体,而且没有适当的自动化测试——如果他们修改了一个地方,不知道在应用程序的其他地方会出现什么问题。

我记得有一次,我们的前端团队面临为某大会交付新版本的压力,但我们忘记跟进一个重要的API变更,这个变更未被包含在即将发布的API版本中。我们要么一直等待,直到错过截止日期,要么有人愿意放弃优先权,以便让我们的变更包括在即将发布的版本中。所幸的是,这个变更最后被包含在新版本中,我们也及时发布了新的前端版本。我真的希望当时我们已经使用了GraphQL,因为它可以消除对外部团队及其发布周期的重度依赖。

在这篇文章中,我将介绍GraphQL的优势,以及为什么它会变得如此受欢迎。

很多公司已经在内部从RESTful转向了GraphQL API:IBM、Twitter、Walmart Labs、纽约时报、Intuit、Coursera,等等。

其他一些公司不仅是在内部而且还将外部API也转为GraphQL:AWS、Yelp、GitHub、Facebook和Shopify,等等。GitHub甚至打算停止使用REST API,他们的v4版本只使用GraphQL。

GraphQL究竟是一个炒作流行语还是真正会带来一场变革?有趣的是,我之前列出的大多数从GraphQL获益的公司都有以下这些共同点。

  • 他们拥有包括移动端在内的多个客户端;

  • 他们正在转向或者已经采用了微服务架构;

  • 他们的遗留REST API数量暴增,变得十分复杂;

  • 他们希望消除客户端团队对API团队的依赖;

  • 他们注重良好的API文档和开发者体验。

GitHub工程团队表明了他们的动机:

“GraphQL弥合了发布的内容与可以使用的内容之间的差距。我们真的很期待能够同时发布它们。GraphQL代表了API开发的巨大飞跃。类型安全、内省、生成文档和可预测的响应都为我们平台的维护者和消费者带来了好处。我们期待着由GraphQL提供支持的平台进入新时代,也希望你们也这样做!”

GraphQL加速了开发速度,提升了开发者体验,并提供了更好的工具。我并不是说这绝对是这样的,但我会尽力说明GraphQL与REST之间的争论点及其原因。

超级数据聚合器

我是Indeed(世界排名第一的求职网站)的软件工程负责人,所以让我们先来看看Indeed.com的主页和职位查询结果页面。它们分别发出了10和11个XHR请求。

\"\"

需要注意的是,在REST中使用POST进行页面浏览并不是很“正规”。

\"\"

以下是其中的一些调用:

在使用GraphQL时,上面的这些请求可以被包含在单个查询和单个请求中。

query HomePage {  getConversationCount(...) {     ...  }  jobdescs(...) {     ...  }  vjslog(...) {     ...  }  preccount(...) {     …  }  jobalert(...) {     …  }  count(...) {     …  }}

响应结果可能是这样的:

{  \u0026quot;data\u0026quot;: {    \u0026quot;getConversationCount\u0026quot;: [      {        ...      }    ],    \u0026quot;vjslog\u0026quot;: [...],    \u0026quot;preccount\u0026quot;: [...],      \u0026quot;jobalert\u0026quot;: [...],    \u0026quot;count\u0026quot;: {}  },  \u0026quot;errors\u0026quot;: []}

通常,单个调用比多个调用更方便、更有效,因为它需要更少的代码和更少的网络开销。来自PayPal过程团队的开发体验还证实,很多UI工作实际上不是UI工作,而是其他任务,例如前端和后端之间的通信:

“我们发现,UI开发人员实际用于构建UI的时间不到三分之一,剩下的时间用于确定在何处以及如何获取数据、过滤/映射数据以及编排API调用,还有一些用于构建和部署。”

需要注意的是,有实时使多个请求也是有必要的,例如多个单独的请求可以快速且异步独立地获取不同的数据,如果采用了微服务架构,它们会增加部署灵活性,而且它们的故障点是多个,而不是一个。

此外,如果页面是由多个团队开发的,GraphQL提供了一个功能,可以将查询分解称为片段。稍后我们将详细介绍这方面的内容。

从更大的角度来看,GraphQL API的主要应用场景是API网关,在客户端和服务之间提供了一个抽象层。

\"\"

微服务架构很好,但也存在一些问题,GraphQL可以用来解决这些问题。以下是来自IBM在微服务架构中使用GraphQL的经验:

“总的来说,GraphQL微服务的开发和部署都非常快。他们5月份开始开发,7月份就进入了生产环境。因为他们不需要征得许可,直接开干。他强烈推荐这个方案,比开会讨论好太多了。”

接下来,让我们逐一讨论GraphQL的每一个好处。

提高开发速度

首先,GraphQL有助于减少发出的请求数。通过单个调用来获取所需的数据比使用多个请求要容易得多。从工程师的角度来看,这加快了开发速度。后面我会解释更多有关为什么会提升开发速度的原因,但现在我想先说明另一个问题。

后端和客户端团队需要通过密切合作来定义API、测试它们,并做出更改。前端、移动、物联网(例如Alexa)等客户端团队不断迭代功能,并尝试使用新的UX和设计。他们的数据需求经常发生变化,后端团队必须跟上他们的节奏。如果客户端和后端代码由同一团队负责,那么问题就没那么严重了。Indeed的大多数工程团队都是由全栈工程师组成,但并非全部都是这样。对于非全栈团队,客户端团队经常因为依赖了后端团队开发速度受到影响。

当我转到Job Seeker API团队时,移动团队开始我们的开发进度。我们之间有很多关于参数、响应字段和测试的事情需要沟通。

在使用了GraphQL之后,客户端工程师就可以完全控制前端,不需要依赖任何人,因为他们可以告诉后端他们需要什么以及响应结构应该是怎样的。他们使用了GraphQL查询,它们会告诉后端API应该要提供哪些数据。

客户端工程师不需要花时间让后端API团队添加或修改某些内容。GraphQL具有自文档的特点,所以可以节省一些用于查找文档以便了解如何使用API的时间。我相信大多数人曾经在找出确切的请求参数方面浪费了很多时间。GraphQL协议本身及其社区在文档方面为我们提供了一些有用的工具。在某些情况下,可以从模式自动生成文档。其他时候,只需使用GraphiQL Web界面就足以编写一个查询。

来自纽约时报的工程师表示,他们在转到GraphQL和Relay之后,在做出变更时不需要改太多的东西:

“当我们想要更新所有产品的设计时,不再需要修改多个代码库。这就是我们想要的。我们认为Relay和GraphQL是帮助我们实现这个伟大目标的完美工具。”

当一家公司已经拥有大量GraphQL API,然后有人想出了一个新的产品创意,这也是我最喜欢GraphQL的应用场景。使用已有的GraphQL API实现原型比调用各种REST端点(将提供太少或太多的数据)或为新应用程序构建新的REST API要快得多。

开发速度的提升与开发者体验的提升密切相关。

提升开发者体验

GraphQL提供了更好的开发者体验(DX),开发者将花更少的时间思考如何获取数据。在使用Apollo时,他们只需要在UI中声明数据。数据和UI放在一起,阅读代码和编写代码都变得更方便。

通常,在开发UI时需要在UI模板、客户端代码和UI样式之间跳转。GraphQL允许工程师在客户端开发UI,减少摩擦,因为工程师在添加或修改代码时无需在文件之间切换。如果你熟悉React,这里有一个很好的比喻:GraphQL之于数据,就像React之于UI。

下面是一个简单的示例,UI中直接包含了属性名称launch.namelaunch.rocket.name

const GET_LAUNCHES = gql`  query launchList($after: String) {    launches(after: $after) {      launches {        id        name        isBooked        rocket {          id          name        }      }    }  }`;export default function Launches() {  return (    \u0026lt;Query query={GET_LAUNCHES}\u0026gt;      {({ data, loading, error }) =\u0026gt; {        if (loading) return \u0026lt;Loading /\u0026gt;;        if (error) return \u0026lt;p\u0026gt;ERROR\u0026lt;/p\u0026gt;;        return (            \u0026lt;div\u0026gt;            {data.launches.launches.map(launch =\u0026gt; (                \u0026lt;div                  key={launch.id}                \u0026gt;{launch.name}\u0026lt;br/\u0026gt;                Rocket: {launch.rocket.name}                \u0026lt;/div\u0026gt;              ))}          \u0026lt;/div\u0026gt;        );      }}    \u0026lt;/Query\u0026gt;  );};

使用这种方法,可以非常容易地修改或向UI或查询(gql)添加新字段。React组件的可移植性更强了,因为它们描述了所需的所有数据。

如前所述, GraphQL提供了更好的文档,而且还有一个叫作GraphiQL的IDE:

\"\"

前端工程师很喜欢GraphiQL,下面引用Indeed的一位高级工程师说过的话:

“我认为开发体验中最好的部分是能够使用GraphiQL。对我来说,与典型的API文档相比,这是一种编写查询更有效的辅助方法”。

GraphQL的另一个很棒的功能是片段,因为它允许我们在更高的组件层面重用查询。

这些功能改善了开发者体验,让开发人员更快乐,更不容易出现JavaScript疲劳。

提升性能

工程师并不是唯一从GraphQL中受益的人。用户也会从中受益,因为应用程序的性能获得了提升(可以感知到的):

1.减少了有效载荷(客户端只需要必要的东西);

2.多个请求合并为一个请求可减少网络开销;

3.使用工具可以更轻松地实现客户端缓存和后端批处理和后端缓存;
4.预取;
5.更快的UI更新。

PayPal使用GraphQL重新设计了他们的结账流程。下面是来自用户的反馈:

“REST的原则并没有为Web和移动应用及其用户的需求考虑,这个在结账优化交易中体现得尤为明显。用户希望能够尽快完成结账,如果应用程序使用了很多原子REST API,就需要在客户端和服务器之间进行多次往返以获取数据。我们的结账每次往返网络时间至少需要700毫秒,这还不包括服务器处理请求的时间。每次往返都会导致渲染变慢,用户体验不好,结算转换率也会降低。”

性能改进中有一项是“多个请求组合成一个请求可以减少网络开销”。对于HTTP/1而言,这是非常正确的,因为它没有HTTP/2那样的多路复用机制。但尽管HTTP/2提供的多路复用机制有助于优化单独的请求,但它对于图遍历(获取相关或嵌套对象)并没有实际帮助。让我们来看一看REST和GraphQL是如何处理嵌套对象和其他复杂请求的。

标准化和简化复杂的API

通常,客户端会发出复杂的请求来获取有序、排好序、被过滤过的数据或子集(用于分页),或者请求嵌套对象。GraphQL支持嵌套数据和其他难以使用标准REST API资源(也叫端点或路由)实现的查询。

例如,我们假设有三种资源:用户、订阅和简历。工程师需要按顺序进行两次单独的调用(这会降低性能)来获取一个用户简历,首先需要通过调用获取用户资源,拿到简历ID,然后再使用简历ID来获取简历数据。对于订阅来说也是一样的。

1.GET /users/123:响应中包含了简历ID和工作岗位通知订阅的ID清单;
2.GET /resumes/ABC:响应中包含了简历文本——依赖第一个请求;
3.GET /subscriptions/XYZ:响应中包含了工作岗位通知的内容和地址——依赖第一个请求。

上面的示例很糟糕,原因有很多:客户端可能会获得太多数据,并且必须等待相关的请求完成了以后才能继续。此外,客户端需要实现如何获取子资源(例如建立或订阅)和过滤。

想象一下,一个客户端可能只需要第一个订阅的内容和地址以及简历中的当前职位,另一个客户端可能需要所有订阅和整个简历列表。所以,如果使用REST API,对第一个客户端来说有点不划算。

另一个例子:用户表里可能会有用户的名字和姓氏、电子邮件、简历、地址、电话、社会保障号、密码(当然是经过混淆的)和其他私人信息。并非每个客户端都需要所有字段,有些应用程序可能只需要用户电子邮件,所以向这些应用程序发送社会保障号等信息就不太安全。

当然,为每个客户端创建不同的端点也是不可行的,例如/api/v1/users和/api/v1/usersMobile。事实上,各种客户端通常都有不同的数据需求:/api/v1/userPublic、/api/v1/userByName、/api/v1/usersForAdmin,如果这样的话,端点会呈指数级增长。

GraphQL允许客户要求API发送他们想要的字段,这将使后端工作变得更加容易:/api/gql——所有客户端只需要这个端点。

注意:对于REST和GraphQL,后端都需要使用访问控制级别。

或者可以使用旧REST来实现GraphQL的很多功能。但是这样要付出什么代价?后端可以支持复杂的RESTful请求,这样客户端就可以使用字段和嵌套对象进行调用:

GET /users/?fields=name,address\u0026amp;include=resumes,subscriptions

上面的请求将比使用多个REST请求更好,但它不是标准化的,不受客户端库支持,而且这样的代码也更难编写和维护。对于相对复杂的API,工程师需要在查询中使用自己的查询字符串参数约定,最终得到类似GraphQL的东西。既然GraphQL已经提供了标准和库,为什么还要基于REST设计自己的查询约定呢?

将复杂的REST端点与以下的GraphQL嵌套查询进行对比,嵌套查询使用了更多的过滤条件,例如“只要给我前X个对象”和“按时间按升序排列”(可以添加无限制的过滤选项):

{    user (id: 123) {        id        firstName        lastName        address {            city            country            zip        }        resumes (first: 1, orderBy: time_ASC) {            text            title            blob            time          }          subscriptions(first: 10) {            what            where            time        }    }}}

在使用GraphQL时,我们可以在查询中保留嵌套对象,对于每个对象,我们将精确地获得我们需要的数据,不多也不少。

响应消息的数据格式反映了请求查询的结构,如下所示:

{    \u0026quot;data\u0026quot;: {        \u0026quot;user\u0026quot;: {            \u0026quot;id\u0026quot;: 123,            \u0026quot;firstName\u0026quot;: \u0026quot;Azat\u0026quot;,            \u0026quot;lastName\u0026quot;: \u0026quot;Mardan\u0026quot;,            \u0026quot;address\u0026quot;: {                \u0026quot;city\u0026quot;: \u0026quot;San Francisco\u0026quot;,                \u0026quot;country\u0026quot;: \u0026quot;US\u0026quot;,                \u0026quot;zip\u0026quot;: \u0026quot;94105\u0026quot;            },            \u0026quot;resumes\u0026quot; [                  {                    \u0026quot;text\u0026quot;: \u0026quot;some text here...\u0026quot;,                    \u0026quot;title\u0026quot;: \u0026quot;My Resume\u0026quot;,                    \u0026quot;blob\u0026quot;: \u0026quot;\u0026lt;BLOB\u0026gt;\u0026quot;,                    \u0026quot;time\u0026quot;: \u0026quot;2018-11-13T21:23:16.000Z\u0026quot;                  },            ],              \u0026quot;subscriptions\u0026quot;: [ ]        },    \u0026quot;errors\u0026quot;: []    }

相比复杂的REST端点,使用GraphQL的另一个好处是提高了安全性。这是因为URL经常会被记录下来,而RESTful GET端点依赖于查询字符串(是URL的一部分)。这可能会暴露敏感数据,所以RESTful GET请求的安全性低于GraphQL的POST请求。我打赌这就是为什么Indeed主页会使用POST发出“阅读”页面请求。

使用GraphQL可有更容易地实现分页等关键功能,这要归功于查询以及BaaS提供商提供的标准,以及后端的实现和客户端库使用的标准。

改进的安全性、强类型和验证

GraphQL的schema与语言无关。对前面的示例进行扩展,我们可以在schema中定义Address类型:

type Address {    city: String!    country: String!    zip: Int}

String和Int是标量类型,!表示字段不可为空。

schema验证是GraphQL规范的一部分,因此像这样的查询将返回错误,因为name和phone不是Address对象的字段:

{    user (id: 123) {        address {            name            phone        }    }}

我们可以使用我们的类型构建复杂的GraphQL schema。例如,用户类型可能会使用我们的地址、简历和订阅类型,如下所示:

type User {    id: ID!    firstName: String!    lastName: String!    address: Address!    resumes: [Resume]     subscriptions: [Subscription]}

Indeed的大量对象和类型都是使用ProtoBuf定义的。类型化数据并不是什么新鲜事物,而且类型数据的好处也是众所周知。与发明新的JSON类型标准相比,GraphQL的优点在于已经存在可以从ProtoBuf自动换换到GraphQL的库。即使其中一个库(rejoiner)不能用,也可以开发自己的转换器。

GraphQL提供了比JSON RESTful API更强的安全性,主要有两个原因:强类型schema(例如数据验证和无SQL注入)以及精确定义客户端所需数据的能力(不会无意泄漏数据)。

静态验证是另一个优势,可以帮助工程师节省时间,并在进行重构时提升工程师的信心。诸如eslint-plugin-graphql之类的工具可以让工程师知道后端发生的变化,并让后端工程师确保不会破坏客户端代码。

保持前端和后端之间的契约是非常重要的。在使用REST API时,我们要小心不要破坏了客户端代码,因为客户端无法控制响应消息。相反,GraphQL为客户端提供了控制,GraphQL可以频繁更新,而不会因为引入了新类型造成重大变更。因为使用了schema,所以GraphQL是一种无版本的API。

GraphQL的实现

在选择实现GraphQL API的平台时,Node是一个候选项,因为最初GraphQL用于Web应用程序和前端,而Node是开发Web应用程序的首选,因为它是基于JavaScript的。使用Node可以非常容易地实现GraphQL(假设提供了schema)。事实上,使用Express或Koa来实现只需要几行代码:

const Koa = require('koa');const Router = require('koa-router'); // koa-router@7.xconst graphqlHTTP = require('koa-graphql');const app = new Koa();const router = new Router();router.all('/graphql', graphqlHTTP({  schema: schema,  graphiql: true}));app.use(router.routes()).use(router.allowedMethods());

schema是使用npm的graphql中的类型来定义的。Query和Mutation是特殊的schema类型。

GraphQL API的大部分实现都在于schema和解析器。解析器可以包含任意代码,但最常见的是以下五个主要类别:

  • 调用Thrift、gRPC或其他RPC服务;

  • 调用HTTP REST API(当优先事项不是重写现有REST API时);

  • 直接调用数据存储;

  • 调用其他GraphQL schema查询或服务;

  • 调用外部API。

这里有一个示例

Node很棒,但在Indeed,我们主要使用Java。包括Java在内的很多语言都支持GraphQL,例如https://github.com/graphql-gohttps://github.com/graphql-python

由于Indeed主要使用了Java,因此这里给出一个使用graphql-java的Java GraphQL示例,完整代码位于这里。它定义了/graphql端点:

import com.coxautodev.graphql.tools.SchemaParser;import javax.servlet.annotation.WebServlet;import graphql.servlet.SimpleGraphQLServlet;@WebServlet(urlPatterns = \u0026quot;/graphql\u0026quot;)public class GraphQLEndpoint extends SimpleGraphQLServlet {    public GraphQLEndpoint() {        super(SchemaParser.newParser()                .file(\u0026quot;schema.graphqls\u0026quot;) //parse the schema file created earlier                .build()                .makeExecutableSchema());    }}

GraphQL的schema使用POJO来定义。GraphQL端点类使用了LinkRepository POJO。解析器包含了操作的(例如获取链接)实际代码:

@WebServlet(urlPatterns = \u0026quot;/graphql\u0026quot;)public class GraphQLEndpoint extends SimpleGraphQLServlet {    public GraphQLEndpoint() {        super(buildSchema());    }    private static GraphQLSchema buildSchema() {        LinkRepository linkRepository = new LinkRepository();        return SchemaParser.newParser()                .file(\u0026quot;schema.graphqls\u0026quot;)                .resolvers(new Query(linkRepository))                .build()                .makeExecutableSchema();    }}

在很多情况下,GraphQL的schema可以从其他类型的schema自动生成,例如gRPC、Boxcar、ProtoBuf或ORM/ODM。

GraphQL不一定需要客户端。一个简单的GraphQL请求就是一个常规的POST HTTP请求,其中包含了查询内容。我们可以使用任意的HTTP代理库(如CURL、axios、fetch、superagent等)来生成请求。例如,在终端中使用curl发送请求:

curl \\  -X POST \\  -H \u0026quot;Content-Type: application/json\u0026quot; \\  --data '{ \u0026quot;query\u0026quot;: \u0026quot;{ posts { title } }\u0026quot; }' \\  https://1jzxrj179.lp.gql.zone/graphql

以下代码可以在任意一个现代浏览器(为了避免CORS,请访问launchpad.graphql.com)中运行。

fetch('https://1jzxrj179.lp.gql.zone/graphql', {  method: 'POST',  headers: { 'Content-Type': 'application/json' },  body: JSON.stringify({ query: '{ posts { title } }' }),})  .then(res =\u0026gt; res.json())  .then(res =\u0026gt; console.log(res.data));

虽然构建GraphQL请求很容易,但是还需要实现很多其他东西,比如缓存,因为缓存可以极大地改善用户体验。构建客户端缓存不是那么容易,所幸的是,Apollo和Relay Modern等提供了开箱即用的客户端缓存。

什么时候不该使用GraphQL?

当然,完美的解决方案是不存在的(尽管GraphQL接近完美),还有一些问题需要注意,例如:

1.它有单点故障吗?

2.它可以扩展吗?

3.谁在使用GraphQL?

最后,以下列出了我们自己的有关GraphQL可能不是一个好选择的主要原因:

  • 当客户端的需求很简单时:如果你的API很简单,例如/users/resumes/123,那么GraphQL就显得有点重了;

  • 为了加快加载速度使用了异步资源加载;

  • 在开发新产品时使用新的API,而不是基于已有的API;

  • 不打算向公众公开API;

  • 不需要更改UI和其他客户端;

  • 产品开发不活跃;

  • 使用了其他一些JSON schema或序列化格式。

总结

GraphQL是一种协议和一种查询语言。GraphQL API可以直接访问数据存储,但在大多数情况下,GraphQL API是一个数据聚合器和一个抽象层,一个可以提升开发速度、减少维护工作并让开发人员更快乐的层。因此,GraphQL比公共API更有意义。很多公司开始采用GraphQL。IBM、PayPal和GitHub声称在使用GraphQL方面取得了巨大的成功。如果GraphQL很有前途,我们现在是否可以停止构建过时且笨重的REST API,并拥抱GraphQL?

英文原文:https://webapplog.com/graphql/

更多内容,请关注前端之巅(ID:frontshow)

\"\"

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值