GraphQL技术怎样?有什么缺点?

举报
Jet Ding 发表于 2020/09/30 15:41:18 2020/09/30
【摘要】 【引言】GraphQL是一种开源的数据查询和操作语言,是一种用于API的数据查询和操作的语言,也是一种利用现有数据完成查询的动态理念。 GraphQL于2012年由Facebook内部开发,2015年公开发布。2018年11月7日,GraphQL项目从Facebook转到了新成立的GraphQL基金会,由非营利性的Linux基金会主持管理。自2012年以来,GraphQL的崛起一直遵循着Gr...

【引言】

GraphQL是一种开源的数据查询和操作语言,是一种用于API的数据查询和操作的语言,也是一种利用现有数据完成查询的动态理念。 GraphQL2012年由Facebook内部开发,2015年公开发布。2018117日,GraphQL项目从Facebook转到了新成立的GraphQL基金会,由非营利性的Linux基金会主持管理。自2012年以来,GraphQL的崛起一直遵循着GraphQL的创建者Lee Byron所制定的推广时间表,并准确无误。Byron的目标是让GraphQLWeb平台上无所不在。

 

它提供了一种开发Web API的方法,并与REST和其他Web服务架构进行了比较和对比。它允许客户端定义所需的数据结构,而服务器返回的数据结构也是一样的,因此可以防止过量的数据被返回,但这对查询结果的web缓存的有效性有影响。查询语言的灵活性和丰富性也增加了程序设计的复杂度,对于简单的API来说,可能并不值得。它由类型系统、查询语言和执行语义、静态验证和类型自省组成。

 

GraphQL 支持读取、写入(包括突变)和订阅数据的变化(实时更新--最常见的是使用 WebHooks )。

 

GraphQL服务器适用于多种语言,包括HaskellJavaScriptPerlPythonRubyJavaC#ScalaGoElixirErlangPHPRClojure

 

201829日,GraphQL Schema Definition Language (SDL)成为规范的一部分。

 

GraphQL 规范最新的版本发布在 https://graphql.github.io/graphql-spec/

 

最新的规范草案可以在 https://graphql.github.io/graphql-spec/draft/

找到。

 

之前发布的GraphQL规范可以在与其发布标签匹配的permalinks中找到例如,https://graphql.github.io/graphql-spec/October2016/

 

【概览】

GraphQLFacebook创建的API查询语言。

 

目标受众不是客户端开发者,而是那些已经对构建自己的GraphQL服务程序和工具感兴趣的人。

 

为了被广泛采用,GraphQL必须提供针对各种后端、框架和语言的支持,这将需要跨项目和组织的协作努力。

 

GraphQL 由类型系统、查询语言和执行语义、静态验证和类型自省组成,下面会分别介绍这些部分:

 

下面的例子中,我们会用GraphQL来查询《星球大战》三部曲中的人物和位置信息。

【类型系统】

任何GraphQL实现的核心是描述它可以返回哪些类型的对象,在GraphQL类型系统中描述并在GraphQL Schema中返回。

对于我们的Star Wars例子,GraphQL.js中的starWarsSchema.js文件定义了这个类型系统。

 

系统中最基本的类型将是Human,代表着像LukeLeiaHan这样的角色。在我们的类型系统中,所有的人类都会有一个名字,所以我们定义Human类型有一个名为 "name "的字段。我们定义 "name "字段为不可空的字符串。使用我们将在整个规范和文档中使用的速记符号,我们将把人类模型定义为:

 

type Human {

  nameString

}

 

这个速记词方便描述类型系统的基本状况;JavaScript实现的功能比较齐全,可以对类型和字段进行记录。它还设置了类型系统和底层数据之间的映射;对于GraphQL.js中的一个测试用例来说,底层数据是一组JavaScript对象,但在大多数情况下,支持的数据将通过一些服务来访问,而这个类型系统层将负责从类型和字段到那个服务的映射。

 

在许多API中,甚至在GraphQL中,一个常见的模式是给对象一个ID,可以用来访问这个对象。所以,让我们把这个添加到我们的Human类型中,此外我们还要添加另一个字符串: homePlanet

 

type Human {

  idString

  nameString

  homePlanetString

}

 

既然我们讨论的是《星球大战三部曲》,那么描述一下每个角色出现的剧集是很有用的。为此,我们先定义一个枚举,列出三部曲:

 

enum Episode { NEWHOPE, EMPIRE, JEDI }

 

现在我们要在Human中添加一个字段,描述他们参加过的剧集。是一个剧集列表:

 

type Human {

  idString

  nameString

  appearsIn: [Episode]

  homePlanetString

}

 

现在,我们来介绍另一种类型,Droid

type Droid {

  idString

  nameString

  appearsIn: [Episode]

  primaryFunctionString

}

 

现在我们有两种类型了让我们在这两种类型之间增加一种关联方式:人类和机器人都有朋友,人类和机器人也都可以做朋友。

 

人类和机器人之间有共同的属性;它们都有ID、名字,以及出现的剧集。

我们添加一个接口Character,让HumanDroid从这里派生。有了这些之后,我们就可以添加好友字段,也即返回一个Character列表。

 

我们的类型系统就成了这样:

 

enum Episode { NEWHOPE, EMPIRE, JEDI }

 

interface Character {

  idString

  nameString

  friends: [Character]

  appearsIn: [Episode]

}

 

type Human implements Character {

  idString

  nameString

  friends: [Character]

  appearsIn: [Episode]

  homePlanetString

}

 

type Droid implements Character {

  idString

  nameString

  friends: [Character]

  appearsIn: [Episode]

  primaryFunctionString

}

 

这里我们需要问一个问题,就是这些字段中的任何一个字段是否可以返回null? 默认情况下,nullGraphQL中任何类型的允许值,因为获取数据以完成GraphQL查询经常需要与不同的服务对话,而这些服务有可能是可用的,也可能是不可用的。如果我们想把某个类型标记为不能为空,我们可以通过在类型后添加一个""来表示。

 

注意,在实现中,我们可能把一些字段保留为nullable,这样我们就有了灵活性,最终可以判断返回null来表示后端错误,同时也告诉客户端发生了错误。

我们尝试把id设定为不为空字段:

 

enum Episode { NEWHOPE, EMPIRE, JEDI }

 

interface Character {

  idString!

  nameString

  friends: [Character]

  appearsIn: [Episode]

}

 

type Human implements Character {

  idString!

  nameString

  friends: [Character]

  appearsIn: [Episode]

  homePlanetString

}

 

type Droid implements Character {

  idString!

  nameString

  friends: [Character]

  appearsIn: [Episode]

  primaryFunctionString

}

 

接下来我们需要定义一个入口来访问类型系统,实际上就是一个查询数据类型。

 

这个类型的名称按照惯例是 Query,它描述了我们公共的、顶层API。这个例子中的查询类型是这样的:

 

type Query {

  hero(episodeEpisode): Character

  human(idString!): Human

  droid(idString!): Droid

}

在这个例子中,有三个操作字段:

l  hero 返回《星球大战》三部曲中的英雄人物;它有一个可选的参数,允许我们获取特定剧集的英雄人物。

l  human有一个非空字符串ID作为查询参数,返回人类。

l  droid有一个非空字符串ID作为查询参数,返回机器人人。

 

这些字段展示了类型系统的另一个特点,即字段可以指定参数来配置它们的结果。

 

当我们将整个类型系统打包在一起,将上面的查询类型定义为查询的入口点,这就创建了一个GraphQL的设计定义。

 

查询语法

GraphQL查询准确的描述了调用者需要获取什么数据。

 

简单查询

我们来看例子:

query HeroNameQuery {

  hero {

    name

  }

}

 

上面这个查询的名字描述了很清晰的目的,就是要查询一个名字。其返回结果大概是这样的:

{

  "hero": {

    "name""R2-D2"

  }

}

 

 

更多数据查询

再来看另一个查询:

query HeroNameAndFriendsQuery {

  hero {

    id

    name

    friends {

      id

      name

    }

  }

}

 

这个查询希望查询到id, name以及其朋友的idname。返回结果大概这样:

{

  "hero": {

    "id""2001",

    "name""R2-D2",

    "friends": [

      {

        "id""1000",

        "name""Luke Skywalker"

      },

      {

        "id""1002",

        "name""Han Solo"

      },

      {

        "id""1003",

        "name""Leia Organa"

      }

    ]

  }

}

 

 

嵌套查询

下面看一个嵌套查询的例子:

query NestedQuery {

  hero {

    name

    friends {

      name

      appearsIn

      friends {

        name

      }

    }

  }

}

 

返回结果:

{

    "hero": {

      "name""R2-D2",

      "friends": [

        {

          "name""Luke Skywalker",

          "appearsIn": ["NEWHOPE""EMPIRE""JEDI"],

          "friends": [

            { "name""Han Solo" },

            { "name""Leia Organa" },

            { "name""C-3PO" },

            { "name""R2-D2" }

          ]

        },

        {

          "name""Han Solo",

          "appearsIn": ["NEWHOPE""EMPIRE""JEDI"],

          "friends": [

            { "name""Luke Skywalker" },

            { "name""Leia Organa" },

            { "name""R2-D2" }

          ]

        },

        {

          "name""Leia Organa",

          "appearsIn": ["NEWHOPE""EMPIRE""JEDI"],

          "friends": [

            { "name""Luke Skywalker" },

            { "name""Han Solo" },

            { "name""C-3PO" },

            { "name""R2-D2" }

          ]

        }

      ]

    }

  }

 

 

指定ID查询

query FetchLukeQuery {

  human(id"1000") {

    name

  }

}

 

或者:

query FetchSomeIDQuery($someIdString!) {

  human(id$someId) {

    name

  }

}

 

返回结果:

{

  "human": {

    "name""Luke Skywalker"

  }

}

 

 

指定ID的多个查询

query FetchLukeAndLeiaAliased {

  lukehuman(id"1000") {

    name

  }

  leiahuman(id"1003") {

    name

  }

}

 

结果:

{

  "luke": {

    "name""Luke Skywalker"

  },

  "leia": {

    "name""Leia Organa"

  }

}

 

 

特殊字段:__typename

下面来看特殊字段的例子:

 

query CheckTypeOfR2 {

  hero {

    __typename

    name

  }

}

 

结果:

{

  "hero": {

    "__typename""Droid",

    "name""R2-D2"

  }

}

 

 

验证

通过使用类型系统,可以预先确定GraphQL查询是否有效。服务器和客户端之间如果创建了一个无效查询,类型系统就可以有效地通知开发人员,而不需要运行时才报错。

 

上面我们列出了一些有效的查询例子,接下来我们看看无效的查询例子:

 

字段未定义

# INVALID: favoriteSpaceship不存在于Character上。

query HeroSpaceshipQuery {

  hero {

    favoriteSpaceship

  }

}

 

在最基础字段(标量)下再查询

# INVALID: name 已经是最基础的字段了,不能再进行子字段查询。

query HeroFieldsOnScalarQuery {

  hero {

    name {

      firstCharacterOfName

    }

  }

}

 

片段定义

 

query DroidFieldInFragment {

  hero {

    name

    ...DroidFields

  }

}

 

fragment DroidFields on Droid {

  primaryFunction

}

 

上面这种查询是有效的,但是如果这个片段没有被多次调用的话,我们可以匿名定义:

query DroidFieldInInlineFragment {

  hero {

    name

    ... on Droid {

      primaryFunction

    }

  }

}

 

上面的查询中,hero没有primaryFunction字段定义,但是Droid有这个字段,通过片段声明可以使这个查询有效。

 

自省

这项功能允许我们询问GraphQL支持哪些查询。

 

缺省询问

比如对上面的系统,我们可以这样询问:

query IntrospectionTypeQuery {

  __schema {

    types {

      name

    }

  }

}

 

 

得到的结果类似于这样:

 

{

  "__schema": {

    "types": [

      {

        "name""Query"

      },

      {

        "name""Character"

      },

      {

        "name""Human"

      },

      {

        "name""String"

      },

      {

        "name""Episode"

      },

      {

        "name""Droid"

      },

      {

        "name""__Schema"

      },

      {

        "name""__Type"

      },

      {

        "name""__TypeKind"

      },

      {

        "name""Boolean"

      },

      {

        "name""__Field"

      },

      {

        "name""__InputValue"

      },

      {

        "name""__EnumValue"

      },

      {

        "name""__Directive"

      }

    ]

  }

}

 

 

上面类型很多,我们分一下类:

l  Query, Character, Human, Episode, Droid - 这些是我们在类型系统中定义的。

l  String, Boolean - 这些是类型系统提供的内置标量。

l  __Schema, __Type, __Type, __TypeKind, __Field, __InputValue, __EnumValue, __Directive - 这些都在前面加了双下划线,表示它们是自省系统的一部分。

 

现在我们只询问查询支持:

query IntrospectionQueryTypeQuery {

  __schema {

    queryType {

      name

    }

  }

}

 

结果如下:

{

  "__schema": {

    "queryType": {

      "name""Query"

    }

  }

}

 

 

询问特定类型

query IntrospectionDroidTypeQuery {

  __type(name"Droid") {

    name

  }

}

 

结果:

{

  "__type": {

    "name""Droid"

  }

}

 

 

类别查询

query IntrospectionDroidKindQuery {

  __type(name"Droid") {

    name

    kind

  }

}

 

结果:

 

{

  "__type": {

    "name""Droid",

    "kind""OBJECT"

  }

}

 

 

上面是对象类型,我们来看接口类型:

query IntrospectionCharacterKindQuery {

  __type(name"Character") {

    name

    kind

  }

}

 

结果:

{

  "__type": {

    "name""Character",

    "kind""INTERFACE"

  }

}

 

 

字段支持查询

 

query IntrospectionDroidFieldsQuery {

  __type(name"Droid") {

    name

    fields {

      name

      type {

        name

        kind

      }

    }

  }

}

 

结果:

 

{

  "__type": {

    "name""Droid",

    "fields": [

      {

        "name""id",

        "type": {

          "name"null,

          "kind""NON_NULL"

        }

      },

      {

        "name""name",

        "type": {

          "name""String",

          "kind""SCALAR"

        }

      },

      {

        "name""friends",

        "type": {

          "name"null,

          "kind""LIST"

        }

      },

      {

        "name""appearsIn",

        "type": {

          "name"null,

          "kind""LIST"

        }

      },

      {

        "name""primaryFunction",

        "type": {

          "name""String",

          "kind""SCALAR"

        }

      }

    ]

  }

}

 

 

无名字段,查类型

像列表类型字段没有名字,我们可以查看类型ofType

 

query IntrospectionDroidWrappedFieldsQuery {

  __type(name"Droid") {

    name

    fields {

      name

      type {

        name

        kind

        ofType {

          name

          kind

        }

      }

    }

  }

}

 

结果:

 

{

  "__type": {

    "name""Droid",

    "fields": [

      {

        "name""id",

        "type": {

          "name"null,

          "kind""NON_NULL",

          "ofType": {

            "name""String",

            "kind""SCALAR"

          }

        }

      },

      {

        "name""name",

        "type": {

          "name""String",

          "kind""SCALAR",

          "ofType"null

        }

      },

      {

        "name""friends",

        "type": {

          "name"null,

          "kind""LIST",

          "ofType": {

            "name""Character",

            "kind""INTERFACE"

          }

        }

      },

      {

        "name""appearsIn",

        "type": {

          "name"null,

          "kind""LIST",

          "ofType": {

            "name""Episode",

            "kind""ENUM"

          }

        }

      },

      {

        "name""primaryFunction",

        "type": {

          "name""String",

          "kind""SCALAR",

          "ofType"null

        }

      }

    ]

  }

}

 

 

询问描述文档

 

query IntrospectionDroidDescriptionQuery {

  __type(name"Droid") {

    name

    description

  }

}

 

结果:

 

{

  "__type": {

    "name""Droid",

    "description""A mechanical creature in the Star Wars universe."

  }

}

 

 

进一步的,我们可以使用自省系统创建更强大的文档系统,从而丰富编码人员的开发体验。

 

小结

上面我们对GraphQL从类型系统,查询语法,验证和自省功能进行了学习。

接下来我们看看GraphQL有什么缺点:

缺陷

GraphQL的主要缺点是它使用单一的端点,而不是遵循HTTP规范进行缓存。而网络层面的缓存很重要,它可以减少服务器的流量。

 

此外,对于简单的应用程序来说,这并不是好的解决方案,因为它增加了复杂度(类型、查询、验证、自省)而使用REST可以更简单地完成的任务。


【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。