欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

GraphQL简单学习-入门

程序员文章站 2022-05-16 11:05:50
...

一、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简单学习-入门

二、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, GraphQLShortGraphQLChar方便开发者使用。

其中需要注意的是,当对field选用了GraphQLID时,只会接受StringInteger类型的值并将其转换为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