Spring Boot与GraphQL – 篇一 如何进行查询

Spring Boot与GraphQL – 篇一 如何进行查询

背景介绍

GraphQL,由Facebook开源,并在github api v4中得到了使用,替代了v3中restful的地位。既然大厂使用,那就得去看看到底是个什么货色。

GraphQL相对于传统的restful接口,主要是在灵活性上有较大改进。例如,要增加一个属性返回,之前的做法可能需要更改接口的返回对象;如果属性越加越多,可能衍生出多个接口(包含返回少量的和大量的数据),也可能由此诞生一个巨大的接口(包含所有的数据);如果是多个对象的组合,由于组合的形式多种多样,有可能也会造成多个接口的产生。

而解决此缺点的做法是,客户端根据自己的需求来传递请求参数,参数中指明需要哪些数据,则服务器端根据这些参数返回给客户端所需要的数据。这样,关于数据的特定字段、或者组合、聚合类的需求,都无需服务端关心,客户端自己确定就好。

查询与变更 Queries and Mutations

字段 Fields

客户端通过指定Fields来控制返回的数据,Fields即为请求的数据,类似字段的含义。指定了服务端定义好的标量,例如:

请求

{
  hero {
    name
    # Queries can have comments!
    friends {
      name
    }
  }
}

响应

{
  "data": {
    "hero": {
      "name": "R2-D2",
      "friends": [
        {
          "name": "Luke Skywalker"
        },
        {
          "name": "Han Solo"
        },
        {
          "name": "Leia Organa"
        }
      ]
    }
  }
}

其中hero, name, friends等均为Fields,在请求中指定什么,就返回什么。

参数 Arguments

在获取数据的操作中,除了指定Fields以外,还可以指定参数,来丰富查询能力,例如:

请求

{
  human(id: "1000") {
    name
    height
  }
}

响应

{
  "data": {
    "human": {
      "name": "Luke Skywalker",
      "height": 1.72
    }
  }
}

甚至还可以在标量中传参来实现服务端统一的数据转换:

请求

{
  human(id: "1000") {
    name
    height(unit: FOOT)
  }
}

响应

{
  "data": {
    "human": {
      "name": "Luke Skywalker",
      "height": 5.6430448
    }
  }
}

指定heightunitFOOT,则返回的数据以英尺为单位。

别名 Aliases

当在一次请求中,需要用不同的参数来请求同一个对象时,就可能用上别名的功能。可以使用别名来重命名字段,例如:

请求

{
  empireHero: hero(episode: EMPIRE) {
    name
  }
  jediHero: hero(episode: JEDI) {
    name
  }
}

响应

{
  "data": {
    "empireHero": {
      "name": "Luke Skywalker"
    },
    "jediHero": {
      "name": "R2-D2"
    }
  }
}

empireHerojediHero都是指定的别名,在一次请求中,返回了两个hero对象。

片段 Fragments

上述别名的例子中,如果请求两个hero的字段更多,并且字段都类似,则需要写两段比较重复的请求字段。片段的作用是用来预定义一个请求格式,类似myBatis中的resultMap。定义好一个片段后,就可以在请求中直接引用,例如:

请求

{
  leftComparison: hero(episode: EMPIRE) {
    ...comparisonFields
  }
  rightComparison: hero(episode: JEDI) {
    ...comparisonFields
  }
}

fragment comparisonFields on Character {
  name
  appearsIn
  friends {
    name
  }
}

响应

{
  "data": {
    "leftComparison": {
      "name": "Luke Skywalker",
      "appearsIn": [
        "NEWHOPE",
        "EMPIRE",
        "JEDI"
      ],
      "friends": [
        {
          "name": "Han Solo"
        },
        {
          "name": "Leia Organa"
        },
        {
          "name": "C-3PO"
        },
        {
          "name": "R2-D2"
        }
      ]
    },
    "rightComparison": {
      "name": "R2-D2",
      "appearsIn": [
        "NEWHOPE",
        "EMPIRE",
        "JEDI"
      ],
      "friends": [
        {
          "name": "Luke Skywalker"
        },
        {
          "name": "Han Solo"
        },
        {
          "name": "Leia Organa"
        }
      ]
    }
  }
}

我们定义了一个comparisonFields的片段,其中定义了一些字段,并在请求中引入了此片段,可以看到返回数据就是此片段的格式。

操作名 Operation name

针对各种各样的请求,可以指定一个操作名来减少歧义。操作名包括类型和名称,例如:

请求

query HeroNameAndFriends {
  hero {
    name
    friends {
      name
    }
  }
}

响应

{
  "data": {
    "hero": {
      "name": "R2-D2",
      "friends": [
        {
          "name": "Luke Skywalker"
        },
        {
          "name": "Han Solo"
        },
        {
          "name": "Leia Organa"
        }
      ]
    }
  }
}

其中query即为操作类型,还可以为mutation或者subscriptionHeroNameAndFriends为操作名称。比较推荐使用操作名,因为在调试问题时比较便于定位是哪一个请求。

变量 Variables

在请求中,查询条件等大都是随着具体业务对象变化的,不是写死的,所以支持变量传入的查询也是最基本的功能。使用变量需如按如下三个步骤:

  1. 使用$variableName替换请求中的固定值。即在请求中定义变量。
  2. 在查询中声明变量$variableName,类似函数中的变量声明。
  3. 在请求时以variableName: value的形式传入变量。

请求

query HeroNameAndFriends($episode: Episode) {
  hero(episode: $episode) {
    name
    friends {
      name
    }
  }
}

参数

{
  "episode": "JEDI"
}

响应

{
  "data": {
    "hero": {
      "name": "R2-D2",
      "friends": [
        {
          "name": "Luke Skywalker"
        },
        {
          "name": "Han Solo"
        },
        {
          "name": "Leia Organa"
        }
      ]
    }
  }
}

还可以指定变量的默认值:

query HeroNameAndFriends($episode: Episode = JEDI) {
  hero(episode: $episode) {
    name
    friends {
      name
    }
  }
}

指令 Directives

动态的请求参数问题,我们可以使用上述的变量来解决,但请求的字段或者结构,同样面临动态变化的需求,这种场景我们就需要用指令来解决。例如某页面可能同时包括缩略信息和全量信息,例如:

请求

query Hero($episode: Episode, $withFriends: Boolean!) {
  hero(episode: $episode) {
    name
    friends @include(if: $withFriends) {
      name
    }
  }
}

变量

{
  "episode": "JEDI",
  "withFriends": false
}

响应

{
  "data": {
    "hero": {
      "name": "R2-D2"
    }
  }
}

如果传入的$withFriends变量为true,返回的结果则会包含friends对象。现在核心GraphQL包含两个指令:

  • @include(if: Boolean)包含指令定义的部分
  • @skip(if: Boolean)排除指令定义的部分

发表评论