GraphQL,高效、结构化与系统化的 REST 替代方案

1,502 阅读6分钟
原文链接: www.oschina.net

在提及web API的时候,我们大多都会想到REST(表述性状态传递,Representational State Transfer)。你发送请求到一个特定请求的URL,然后你会收到结果,就像是HTML,XML,JSON,明文,PDF,JPEG…这些应用可以理解的任何格式。

Facebook的web API系统,GraphQL,提供了一种新的定义API的方式。开发人员使用强类型查询语言来定义请求和响应,这可以让一个应用精确地指定它需要从API中获取哪些数据。因此GraphQL的目的是提供一个更高效,更结构化,更系统的方式来替代REST。

本文我们会展示GraphQL与REST的不同之处,这些不同是如何影响API设计的,以及在从服务器获取数据方面GraphQL为什么是比REST更好的选择。

GraphQL 对比 REST

使用REST,您一般会通过一个特制的URL来提交您的请求,并将每种请求发送到不同的终点——例如:/movie/2120 或者 /director/5130。

使用GraphQL,您可以在一个类似JSON的查询中为您正在搜索的数据提交一个陈述性请求,并且所有的请求都会发往同一个终点。用于请求的schema决定了您将得到的数据返回。这是一个标准化的、自我描述的方法来请求所需的具体数据,并且只是所需的数据。

不同的请求类型使用不同的schema,替代了使用不同终点的URL格式,使得查询机制更加灵活。虽然像Swagger这样的很多REST API也符合通用规范,没有规则说REST APT必须通过Swagger生成。GraphQL默认为API提供了一个形式定义。

从某种意义上来说,GraphQL有点像SQL(结构化查询语言)。使用SQL提供的数据源,您可以将所有数据请求连接到一个公共终点,并且请求的格式决定了您将得到什么样的记录返回。虽然SQL有许多不同的实现,但是SQL查询语法在这些实现之间保持高度一致。

GraphQL 查询

GraphQL使用一个schema或者一个数据定义来描述在查询和响应中如何组织要检索的数据。任何使用ORM(对象关系映射器)的人都应该熟悉GraphQL的数据schema定义。

type Movie {
    id: ID
    title: String
    released: Date
    director: Director
}

type Director {
    id: ID
    name: String
    movies: [Movie]
}

您会注意到在schema中的每个元素都有一个类型定义。GraphQL有自己的查询类型系统,用于验证传入的schema并返回与定义格式一致的数据。

提交给GraphQL的查询类似通过一个schema定义:

type Query {
    movie(id: ID!): Movie
    director(id: ID): Director
}

我们这有一个查询需要最多两个参数,movie(通过它的ID号)和director(也通过ID)。

类型旁边的“!”表示这是查询中一个强制元素。换句话说,您必须通过ID查询movie,director是可选项。强制元素也可以使用在数据schema中。

这是使用上面的schema来定义的查询格式的一种可行方法:

query GetMovieByID ($id: ID!) {
    movie(id: $id) {
        name
    }
}

这个查询使用了一个必需的单独定义变量($id),通过ID号码查找movie并返回它的name。需要注意的是,GraphQL查询可以通过字段内嵌套的数组来返回相关对象和字段,而不是只能返回单个字段。

像这样的查询变量在查询的一个单独的部分传递,使用如下格式:

{
    “id”: 23
}

GraphQL 类型

GraphQL的查询类型系统说明了许多常见的标量类型,像strings和integers。大多数查询将通过他们重写。但是这些类型系统也包括了几种用于更复杂查询工作的高级类型。

  • interface类型能用来创建一个预定义字段集合的抽象类型,其他的类型可以实现和复用。

  • union类型允许从单一类型的查询中穿过多种类型返回不同类型的结果。

  • input类型能用来传递上述类型的整个对象作为查询参数,前提是这些对象是用可验证的常用标量类型创建的。

如果您使用interface或者union,您将需要使用内联片段指令来返回数据,这些数据基于这些对象类型可指定的条件。

可以在查询中返回的另一种类型是边缘类型,在可选边缘字段返回。边缘包含节点——实质上是记录——和游标,他们是编码字符串,提供关于如何从该对象向前或者向后翻页的上下文信息。

{
    movie {
        name
        actors (first:5) {
            edges {
                cursor
                node {
                    name
                }
            }
        }
    }
}

这个例子中,一个movie节点将返回movie的name和actors的name。对于movie中的每个actor,我们都会收到一个节点,这个节点包含actor的name和允许浏览actor的“邻居”的光标。

GraphQL 分布

处理任何数据源时的常见情况是通过光标请求页面中的数据。GraphQL提供了多种分页的方式。

当你请求记录时,你不仅可以指定要请求的记录数和起始偏移量,还可以指定如何请求连续页面。上一节中的示例代码仅返回与给定影片相关联的前五位演员 - 和括号中的first:5参数所表示的一样。

actors之后的first:子句可以紧跟其他描述如何获取后续项的关键字。offset:可用于简单偏移量,但添加或删除数据时偏移量可能会被丢弃。

要获得最健壮的分页,你需要使用可以与请求的对象一起传递的光标,通过使用上述的edge类型。这允许你创建在分页之间插入或删除数据时不会被打断的分页机制 - 例如,通过将对象的唯一ID用作其他计算的起始键索引。

GraphQL的mutations

使用REST API,您可以通过使用POST、PATCH和其他HTTP动作提交请求来改变服务器端的数据。使用GraphGL,您可以使用特定的查询schema做出改变,一个mutation查询——再一次,与SQL使用UPDATE或DELETE同样的方式查询。

为了改变数据,需要您使用一个叫做mutation的schema来提交GraphQL查询。

mutation CreateMovie ($title: String!, $released: Date!) {
    createMovie (title: $title, released: $released){
        id
        title
        released
    }
}

[submitted data]

{
    “title”: “Seven Samurai”
    “released”: “1950”
}

包括mutation查询在内的所有的查询都能返回数据。在例子中,createMovie后面的大括号中的字段列表指定了在使用该请求创建新纪录之后,从服务器返回的内容。这种情况下,id字段的值将由数据库创建;其他字段的值将在查询中提交。

需要记住的另外一件事情,用于返回数据的查询和数据类型在设计上不同于用于请求数据的查询和数据类型。Mutation查询需要校验传入的数据,所以这些查询使用的类型意味着服务该功能。同样,返回查询对象中使用的字段用于显示而不是验证。如果您从查询中得到了一个GraphQL对象返回,那它可能带有循环引用或者其他问题的字段,使得它不能用于查询参数。

为什么使用GraphQL

GraphQL查询具有明确声明特性是选择GraphQL而不选择REST的一个关键原因。查询和返回数据有个正式定义看起来应该是有好处的,不只是为了与API和实现方面保持一致。

正如Phil Sturgeon在他关于GraphQL与REST的考察报告中所指出的那样,GraphQL字段结构能更容易的使用更细化的版本来查询。因为会被弃用或者随着时间流逝是特定字段,而不是整个API版本。GraphQL还可以使用批量版本管理方法; 关键是在推出变更时您不会被迫这么做。

Apollo GraphQL的工程经理Sashko Stubailo说,GraphQL API的开源工具制造商指出了GraphQL方法的另一个优点:自文档化。Stubailo写到:“每个可能的查询、对象和字段都具有名称、描述和类型信息,可以用标准方法从服务器查询。”

GraphQL的自文档性质提供了一种“自我反省”,它意味着您可以使用查询返回与它自己相关的信息。用这个方法,使用GraphQL查询来工作的软件可以不用强制处理特定的字段集合;它能自动推断出这些字段。

也就是说,GraphQL比较新,REST/Swagger比较老的这个事实不应该成为支持前者的原因。正如《每日API设计》的作者Arnaud Lauret在讨论两个标准时所说的:“一个GraphQL API就像一个REST API一样,必须是为了达成某个目的而创建,并且从是由外而内的设计而不是由内而外的。”

 loading...正在加载...

Serdar Yegulalp:Serdar Yegulalp是InfoWorld的资深作家,专注于机器学习、容器化、devops、Python生态系统和定期评审。