Graphql入门

GraphQL是一个查询语言,由Facebook开发,用于替换RESTful API。服务端可以用任何的语言实现。具体的你可以查看Facebook关于GraphQL的文档各种语言的实现

GraphQL的小历史

早在2012年,Facebook认为人们只有在离开PC的时候才会用智能手机,很快他们就发现这个认识是多么的错误!于是Facebook把注意力从Web移到了智能终端上。在那个时候,他们严重的依赖于RESTful API。大量的并发请求和对补充数据的二次请求给他们造成了很大的麻烦,尤其是响应时间。一个解决方案是设计足够多的资源来满足单次的请求。但是,这造成了服务端的扩展和维护困难。

在寻找更好的解决方案的过程中,Facebook的工程师发现开发人员不应该先入为主的把数据看成RESTful一样的集合。如何更好地存储和获取数据不应该是他们要主要考虑的内容。他们应该更多的考虑数据的关系,网状的关系。

在这个情况下GraphQL应运而生。

GraphQL工作机制

一个GraphQL查询可以包含一个或者多个操作(operation),类似于一个RESTful API。操作(operation)可以使两种类型:查询(Query)或者修改(mutation)。我们看一个例子:

query {

  client(id: 1) {

    id 

    name

  }

}

你的第一印象:“这个不是JSON?”。还真不是!就如我们之前说的,GraphQL设计的中心是为客户端服务。GraphQL的设计者希望可以写一个和期待的返回数据schema差不多的查询。

注意上面的例子有三个不同的部分组成:

* client是查询的operation

* (id: 1)包含了传入给Query的参数

* 查询包含idname字段,这些字段也是我们希望查询可以返回的

我们看看server会给这个查询返回什么:

{

  "data": {

    "client": {

      "id": "1",

      "name": "Uncle Charlie"

    }

  }

}

就如我们期望的,server会返回一个JSON串。这个JSON的schema和查询的基本一致。

我们再看看另一个例子:

query {

  products(product\_category\_id: 1, order: "price DESC") {

    name 

    shell\_size

    manufacture

    price

  }

}

这次我们查询products,并传入两个参数:product\_category\_id用于过滤,一个指明按照price字段降序排列。查询中包含的字段是:nameshell\_sizemanufacturerprice)。

你可能已经猜到返回的结果是什么样子的了:

{

  "data": {

    "products": [

      {

        "name": "Mapex Black Panther Velvetone 5-pc Drum Shell Kit",

        "shell\_size": "22\"x18\" Bass Drum, 10\"x8\" & 12\"x9\" Toms, 14\"x14\" & 16\"x16\" Floor Toms",

        "manufacturer": "Mapex",

        "price": 2949.09

      },

      {

        "name": "Pearl MCX Masters Natural Birdseye Maple 4pc Shell Pack with 22\" Kick",

        "shell\_size": "22x18\" Virgin Bass Drum 10x8\" Rack Tom 12x9\" Rack Tom 16x16\" Floor Tom",

        "manufacturer": "Pearl",

        "price": 1768.33

      }

    ]

  }

}

从这几个初级的例子里你可以看出来GraphQL允许客户端明确指定它要的是什么,避免了数据后去的冗余或者不足。和RESTful API对比一下,每一个客户端都会对应很多个RESTful API或者一个API要服务很多个客户端。所以说GraphQL是很好的查询语言。所有的operation、参数和所有可以查询的字段都需要在GraphQL server上定义、实现。

GraphQL还解决了另外一个问题。假设我们要查询product\_categories和相关的products。在一个RESTful server上你可以实现一个API,返回全部的数据。但是,大多数的情况下,客户端会先请求product\_categories之后在其他的请求中获取相关的某些products

我们看看使用GraphQL可以怎么做:

query {

  product\_categories {

    name 

    products {

      name 

      price

    }

  }

}

我们这一次没有使用参数。在查询中我们指定了我么需要每一个product\_categoryname,还有所有的这个类别下的产品,每个产品的字段也都分别指定。返回的结果:

{

  "data": {

    "product\_categories": [

      {

        "name": "Acoustic Drums",

        "products": [

          {

            "name": "Mapex Black Panther Velvetone 5-pc Drum Shell Kit",

            "price": 2949.09

          },

          {

            "name": "Pearl MCX Masters Natural Birdseye Maple 4pc Shell Pack with 22\" Kick",

            "price": 1768.33

          }

        ]

      },

      {

        "name": "Cymbals",

        "products": [

          {

            "name": "Sabian 18\" HHX Evolution Crash Cymbal - Brilliant",

            "price": 319

          },

          {

            "name": "Zildjian 20\" K Custom Dry Light Ride Cymbal",

            "price": 396.99

          },

          {

            "name": "Zildjian 13\" K Custom Dark Hi Hat Cymbals",

            "price": 414.95

          }

        ]

      }

    ]

  }

}

查询的嵌套没有限制,全看我们的查询和server的实现。比如西面的例子完全合法:

{

  purchases(client\_id: 1) {

    date

    quantity

    total

    product {

      name

      price

      product\_category {

        name

      }

    }

    client {

      name

      dob

    }

  }

}

这里我们请求server返回某个客户的purchases。查询里不仅指定了purchase的字段,还指定了相关的productproduct\_category的名称。

GraphQL有非常重要的一个特点:**强类型**

每一个GraphQL server都要定义类型系统。查询实在这个类型系统的上下文中执行的。

也就是说,你可以查询值类型:Int, Float, String, BooleanID

而上例中的purchase里的字段,productclientproduct\_category都是**对象类型**(Object Type)的。这些类型都需要我们自己定义。

由于GraphQL查询都是结构化的,信息也是类树结构展示的。*值类型*(Scalar Type)的可以理解为叶子,对象类型(Object Type)可以理解为树干。

操作(Operation)和字段别名

在GraphQL查询中可以为Operation里的字段指定别名。比如查询里指定了字段cymbal\_size,但是客户端只能接受diameter。另外查询的返回结果都包含在以operation名称为key的对象里,所以这个名称也可以设置一个别名:

{

  my\_product: product(id: 3) {

    id 

    name

    diameter: cymbal\_size

  }

}

返回的数据:

{

  "data": {

    "my\_product": {

      "id": "3",

      "name": "Zildjian 13\" K Custom Dark Hi Hat Cymbals",

      "diameter": "13\""

    }

  }

}

Fragments

现在,客户端APP要获取另个分开的list: drum setscymbals。在GraphQL里你不会被限制在一个operation里。同时我们也可以像设置字段别名那样设置返回结果的别名:

query {

  drumsets: product(product\_category\_id: 1) {

    id

    name

    manufacture

    price

    pieces

    shell\_size

    shell\_type

  }



  cymbals: products(product\_category\_id: 2) {

    id

    name

    manufacture

    price

    cymbal\_size

  }

}

你可能已经注意到,在查询的两个对象中都包含了字段:idnamemanufacturerprice

为了避免重复字段,我们可以使用GraphQL提供的**Fragments**。我们来把重复的字段都提出来,放到一个fragment里:

query {

  drumsets: products(product\_category\_id: 1) {

    ...ProductCommonFields

    prices

    shell\_size

    shell\_type

  }



  cymbals: products(product\_category\_id: 2) {

    ...ProductCommonFields

    cymbal\_size

  }

}



fragment ProductCommonFields on Product {

  id

  name

  manufacture

  price

}

要使用一个*Fragment*就使用操作符:...

变量(Variable)

我们要减少查询语句中的重复,我们来看看另外的一个例子该如何处理:

client(id: 1) {

  name

  dob

}



purchasses(client\_id: 1) {

  date

  quantity

  total

  product {

    name 

    price

    product\_category {

      name

    }

  }

  client {

    name 

    dob 

  }

}

我们使用两个operation查询server,并且每个都包含了client\_id参数。如果可以把这个集中到一起就非常好了。我们可以使用GraphQL的**变量**来实现这个效果。我们来添加一个clientID变量。

query($clientId: Int) {

  client(id: $clientId) {

    name

    dob

  }



  purchases(client\_id: $clientId) {

    date

    quantity

    total

    product {

      name

      price

      product\_category {

        name

      }

    }

    client {

      name

      dob

    }

  }

}

我们在operation的前面定义了变量,然后我们就可以在整个查询中使用这个变量了。 **为了使用变量的定义,我们需要在查询的时候附带变量值的JSON**。

{

  "clientId": 1

}

当然,我们也可以指定一个默认值:

query ($date: String = "2017/01/28") {

  purchases(date: $date) {

    date

    quantity

    total

  }

}

Mutation(修改)

GraphQL不仅可以用来查询数据,也可以创建、更新和销毁数据。当然和查询一样,这些也需要server端有对应的实现。增、删、改一类的operation在GraphQL里统称为Muration(修改)。我们就通过几个例子来演示一下mutation。

mutation {

  create\_client (

    name: "查理大叔"

    dob: "2017/01/28"

  ) {

    id 

    name

    dob

  }

}

我们现在指定operation的类型为**mutation**,而不是**query**。在create\_client操作里我们传入了创建一个client需要的数据,并最终返回一个查询集合:

{

  "data": {

    "create\_client": {

      "id": "5",

      "name": "查理大叔",

      "dob": "2017/01/28"

    }

  }

}

上面的数据有一点错误,生日不对。下面就来用更新来fix这个错误:

mutation {

  update\_client (

    id: 5

    dob: "1990/01/01"

  ) {

    id

    name

    dob

  }

}

最后,如果我们要删除这个数据可以这样:

mutation {

  destroy\_client(id: 5) {

    name 

    dob

  }

}

**注意:**create\_clientupdate\_clientdestroy\_client这些operation都是在GraphQL server实现好的。如果有什么方法可以知道GraphQL server都实现了什么方法不是很好,是的有GraphQL的*doc*可以查看。

定义说明

GraphQL的一个非常好的特性就是,它会根据已经定义好的类型系统来自动生成出一个说明文档。这样你就不用一次一次的翻看代码,而直接查看文档来了解operation的全部实现细节。如果你用的是express-graphql, 并设置graphiqltrue的话,那么就会生成一个web的调试界面。在最右侧可以直接使用doc:

app.use('/mobile/egoods', graphqlHTTP({

  schema: schema,

  rootValue: root,

  graphiql: true,

  pretty: IS\_DEVELOPMENT,

}))

或者,也可以使用对于应定义好的schema的查询,如:

{

  \_\_schema {

    queryType {

      name 

      fields {

        name

      }

    }

  }

}

结果为:

{

  "data": {

    "\_\_schema": {

      "queryType": {

        "name": "Query",

        "fields": [

          {

            "name": "client"

          },

          {

            "name": "clients"

          },

          {

            "name": "product"

          },

          {

            "name": "product\_categories"

          },

          {

            "name": "product\_category"

          },

          {

            "name": "products"

          }

        ]

      }

    }

  }

}

对于mutation类型的操作也是一样的:

{

  \_\_schema {

    mutationType {

      name

      fields {

        name

      }

    } 

  }

}

查询的结果为:

{

  "data": {

    "\_\_schema": {

      "mutationType": {

        "name": "Mutation",

        "fields": [

          {

            "name": "create\_client"

          },

          {

            "name": "destroy\_client"

          },

          {

            "name": "update\_client"

          }

        ]

      }

    }

  }

}

就像上文展示的一样,你还可以查询很多其他的内容。比如:

{

  \_\_schema {

    queryType {

      name

      fields {

        name

        args {

          name

        }

      }

    }

  }

}

我们来简单的看看结果是什么样的:

{

  "data": {

    "\_\_schema": {

      "queryType": {

        "name": "Query",

        "fields": [

          {

            "name": "clients",

            "args": [

              {

                "name": "ids"

              },

              {

                "name": "name"

              },

              {

                "name": "dob"

              }

            ]

          },

          {

            ...

          },

          {

            "name": "products",

            "args": [

              {

                "name": "ids"

              },

              {

                "name": "product\_category\_id"

              },

              {

                "name": "order"

              },

              {

                "name": "limit"

              }

            ]

          }

        ]

      }

    }

  }

}

你会看到server实现了一个clients的查询operation,参数为idsnamedob。第二个操作是products,在这里的参数是idsproduct\_category\_idorderlimit

最后

GraphQL可以让我们定义更加便捷的查询Server。如果你有兴趣学习的话,我强烈的建议你可以读一读GraphQL的定义说明,然后试着自己实现一个GraphQL server。

原文链接:https://icannotremember.com

原文作者:some guy

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏大内老A

SQLXML初体验:用XML代替T-SQL来操作数据库

随着Internet的飞速发展,W3C成员意识到必须找到一种办法将数据和Web的表现方式分离出来,于是XML诞生了。当今的XML已经成为IT领域各个数据(特别是...

3866
来自专栏Golang语言社区

golang-xorm库快速学习

xorm xorm是一个Go语言ORM库. 通过它可以使数据库操作非常简便. 全部文档点我 用法入门: 前提:定义本文中用到的struct和基本代码如下 // ...

5978
来自专栏决胜机器学习

PHP数据结构(十九) ——B+树

PHP数据结构(十九)——B+树 (原创内容,转载请注明来源,谢谢) 一、概述 B+树是B树的变种,在数据库系统、文件系统等方面,B+树的运用...

4376
来自专栏蘑菇先生的技术笔记

探索c#之不可变数据类型

1894
来自专栏linux驱动个人学习

高通Audio中ASOC的codec驱动(二)

继上一篇文章:高通Audio中ASOC的machine驱动(一) ASOC的出现是为了让codec独立于CPU,减少和CPU之间的耦合,这样同一个codec驱动...

6535
来自专栏iOS开发攻城狮的集散地

iOS CoreData (一) 增删改查

选择Arguments,在下面的ArgumentsPassed On Launch中添加下面两个选项,如图:

1977
来自专栏Kubernetes

runC源码分析——namespace

runc/libcontainer/configs/config.go中定义了container对应的Namespaces。另外对于User Namespace...

2788
来自专栏更流畅、简洁的软件开发方式

【自然框架】表单控件 之 一个表单修改多个表里的记录

      FormView 确实挺方便的,不过他也有几个小问题,只把FormView拖到页面里是不行的,还得再拽几个文本框、下拉列表框这一类的控件,还得布局。...

1906
来自专栏Spring相关

快速初步了解Neo4j与使用

Neo4j是一个高性能的,NOSQL图形数据库,它将结构化数据存储在网络上而不是表中。它是一个嵌入式的、基于磁盘的、具备完全的事务特性的Java持久化引擎,但是...

991
来自专栏芋道源码1024

数据库中间件 Sharding-JDBC 源码分析 —— 结果归并

本文主要基于 Sharding-JDBC 1.5.0 正式版 1. 概述 2. MergeEngine 2.2.1 AbstractStreamResultSe...

3348

扫码关注云+社区