作者,罗泽轩
上个月马斯克评论 Twitter App 滥用 RPC 后,与一些 Twitter 的技术主管产生了矛盾 —— 婉言马斯克不懂技术。那这个马斯克都不懂的 GraphQL 到底是什么?
什么是 GraphQL?它有多风行?
GraphQL 是一套由 Facebook 在 2015 年公布的一套面向 API 的查问操作语言。相比于其余的 API 设计形式,GraphQL 容许客户端依据当时约定的数据结构组建查问语句,由服务端解析这一语句并只返回所需的内容。这么一来,GraphQL 在提供丰富性和灵活性的同时,防止了冗余数据带来的性能损耗。
GraphQL 的这一个性,让它在须要跟许多简单数据对象打交道的利用场景里大行其道,成为该环境下的不二之选。
2018 年 GraphQL 实现了标准的制订工作,并推出了稳固版本。同年,Facebook 将 GraphQL 我的项目募捐给了 Linux 基金会上司的 GraphQL 基金会。自那当前,GraphQL 曾经在许许多多的开源我的项目和商业机构中落地。到目前为止,市面上曾经有了多个 GraphQL 的支流客户端实现。而服务端的实现遍布各大服务端编程语言,甚至连一些小众编程语言如 D 和 R 都有对应的实现。
GraphQL 的一些实在场景和挑战
最为出名的采纳 GraphQL 的例子,莫过于 GitHub 的 GraphQL API 了。
在拥抱 GraphQL 之前,GitHub 提供了 REST API 来裸露千千万万托管我的项目所产生的丰盛数据。GitHub 的 REST API 是如此的胜利,以致于它成为了人们设计 REST API 时竞相模拟的榜样。
然而随着数据对象的变多和对象内字段的变大,REST API 开始暴露出越来越多的弊病。在服务端,因为每次调用都会产生大量的数据,GitHub 为了降低成本不得不对调用频率设置严格的限度。
而在开发者这边,他们则不得不与这一限度做奋斗。因为尽管单次调用会返回繁多的数据,然而绝大部分都是无用的。开发者要想获取某一特定的信息,往往须要发动多个查问,而后编写许多胶水代码把查问后果中有意义的数据拼接成所需的内容。在这一过程中,他们还不得不带上“调用次数”的镣铐。
所以 GraphQL 的呈现,立即就让 GitHub 皈依了。GitHub 成为了 GraphQL 的使者保罗,为万千开发者传递福音。目前 GraphQL API 曾经是 GitHub API 的首选。从第一次发表对 GraphQL 的反对之后,GitHub 每一年都会发几篇对于 GraphQL 的文章。为了让开发者可能迁徙到 GraphQL 上来,GitHub 专门写了个交互式查问利用,开发者能够通过这个利用学习怎么编写 GraphQL。
然而 GraphQL 并非灵丹妙药。就在最近,GitHub 废除本人 package API 的 GraphQL 实现。许多人也开始热议 GraphQL 的一些毛病。
GraphQL 的许多问题源自于它跟 HTTP 规范的构造差异较大,没方法简略地将 GraphQL 的一些概念映射到诸如 HTTP path/header 这样的构造中。把 GraphQL 当作一般的 HTTP API 来解决,须要额定的开发工作。如此一来,开发者如果要治理本人的 GraphQL API,就必须采纳反对 GraphQL 的 API 网关才行。
APISIX 当初对 GraphQL 的反对
Apache APISIX 是一个动静、实时、高性能的 API 网关,提供负载平衡、动静上游、灰度公布、精细化路由、限流限速、服务降级、服务熔断、身份认证、可观测性等数百项性能。作为 Apache 的顶级我的项目,APISIX 始终致力于周边生态的扩大与跟进。
APISIX 目前反对通过 GraphQL 的一些属性进行动静路由。通过该能力,咱们能够只承受特定的 GraphQL 申请,或者让不同的 GraphQL 转发到不同的上游。
以上面的 GraphQL 语句为例:
query getRepo {
owner {name}
repo {created}
}
APISIX 会提取 GraphQL 以下三个属性,用在路由当中:
- graphql_operation
- graphql_name
- graphql_root_fields
在下面的 GraphQL 语句中:
graphql_operation
对应query
graphql_name
对应getRepo
graphql_root_fields
对应["owner", "repo"]
让咱们来创立一个路由,展现下 APISIX 对 GraphQL 的精细化路由能力。
curl http://127.0.0.1:9180/apisix/admin/routes/1 \
-H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -i -d '{"methods": ["POST"],"uri":"/graphql","vars": [["graphql_operation", "==", "query"],
["graphql_name", "==", "getRepo"],
["graphql_root_fields", "has", "owner"]
],
"upstream": {
"type": "roundrobin",
"nodes": {"127.0.0.1:2022": 1}
}
}'
接下来应用带有 GraphQL 语句的申请去拜访:
curl -i -H 'content-type: application/graphql' \
-X POST http://127.0.0.1:9080/graphql -d '
query getRepo {
owner {name}
repo {created}
}'
HTTP/1.1 200 OK
...
咱们能够看到申请达到了上游,这是因为查问语句匹配了全副三个条件。反之,如果咱们应用不匹配的语句来拜访,比方不蕴含 owner
字段:
curl -i -H 'content-type: application/graphql' \
-X POST http://127.0.0.1:9080/graphql -d '
query getRepo {
repo {created}
}'
HTTP/1.1 404 Not Found
...
则不会匹配对应的路由规定。
接下来,咱们能够另外创立一个路由,让不蕴含 owner
字段的语句路由到别的上游:
curl http://127.0.0.1:9180/apisix/admin/routes/2 \
-H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -i -d '{"methods": ["POST"],"uri":"/graphql","vars": [["graphql_operation", "==", "query"],
["graphql_name", "==", "getRepo"],
["graphql_root_fields", "!", "has", "owner"]
],
"upstream": {
"type": "roundrobin",
"nodes": {"192.168.0.1:2022": 1}
}
}'curl -i -H'content-type: application/graphql' \
-X POST http://127.0.0.1:9080/graphql -d '
query getRepo {
repo {created}
}'
HTTP/1.1 200 OK
..
瞻望 APISIX 将来对 GraphQL 的反对
除了动静路由之外,APISIX 在将来也可能会依据 GraphQL 的具体字段推出更多的操作。比如说,GitHub 的 GraphQL API 有专门一套针对限流的计算公式,咱们也能够利用相似的规定来把单个 GraphQL 申请转成相应的“虚构调用”次数,来实现 GraphQL 专属的限流工作。
当然,咱们也能够换个思路解决问题。即利用本身还是提供 REST API,由网关在最外层把 GraphQL 申请转成 REST 申请,把 REST 响应转成 GraphQL 响应。这种形式提供的 GraphQL API 无需开发专门的插件,就能够实现诸如 RBAC、限流、缓存等性能。
从插件角度来看,它就是个平平无奇的 REST API。从技术角度上讲,这个思路并不难实现。毕竟在 2022 年的当初,REST API 也会提供 OpenAPI spec 来作为 schema,无非是 GraphQL schema 和 OpenAPI schema 之间的互转,外加 GraphQL 特有的字段筛选罢了(当然,我必须抵赖,我并没有亲自实际过,或者在一些细节上存在尚待克服的挑战)。
仔细的读者会发现,这种形式转换得来的 GraphQL API,每次只能操作一个模型,显然无奈满足 GraphQL 灵活性的要求,无非是披着 GraphQL 外衣的 REST API。 且慢,我还没有把话说完呢!GraphQL 有一个叫 schema stitch 的概念,容许实现者把多个 schema 组合在一起。
举个例子,当初有两个 API。一个叫 GetEvent,另一个叫 GetLocation。他们返回的类型别离是 Event 和 Location。
type Event {
id: string
location_id: string
}
type Location {
id: string
city: string
}
type Query {GetEvent(id: string): Event
GetLocation(id: string): Location
}
咱们能够加一个配置,由这两个 API 组合成新的 API 叫 GetEventWithLocation
。新的 API 是这样的:
type EventWithLocation {
id: string
location: Location
}
type Query {GetEventWithLocation(id: string): EventWithLocation
}
整体 schema stitch 的过程都由网关来实现。在下面的例子中,网关会把 API 拆分成两个,先调用 GetEvent 失去 location_id
,再调用 GetLocation 失去组合后的数据。
总而言之,通过 REST 转 GraphQL,每个 REST API 能够变成对应的 GraphQL 模型;再借助 schema stitch,能够把多个模型组合成一个 GraphQL API。
这样一来,咱们就能在现有的 REST API 上构建起丰盛灵便的 GraphQL API,且在 REST API 的粒度上实现具体的插件治理。这一设计顺带解决了局部 API 编排的问题。就像下面的例子中,咱们把一个 API 的输入(Event.location_id)作为另一个 API 的输出(Location.id)。