GraphQL正在超越REST

Posted 21CTO

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了GraphQL正在超越REST相关的知识,希望对你有一定的参考价值。



GraphQL是一门新的领域驱动查询语言,在其设计中可以使用现有数据库实体或模型。


GraphQL语言由Facebook创建于2015年下半年,旋即开源。与此同时,Facebook将其交由GraphQL社区维护,以保证开放、公正性。


开发者要开始用GraphQL,需要熟悉一些新规范,因为它并非一个简单实现。


如果你熟悉其他API或查询语言,学习起来也并不会感到太困难,也许几个小时就能了解的差不多。此外,GraphQL规范也提供了详细帮助,为我们展示了如何操作,比如查询,定义新模式以及应该遵循的技术最佳实践。

GraphQL 运行原理


GraphQL允许从服务中一次性检索数据。如何做到这一点?GraphQL公布了一个端口,给出了包含的模型和操作模式。可以通过向/graphql 发送操作名称和有效变量的HTTP请求就可以。


GraphQL支持GET和POST两种方法。在GET的情况下,需要使用查询参数(如?query={operationName{field}})。另一方面,也可以使用JSON标准提交POST请求。

来看如下代码:

{ "query": "...", "operationName": "...",}


说明,URL字符只能是ASCII字符集,不能包含有其它字符,如中文。

GraphQL相关操作


query 查询

query 用于检索数据,可以用它在服务中执行读取操作。样例代码如下:

query { findHotelById(id: 2) { name address room { type } }}

findHotelById 即是查询操作,并且调用操作中的全部内容,它使用了参数(id: 2)返回  Hotel 的 id 为2的记录

query 还支持动态参数,在操作中还可将它们作为JSON传递的方法。如下代码:

query MyQuery($hotelId:ID) { findHotelById(id:$hotelId) { name room { type occupants } }}
{“hotelId”:“1”}

可以简写语法省略query 和  query name关键字,但最好使用完整语句,一方面代码更具可读性,另外也便于调试和识别不同请求。

创建一个较复杂的查询,可以使Fragments(代码段Fragments是包含一组字段的可重用代码块。请见如下代码:

query MyQuery { firstHotel: findHotelById(id:1) { ...compareHotels } secondHotel: findHotelById(id:3) { ...compareHotels }}fragment compareHotels on Hotel { name room { type occupants }}

说明:使用三个小圆点加片段名来调用一个代码片段。

突变


突变(mutation,并非生物学上的基因突变之意,本文为直译)用来操作和改变数据,即触发数据库中的插入、更新或删除等操作。


创建突变使用mutation 关键字。请看如下代码:

mutation { newHotel(name:"test 1", address: "test 1"){ id }}

我可以看到,就像query一样,mutation返回一个对象类型,还可以嵌套请求字段。在上面的示例中,创建一个new Hotel 并返回已创建的  hotel内容和id,此为自动生成。

除了语法外,查询和突变在一件事上有着本质的区别:查询可以并行执行,而突变则是顺序执行。

subscriptions(订阅)


subscriptions是一种将从服务器传输数据到正在侦听的客户端的方法。


与query一样,subscriptions可以请求一组字段,但它不是使用无状态的HTTP请求,而是使用WebSocket连接来获取来自服务器的数据流,以便在服务器上发生更改时,结果被发送到客户端。


换句话说,当客户端运行突变  mutation 时,就会触发subscriptions操作。

“执行subscriptions会在服务器上创建一个持久方法,将基础源数据流映射到返回的响应流中。”

/subscriptions默认情况下使用 WebSocket 端口。以下代码是javascript客户端使用订阅的示例:

function subscribeToHotels() { let socket = new WebSocket("ws://localhost:8080/subscriptions"); socket.onopen = function () { let query = ` subscription MySubscription { getNewHotel { name address creationDate } } `; let graphqlRequest = { query: query, variables: {} }; socket.send(JSON.stringify(graphqlRequest)); }; socket.onmessage = function (event) { // handle response }}

架构文件

架构文件是一个扩展名为.grapqhl的文本文件。在此文件中定义了操作与模型,GraphQL提供了一种模式描述语言,包括标量类型,标记和其它关键字来构建较复杂的模式。

内置标量变量类型包括:

GRAPHQL变量类型 序列化后值
int 有符号32位整数
float 有符号双精度浮点值
string UTF-8字符序列
boolean True或False
ID 字符串


类型标签:

GRAPHQL标签
<type>! 非空的
[<type>] 名单
[<type>!] 非空元素列表
[<type>]! 不是空列表
[<type>!]! 非Null元素的Null列表


下面,来看一个代码实例 :

type Hotel { id: ID! # 酒店名 name: String! # 酒店地址 address: String! # 酒店创建的日期 creationDate: String! # 可以预约的酒店房间 room: [Room]!}


和很多编程语言一样,使用#号添加注释,用来记录和说明模式。

 Queries mutations,subscriptions 可以这样来创建。如下代码:

#应用程序查询Type Query { #检索所有酒店 findAllHotels:[Hotel] #检索带有ID的酒店(例如:'1,4,12' findHotelById(id:ID):Hotel #酒店数量 countHotels:Int #查找所有付款方式 findAllPayments:[Payment]}

只需要使用可选参数定义操作的名称,然后返回返回的类型。

 Query Mutation和  Subscription 关键字被用作应用程序中每个类型的操作的根。使用 extend 关键字很容易添加其它操作。例如: 

extend type Query { foos:[ Foo ] !}

自定义类型也可以扩展,可以避免大型字段列表。

还有像其它的更高级元素:interfaceunionenumscalar

创建GraphQL Spring Boot 服务器


首先我们需要:

  1. 定义GraphQL架构

  2. 确定如何获取查询的数据

定义GraphQL架构


这里有一个例子:

type hotel{ id:ID! #酒店名称 name:String #酒店地址 address:String #酒店注册表创建日期 creationDate:String #特定酒店的房间清单 room:[room]!}type room{ id:ID! #房间描述 typeString #几人间 owner:Int!}input payment{ id:ID! #付款方式名称 name:String}#应用程序的根查询type Query { #检索所有酒店 findAllHotels:[hotel] #检索带有ID的酒店(例如:'1,4,12' findHotelById(id:ID):hotel #酒店数量 countHotels:Int #查找所有付款方式 findAllPayments:[payment]}#应用程序的Root Mutationtype Mutation { #创建新酒店 newHotel(name:String!,address:String!):hotel #创建新的付款方式 newPayment(name:String!):payment}type sucribtion{ getNewHotel:hotel!}

有一个Java GraphQL工具库(https://github.com/graphql-java-kickstart/graphql-java-tools#why-graphql-java-tools)可以将以上模式解析为用于创建GraphQLSchema的配置类。

<dependency><groupId>com.graphql-java</groupId><artifactId> graphql-java-tools </artifactId ><version> 5.2.4 </version></dependency>

构建Spring Boot应用程序


这两个库需要在Spring中开始使用GraphQL,基本上就是设置servlet。

<properties ><kotlin.version > 1.3.10 </kotlin.version></properties><dependencies> ...<dependency><groupId> com.graphql-java </groupId><artifactId> graphql-java </artifactId><version> 11.0 </version></dependency><dependency><groupId> com.graphql-java </groupId><artifactId> graphql-spring-boot-starter </artifactId><version> 5.0.2 </version></dependency></dependencies>

第一个是GraphQL Java实现,而第二个是用servlet的URI:/graphql

graphl-java-tools依赖于kotlin.version Kotlin 1.3.10,Spring Boot Starter目前使用Kotlin的1.2版本覆盖它。Spring Boot团队表示,在Spring Boot 2.2中,Kotlin版本将升级到1.3。

本示例使用MongoDB存储库,希望使用React 通过GraphQL订阅实时获取更新。


public interface HotelRepository extends ReactiveCrudRepository<Hotel, String> { @Tailable Flux<Hotel> findWithTailableCursorBy();}

 @Tailable 需要在MongoDB中查询有限集合。即使在收集结束后,有限集合也会使游标保持打开状态。

下一步是为架构中定义的每个对象创建解析器。查询变异订阅是GraphQL的根对象,需要分别实现GraphQLQueryResolver,GraphQLMutationResolver和GraphQLSubscriptionResolver,以便graphql-java-tools能够使用解析器中创建的方法映射GraphQL操作。以下为代码例子:

@Component@RequiredArgsConstructorpublic class Query implements GraphQLQueryResolver { private final HotelRepository hotelRepository; private final PaymentRepository paymentRepository; public Iterable<Hotel> findAllHotels() { return hotelRepository.findAll().toIterable(); } public Optional<Hotel> findHotelById(String id) { return hotelRepository.findById(id).blockOptional(); } public Optional<Long> countHotels() { return hotelRepository.count().blockOptional(); } public Iterable<Payment> findAllPayments() { return paymentRepository.findAll().toIterable(); }}

方法名称和签名必须与GraphQL相应操作定义相匹配。

此外,你可能需要为嵌套字段创建解析器。例如,

@Component@RequiredArgsConstructorpublic class HotelResolver implements GraphQLResolver<Hotel> { private final RoomRepository roomRepository; public Iterable<Room> getRoom(Hotel hotel) { return roomRepository.findAllByHotelId(hotel.getId()).toIterable(); }}

在前面的示例中,当我们检索酒店时,我们也可能要求提供房间,因此需要提供按酒店ID检索房间的方法。

GraphiQL,GraphQL客户端


GraphiQL(名字没有拼错)是一个非常有用的工具,可以获取GraphQL架构并发出请求。开始使用Graphiql的最简单方法是pom.xml作为依赖项添加到配置文件中。

<dependency> <groupId>com.graphql-java-kickstart</groupId> <artifactId>graphiql-spring-boot-starter</artifactId> <version>5.7.0</version></dependency>

默认情况下可以访问/graphiql

GraphQL的优点与挑战


  • 使用GraphQL的主要好处是,可以在单个请求中获得所需的全部内容,而使用REST,人们往往倾向于“过度获取”或“获取不足”。

  • GraphQL简单,更快速,但是,当组合多个字段时,可能会遇到无法预测的性能问题。

  • 另一个挑战是版本控制。人们将不得不弃用一些字段,也无法知道字段是否随着时间发生了变化。

  • GraphQL的另一缺点是缓存问题。在GraphQL中,不能将URL加入缓存标识符,这需要在应用程序中创建唯一键值来实现缓存。

  • 还有一部分额外的开销,因为服务器需要执行更多处理来解析查询与验证参数。

  • 最后,如果只是一些简单API的情况下,使用GraphQL增加额外复杂度并不值得。

结论


GraphQL类似于位于下游服务或数据源之前的API网关或代理服务器。就如同HTTP一样,可以使用动词来获得要求的内容。它也是REST,SOAP或gRPC的替代品,但这并不意味着必须替代当前的体系结构。比如,可以在REST服务之上安装GraphQL。

GraphQL技术正变得越来越成熟,可用于多种语言,包括JavaScript,php,Python,Ruby,C#,Go,Scala或Java,而Pivotal等公司也大力倡导GraphQL。在实际应用中,它也是Spring IO 2019中提出的关键主题之一。


编译:老夏

来源:21CTO社区


以上是关于GraphQL正在超越REST的主要内容,如果未能解决你的问题,请参考以下文章

在 Java 的 GraphQL 查询中添加片段

使用 REST 包装 GraphQL 从 NestJS 提供 GraphQL 和 REST API

GraphQL 是不是具有与 REST 相同的缓存能力

重用 GraphQL 片段

将 GraphQL 片段与 Apollo Hooks 一起使用时出错

盖茨比没有找到graphql片段