为何说GraphQL能够取代REST API?

为何说GraphQL能够取代REST API?

为何说GraphQL能够取代REST API?
做者|Azat Mardan
译者|薛命灯
在这篇文章中,我将介绍 GraphQL 的优点,以及为何它会变得如此受欢迎。
几年前,我在 DocuSign 带领了一个开发团队,任务是重写一个有数千万个用户在使用的 Web 应用程序。当时尚未能够支持前端的 API,由于从一开始,Web 应用程序就是一个.NET 大单体。西雅图的 API 团队在将拆分单体,并逐步暴露出 RESTful API。这个 API 团队由两名工程师组成,发布周期为一个月,而咱们在旧金山的前端团队每周都会发布新版本。
API 团队的发布周期太长,由于不少(几乎全部)功能都必须进行手动测试,这是能够理解的。它毕竟是一个单体,并且没有适当的自动化测试——若是他们修改了一个地方,不知道在应用程序的其余地方会出现什么问题。
我记得有一次,咱们的前端团队面临为某大会交付新版本的压力,但咱们忘记跟进一个重要的 API 变动,这个变动未被包含在即将发布的 API 版本中。咱们要么一直等待,直到错过截止日期,要么有人愿意放弃优先权,以便让咱们的变动包括在即将发布的版本中。所幸的是,这个变动最后被包含在新版本中,咱们也及时发布了新的前端版本。我真的但愿当时咱们已经使用了 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 请求。
    为何说GraphQL能够取代REST API?
    须要注意的是,在 REST 中使用 POST 进行页面浏览并非很“正规”。
    为何说GraphQL能够取代REST API?
    如下是其中的一些调用:java

  • GET:
    https://inbox.indeed.com/api/getConversationCount
  • GET :
    https://www.indeed.com/rpc/jobdescs
  • GET :
    https://www.indeed.com/rpc/vjslog
  • GET :
    https://www.indeed.com/rpc/preccount
  • POST :
    https://www.indeed.com/rpc/jobalert
  • POST :
    https://www.indeed.com/rpc/count
    在使用 GraphQL 时,上面的这些请求能够被包含在单个查询和单个请求中。
query HomePage {
 getConversationCount(...) {
    ...
 }
 jobdescs(...) {
    ...
 }
 vjslog(...) {
    ...
 }
 preccount(...) {
    …
 }
 jobalert(...) {
    …
 }
 count(...) {
    …
 }
}

响应结果多是这样的:python

{
 "data": {
   "getConversationCount": [
     {
       ...
     }
   ],
   "vjslog": [...],
   "preccount": [...],
     "jobalert": [...],
   "count": {}
 },
 "errors": []
}

一般,单个调用比多个调用更方便、更有效,由于它须要更少的代码和更少的网络开销。来自 PayPal 过程团队的开发体验还证明,不少 UI 工做实际上不是 UI 工做,而是其余任务,例如前端和后端之间的通讯:
“咱们发现,UI 开发人员实际用于构建 UI 的时间不到三分之一,剩下的时间用于肯定在何处以及如何获取数据、过滤 / 映射数据以及编排 API 调用,还有一些用于构建和部署。”
须要注意的是,有实时使多个请求也是有必要的,例如多个单独的请求能够快速且异步独立地获取不一样的数据,若是采用了微服务架构,它们会增长部署灵活性,并且它们的故障点是多个,而不是一个。
此外,若是页面是由多个团队开发的,GraphQL 提供了一个功能,能够将查询分解称为片断。稍后咱们将详细介绍这方面的内容。
从更大的角度来看,GraphQL API 的主要应用场景是 API 网关,在客户端和服务之间提供了一个抽象层。
为何说GraphQL能够取代REST API?
微服务架构很好,但也存在一些问题,GraphQL 能够用来解决这些问题。如下是来自 IBM 在微服务架构中使用 GraphQL 的经验:
“总的来讲,GraphQL 微服务的开发和部署都很是快。他们 5 月份开始开发,7 月份就进入了生产环境。由于他们不须要征得许可,直接开干。他强烈推荐这个方案,比开会讨论好太多了。”
接下来,让咱们逐一讨论 GraphQL 的每个好处。ios

提升开发速度

首先,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 要快得多。
开发速度的提高与开发者体验的提高密切相关。git

提高开发者体验

GraphQL 提供了更好的开发者体验(DX),开发者将花更少的时间思考如何获取数据。在使用 Apollo 时,他们只须要在 UI 中声明数据。数据和 UI 放在一块儿,阅读代码和编写代码都变得更方便。
一般,在开发 UI 时须要在 UI 模板、客户端代码和 UI 样式之间跳转。GraphQL 容许工程师在客户端开发 UI,减小摩擦,由于工程师在添加或修改代码时无需在文件之间切换。若是你熟悉 React,这里有一个很好的比喻:GraphQL 之于数据,就像 React 之于 UI。
下面是一个简单的示例,UI 中直接包含了属性名称 launch.name 和 launch.rocket.name。github

const GET_LAUNCHES = gql`
 query launchList($after: String) {
   launches(after: $after) {
     launches {
       id
       name
       isBooked
       rocket {
         id
         name
       }
     }
   }
 }
`;

export default function Launches() {
 return (
   <Query query={GET_LAUNCHES}>
     {({ data, loading, error }) => {
       if (loading) return <Loading />;
       if (error) return <p>ERROR</p>;

       return (
           <div>
           {data.launches.launches.map(launch => (
               <div
                 key={launch.id}
               >{launch.name}<br/>
               Rocket: {launch.rocket.name}
               </div>
             ))}
         </div>
       );
     }}
   </Query>
 );
};

使用这种方法,能够很是容易地修改或向 UI 或查询(gql)添加新字段。React 组件的可移植性更强了,由于它们描述了所需的全部数据。
如前所述, GraphQL 提供了更好的文档,并且还有一个叫做 GraphiQL 的 IDE:
为何说GraphQL能够取代REST API?
前端工程师很喜欢 GraphiQL,下面引用 Indeed 的一位高级工程师说过的话:
“我认为开发体验中最好的部分是可以使用 GraphiQL。对我来讲,与典型的 API 文档相比,这是一种编写查询更有效的辅助方法”。
GraphQL 的另外一个很棒的功能是片断,由于它容许咱们在更高的组件层面重用查询。
这些功能改善了开发者体验,让开发人员更快乐,更不容易出现 JavaScript 疲劳。web

提高性能

工程师并非惟一从 GraphQL 中受益的人。用户也会从中受益,由于应用程序的性能得到了提高(能够感知到的):
1.减小了有效载荷(客户端只须要必要的东西);
2.多个请求合并为一个请求可减小网络开销;
3.使用工具能够更轻松地实现客户端缓存和后端批处理和后端缓存;
4.预取;
5.更快的 UI 更新。
PayPal 使用 GraphQL 从新设计了他们的结帐流程。下面是来自用户的反馈:
“REST 的原则并无为 Web 和移动应用及其用户的需求考虑,这个在结帐优化交易中体现得尤其明显。用户但愿可以尽快完成结帐,若是应用程序使用了不少原子 REST API,就须要在客户端和服务器之间进行屡次往返以获取数据。咱们的结帐每次往返网络时间至少须要 700 毫秒,这还不包括服务器处理请求的时间。每次往返都会致使渲染变慢,用户体验很差,结算转换率也会下降。”
性能改进中有一项是“多个请求组合成一个请求能够减小网络开销”。对于 HTTP/1 而言,这是很是正确的,由于它没有 HTTP/2 那样的多路复用机制。但尽管 HTTP/2 提供的多路复用机制有助于优化单独的请求,但它对于图遍历(获取相关或嵌套对象)并无实际帮助。让咱们来看一看 REST 和 GraphQL 是如何处理嵌套对象和其余复杂请求的。npm

标准化和简化复杂的 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 请求,这样客户端就可使用字段和嵌套对象进行调用:json

GET /users/?fields=name,address&include=resumes,subscriptions

上面的请求将比使用多个 REST 请求更好,但它不是标准化的,不受客户端库支持,并且这样的代码也更难编写和维护。对于相对复杂的 API,工程师须要在查询中使用本身的查询字符串参数约定,最终获得相似 GraphQL 的东西。既然 GraphQL 已经提供了标准和库,为何还要基于 REST 设计本身的查询约定呢?
将复杂的 REST 端点与如下的 GraphQL 嵌套查询进行对比,嵌套查询使用了更多的过滤条件,例如“只要给我前 X 个对象”和“按时间按升序排列”(能够添加无限制的过滤选项):axios

{
   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 时,咱们能够在查询中保留嵌套对象,对于每一个对象,咱们将精确地得到咱们须要的数据,很少也很多。
响应消息的数据格式反映了请求查询的结构,以下所示:

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

相比复杂的 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:https://github.com/google/rejoiner) 不能用,也能够开发本身的转换器。
GraphQL 提供了比 JSON RESTful API 更强的安全性,主要有两个缘由:强类型 schema(例如数据验证和无 SQL 注入)以及精肯定义客户端所需数据的能力(不会无心泄漏数据)。
静态验证是另外一个优点,能够帮助工程师节省时间,并在进行重构时提高工程师的信心。诸如 eslint-plugin-graphql(https://github.com/apollographql/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.x
const 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 和解析器。解析器能够包含任意代码,但最多见的是如下五个主要类别:

import com.coxautodev.graphql.tools.SchemaParser;
import javax.servlet.annotation.WebServlet;
import graphql.servlet.SimpleGraphQLServlet;

@WebServlet(urlPatterns = "/graphql")
public class GraphQLEndpoint extends SimpleGraphQLServlet {

   public GraphQLEndpoint() {
       super(SchemaParser.newParser()
               .file("schema.graphqls") //parse the schema file created earlier
               .build()
               .makeExecutableSchema());
   }
}

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

@WebServlet(urlPatterns = "/graphql")
public class GraphQLEndpoint extends SimpleGraphQLServlet {

   public GraphQLEndpoint() {
       super(buildSchema());
   }

   private static GraphQLSchema buildSchema() {
       LinkRepository linkRepository = new LinkRepository();
       return SchemaParser.newParser()
               .file("schema.graphqls")
               .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 "Content-Type: application/json" \
 --data '{ "query": "{ posts { title } }" }' \
 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 => res.json())
 .then(res => 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/

相关文章
相关标签/搜索