GraphQL简单学习-入门
一、GraphQL是什么
GraphQL是Facebook在2012年创建、2015年形成规范的一种应用层查询语言,它具有很多优点:客户端可以自定义查询语句,通过自定义不仅提高了灵活性,而且服务端只返回客户端所需要的数据,减少网络的开销,提高了性能;服务端收到客户端的请求,首先做类型检查,及时反馈,而且更加安全;自动生成文档,降低维护成本;服务端通过新增字段,Deprecated字段,避免版本的繁杂和紊乱。然而上面所述也都是Rest API弊端,GraphQL却能很好的解决他们。
GraphQL不与任何数据库或存储引擎绑定,而且已经有很多编程语言都提供了类库对GraphQL的支持,如NodeJs、Java、PHP、Ruby、Python、Scala、Clojure、Go、Net、Elixir、Swift等。GraphQL更关注的是前端交互,因此你可以在你现有的系统上做改造
二、GraphQL的查询
Graph的查询语句非常类似JSON,如客户端的查询语句
{
user{
name
sex
}
}
上述语句的目的是查询用户信息的name和sex字段数据,服务端返回结果如
{
"data": {
"user": {
"name": "wind",
"sex": "男"
}
}
}
当客户端使用如下查询语句时
{
user{
name
intro
}
}
服务端返回的结果如
"data": {
"user": {
"name": "windhan",
"intro": "程序员,Java程序员,Web前端"
}
}
}
这样看-->是不是客户端完全*了,可以根据自己特定的业务来获取数据。
三、Schema
GraphQL中非常重要的一个概念是Schema,如果你了解XML Schema的话,就非常容易理解GraphQL的Schema了,Schema由服务端来定义,用于定义API接口,并依靠Schema来生成文档以及对客户端请求进行校验。Schema只是一个概念,它是由各种数据类型及其字段组成,而每个类型的每个字段都有相应的函数来返回数据,这是其灵活的关键所在,如Schema
type User{
name: String
sex: String
intro: String
}
type Query {
user:User
}
上面定义了两个类型,User和Query:User有三个字段分别是name、sex和intro;Query是个特殊的类型,用于查询数据,内含user字段;这些类型中的字段都有自动生成的函数来返回数据,函数类似(JS类型):
function User_name(user) {
return user.getName();
}
function User_sex(user) {
return user.getSex();
}
function User_intro(user) {
return user.getIntro();
}
function Query_user(request) {
return request.user;
}
因此当服务端接收到客户端的如下查询语句
{
user{
name
sex
}
}
会调用上面的函数组合来返回数据。下面我们一下JAVA(springboot)为例做一个demo: https://my.oschina.net/hanchao/blog/3014079
四、数据类型
GraphQL是应用层查询语言,它有自己的数据类型,用来描述数据,比如标量类型、集合类型、Object类型、枚举等,甚至有接口类型。
1.标量类型(Scala)
标量类型是不能包含子字段,主要有如下类型:
- Int: 有符号的32位整型
- Float: 有符号的双精度浮点型
- String: UTF‐8字符序列
- Boolean: 布尔型
- ID: 常用于获取数据的唯一标志,或缓存的键值,它也会被序列化为String,但可读性差。
以上的类型是GraphQL规范的基本类型,每个规范的实现也可以有自定以标量,如 scala Date ,但它必须能够序列和反序列化,至于如何定于取决于具体的规范实现。
graphql-java中的ScalarType
在graphql-java中,除了GraphQL文档中说明的最基本的类型 GraphQLInt, GraphQLFloat, GraphQLString, GraphQLBoolean, GraphQLID之外,还包含了GraphQLLong, GraphQLBigInteger, GraphQLBigDecimal, GraphQLByte, GraphQLShort和GraphQLChar方便开发者使用。
其中需要注意的是,当对field选用了GraphQLID时,只会接受String和Integer类型的值并将其转换为String传递出去, 而通常数据库默认定义的id是Long,如果用GraphQLID的话可能会出错。
2.枚举(Enum)
enum Unit {
MM
mm
}
MM代表米做单位,mm代码毫米做单位。
3.对象(Ojbect)
组成Schema最常用的是对象类型,它包含各种字段,定义如:
type User{
name: String
sex: String
intro: String
}
以上定义了一个User对象,包含name(名字)、sex(性别)、intro(介绍)属性字段,而这些属性字段都是标量String类型,当然属性也可以是对象类型。
4.集合(List)
GraphQL规范中的集合只有List一种,它是有序的用 [] 表示,如
type User{
name: String
sex: String
intro: String
skills: [String]
}
skills(技能)就是一个list集合,其实更像是一个数组。
5.非空/Null(Non-Null)
在类型后使用 ! 声明 非空/Null,如
type Query {
user(id:Int!):User
}
type User{
name: String!
sex: String
intro: String
skills: [String]!
}
以上定义了查询接口user,其中参数id为必须提供,如果你使用如下查询语句
query{
user {
name
sex
intro
}
}
则会反馈异常
{
"errors": [
{
"message": "Field \"user\" argument \"id\" of type \"Int!\" is required but not provided.",
"locations": [
{
"line": 2,
"column": 3
}
]
}
]
}
而实体类型User的name为非null,一但服务端返回的数据name为null,则反馈为
{
"data": {
"user": null
},
"errors": [
{
"message": "Cannot return null for non-nullable field User.name.",
"locations": [
{
"line": 3,
"column": 5
}
],
"path": [
"user",
"name"
]
}
]
}
然而需要注意区别 skills: [String]! 与 skills: [String!] ,前者是集合不能为null,后者是集合元素不能为null。
6.模式(Schema)
GraphQL的Schema用于生成文档、格式定义与校验等,除了自定义的类型如上文中的User,还有两个特殊的类型Query(查询)和Mutation(维护)。如增查改删(CRUD),增改删属于后者,查属于前者。
schema {
query: Query
mutation: Mutation
}
一个Schema可以没有mutaion,但必须有query。
7.查询(Query)
Query目的是获取数据。格式:
query queryName{
operation
}
如
query userQuery{
user(id:0){
name
sex
stature
}
users{
name
sex
stature
}
}
实际上如果只有一个查询,“query”“queryName”都可以省略,如
{
user(id:0){
name
sex
stature
}
users{
name
sex
stature
}
}
8.维护(Mutation)
Mutataion是用来维护数据的,格式和查询类似
mutation mutationName{
operation
}
如:
mutation{
addUser(name:"testUser",sex:"男",intro:"简介",skills:[]){
name
sex
intro
}
}
由于Schema可以没有Mutation,但必须有Query,因此mutaion关键字是不可以省略的,否则被认为是Query而找不到操作名。
9.参数(Argument)
客户端的查询语句中对象和字段都可以传入参数,方便精确控制服务返回结果,查询语句如
{
user(id:1) {
name
sex
intro
skills
stature(unit:MM)
}
}
服务返回数据
{
"data": {
"user": {
"name": "James",
"sex": "男",
"intro": "xxx的英文名",
"skills": [
"Linux",
"Java",
"nodeJs",
"前端"
],
"stature": 1.8
}
}
}
服务端是靠resolver来解析实现的
10.指令(Directive)
客户端可以使用两种指令skip和include,可以用与字段(field)、片段(fragment),格式和含义如下:
- field/fragment @skip(if: $isTrue) 当$isTrue为真(true)时不查询field或不使用fragment;
- field/fragment @include(if: $isTrue) 当$isTrue为真(true)时查询field或使用fragment;
而参数变量是通过query或mutation传递的;变量形如$withName:Boolean!,以$开头,以类型结尾,类型必须是标量(scalar)、枚举(enum)或输入类型(input)。如query为
query(
$noWithDog:Boolean!,
$withName:Boolean!,
$withFish:Boolean!
){
animals{
name @include(if:$withName)
... dogQuery @skip(if:$noWithDog)
... on Fish @include(if:$withFish){
tailColor
}
}
}
fragment dogQuery on Dog{
legs
}
在query定义中分别定义了$noWithDog(是否带着狗狗)、$withName(是否带着Name)、$withFish(是否带着鱼儿)变量,传入的参数变量为
{
"noWithDog": true,
"withName": true,
"withFish": true
}
查询的结果如
{
"data": {
"animals": [
{
"name": "dog"
},
{
"name": "fish",
"tailColor": "red"
}
]
}
}
至于如何传递参数,可以参考:https://my.oschina.net/hanchao/blog/3014116#h3_7
如果参数变量传给非空的字段,那么参数变量也必须是非空类型,否则可以允许为空,允许为空是属于可选参数。而且参数变量可以有默认值值,如
query Animal($type: String = "dog") {
animal(type: $type) {
name
}
}
11.输入(Input)
上面的参数(Argument)我们使用是标量,但当使用Mutation来更新数据时,你可能更喜欢传入一个复杂的实体Object,GraphQL使用input关键字来到定义输入类型,不直接用Object是为了避开循环引用、接口或联合等一些不可控的麻烦,特别是input类型不能像Object那样带参数的。如
input UserInput {
name: String!
sex: String
intro: String
skills: [String]!
}
定义了一个输入类型UserInput,字段有name、sex、intro和skills.
12.接口(Interface)
类似其他语言,GraphQL也有接口的概念,方便查询时返回统一类型,接口是抽象的数据类型,因此只有接口的实现才有意义,如
interface Animal{
name: String!
}
type Dog implements Animal{
name: String!
legs: Int!
}
type Fish implements Animal{
name: String!
tailColor: String!
}
上面的接口Animal有两个实现:Dog和Fish,它们都有公共字段name。服务端的查询Schema可以是类似如下的定义
type Query {
animals:[Animal]!
}
而客户端的查询需要使用下面的Fragment,稍后展示。
13.联合(Union)
联合查询,类似接口式的组合,但不要求有公共字段,使用union关键字来定义,如
type Dog{
chinaName: String!
legs: Int!
}
type Fish{
englishName: String!
tailColor: String!
}
union Animal = Dog | Fish
则Animal可以是Dog也可以是Fish,服务端定义查询可以和上面的接口相同,客户端查询也需要使用Fragment,稍后展示。
14.片段(Fragment)
Fragment分为内联和外联,两种都是用于选择主体,而后者还可以共用代码。如上面的接口示例,Dog和Fish都是接口Animal的实现,有接口的公共字段name,简单的内联
{
animals{
name
... on Dog{
legs
}
... on Fish{
tailColor
}
}
animalSearch(text:"dog"){
name
... on Dog{
legs
}
... on Fish{
tailColor
}
}
}
如果是Dog则查询legs字段,如果是Fish则查询tailColor字段,这种内联如果单个查询则比较方便,但多个查询则使用外联更简洁,如下
{
animals{
... animalName
... dogLegs
... fishTail
}
animalSearch(text:"dog"){
... animalName
... dogLegs
... fishTail
}
}
fragment animalName on Animal {
name
}
fragment dogLegs on Dog{
legs
}
fragment fishTail on Fish{
tailColor
}
在联合(union)查询,因为没有公共字段,使用fragment示例如下
{
animals{
... on Dog{
chinaName
legs
}
... on Fish{
englishName
tailColor
}
}
}
15.别名(Alias)
GraphQL支持别名命名,这对于查询多个雷同的结果非常有用,格式为 aliasName: orginName 如
{
james:user(id:1) {
name
sex
intro
skills
MM:stature(unit:MM)
mm:stature(unit:mm)
}
jerry:user(id:0) {
name
sex
intro
skills
MM:stature(unit:MM)
mm:stature(unit:mm)
}
}
以上分别把id为1、2的分别给予别名为james、jerry,以米(MM)、毫米(mm)为单位的身高分别给予别名MM、mm,服务端返回的数据如
{
"data": {
"james": {
"name": "James",
"sex": "男",
"intro": "xiaozhao的英文名",
"skills": [
"Linux",
"Java",
"nodeJs",
"前端"
],
"MM": 1.8,
"mm": 180
},
"jerry": {
"name": "jerry",
"sex": "男",
"intro": "博主,专注于Linux,Java,nodeJs,Web前端:Html5,JavaScript,CSS3",
"skills": [
"Linux",
"Java",
"nodeJs",
"前端"
],
"MM": 1.8,
"mm": 180
}
}
}
参考文章
加强理解:https://segmentfault.com/a/1190000014829295
GraphQL之Java实战入门:http://www.zhaiqianfeng.com/2017/06/learn-graphql-action-by-java.html
转载于:https://my.oschina.net/hanchao/blog/3017296
上一篇: 谈谈新手如何学习PHP网络编程第1/2页_PHP教程
下一篇: 一个好用的UBB类!_PHP