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 {
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 ] !
}
自定义类型也可以扩展,可以避免大型字段列表。
还有像其它的更高级元素:interface
,union
,enum
或scalar
。
创建GraphQL Spring Boot 服务器
首先我们需要:
定义GraphQL架构
确定如何获取查询的数据
定义GraphQL架构
这里有一个例子:
type hotel{
id:ID!
#酒店名称
name:String!
#酒店地址
address:String!
#酒店注册表创建日期
creationDate:String!
#特定酒店的房间清单
room:[room]!
}
type room{
id:ID!
#房间描述
type:String!
#几人间
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 Mutation
type 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> {
Flux<Hotel> findWithTailableCursorBy();
}
@Tailable
需要在MongoDB中查询有限集合。即使在收集结束后,有限集合也会使游标保持打开状态。
下一步是为架构中定义的每个对象创建解析器。查询,变异和订阅是GraphQL的根对象,需要分别实现GraphQLQueryResolver,GraphQLMutationResolver和GraphQLSubscriptionResolver,以便graphql-java-tools
能够使用解析器中创建的方法映射GraphQL操作。以下为代码例子:
public 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相应操作定义相匹配。
此外,你可能需要为嵌套字段创建解析器。例如,
public 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的主要内容,如果未能解决你的问题,请参考以下文章
使用 REST 包装 GraphQL 从 NestJS 提供 GraphQL 和 REST API