GraphQL

一、什么是GraphQL

GraphQL 是一个用于 API 的查询语言,是一个使用基于类型系统来执行查询的服务端运行时(类型系统由你的数据定义)。GraphQL 并没有和任何特定数据库或者存储引擎绑定,而是依靠你现有的代码和数据支撑,GraphQL 可以运行在任何后端框架或者编程语言之上。

GraphQL 全称叫 Graph Query Language,官方宣传语是“为你的 API 量身定制的查询语言”。

用传统的方式来解释就是:相当于将你所有后端 API 组成的集合看成一个数据库,用户终端发送一个查询语句,你的 GraphQL 服务解析这条语句并通过一系列规则从你的“ API 数据库”里面将查询的数据结果返回给终端,而 GraphQL 就相当于这个系统的一个查询语言。

二、存在的问题:

REST API :服务端决定有哪些数据返回,客户端只能挑选使用,如果数据过于冗余也只能默默接收再对数据进行处理;而数据不能满足需求则需要请求更多的接口。以上就是我们常说的“过渡获取”和“欠缺获取”

由于"过度"和"欠缺"的获取问题及其对客户端应用程序性能的影响,促进有效获取的 API 技术才有机会在市场上引起轰动 —— GraphQL 大胆地介入并填补了这一空白。

举个简单的例子

graphQL 查询语句

{
  getCategories {
    id
    name
    products {
      id
      name
    }
  }
}

输出结果

{
  "data": {
    "getCategories": [
      {
        "id": "1",
        "name": "吃的",
        "products": [
          {
            "id": "1",
            "name": "浪味仙"
          }
        ]
      },
      {
        "id": "2",
        "name": "喝的",
        "products": [
          {
            "id": "4",
            "name": "茶颜悦色"
          }
        ]
      }
    ]
  }
}

客户端现在不想要商品信息了 只需要获取商品分类,你只需要将你的查询语句修改一下即可

{
  getCategories {
    id
    name
  }
}

输出结果

{
  "data": {
    "getCategories": [
      {
        "id": "1",
        "name": "吃的"
      },
      {
        "id": "2",
        "name": "喝的"
      }
    ]
  }
}

现在我想要通过这一个查询,获取到商品分类,以及所有商品的信息

{
  getCategories {
    id
    name
   
  }
  getProducts {
    id
    name
  }
}

输出结果

{
  "data": {
    "getCategories": [
      {
        "id": "1",
        "name": "吃的"
      },
      {
        "id": "2",
        "name": "喝的"
      }
    ],
    "getProducts": [
      {
        "id": "1",
        "name": "浪味仙"
      },
      {
        "id": "4",
        "name": "茶颜悦色"
      }
    ]
  }
}

是不是很方便,通过上面例子,可以发现GraphQL API有如下优点:客户端可以自定义查询语句,数据获取灵活多变,服务端按需返回数据,减少网络的开销,提高了性能。而这些都是Restful API的弊端。

三、基础概念

在介绍GraphQL的使用之前先要了解一些概念

1.GraphQL 类型系统(Type System)

类型系统 是整个 GraphQL 的核心,它用来定义每个查询对象和返回对象的类型。GraphQL的Type简单可以分为两种,一种叫做Scalar Type(标量类型),另一种叫做Object Type(对象类型)。

a)标量类型(Scalar Types)

一个对象类型有自己的名字和字段,而某些时候,这些字段必然会解析到具体数据。这就是标量类型的来源:它们表示对应 GraphQL 查询的叶子节点。

标量类型这个概念有点重要,这个字段是不是一个标量类型,决定了我们这个查询是不是还要继续往更深层去递归查询。

GraphQL 自带一组默认标量类型:
GraphQLInt:有符号 32 位整数。
GraphQLFloat:有符号双精度浮点值。
GraphQLString:UTF‐8 字符序列。
GraphQLBoolean 或者 false。
GraphQLID:ID 标量类型表示一个唯一标识符。

b)对象类型

一个 GraphQL schema 中的最基本的组件是对象类型,表示你可以从服务上获取到什么类型的对象,以及这个对象有什么字段。

例如 以下这段代码中 id 、name 就是标量类型。products就是一个对象类型

 type categories {
    id
    name
    products {
      id
      name
    }
b)Schema

在 GraphQL 中,类型的定义以及查询本身都是通过 Schema 去定义的。
Schema它是用来描述接口获取数据逻辑的。我们可以将Schema理解为多个Query组成的一张表。

这里又涉及一个新的概念Query,GraphQL中使用Query来抽象数据的查询逻辑,当前标准下,有三种查询类型,分别是query(查询)、mutation(更改)和subscription(订阅)。

query(查询):当获取数据时,应当选取Query类型
mutation(更改):当尝试修改数据时,应当使用mutation类型
subscription(订阅):当希望数据更改时,可以进行消息推送,使用subscription类型

c)Resolver

我们已经了解了graphQL的类型系统和schema,那么我们的数据到底怎么来呢?答案是来自 Resolver 函数。

Resolver 的概念非常简单。Resolver 对应着 Schema 上的字段,当请求体查询某个字段时,对应的 Resolver 函数会被执行,由 Resolver 函数负责到数据库中取得数据并返回,最终将请求体中指定的字段返回。

type Movie {
    name
    genre
}
type Query {
    movie: Movie!
}

当请求体查询movie时,同名的 Resolver 必须返回Movie类型的数据。当然你还可以单独为name字段使用独立的 Resolver 进行解析。

以上是一些最基本的概念,最后我们来详细的分析一下,graphQL是如何定义并查询到数据的

服务端的schema:

//schema.js

let categories = [
    { id: '1', name: '吃的' },
    { id: '2', name: '喝的' },
]
let products = [
    { id: '1', name: '浪味仙', category: '1' },
    { id: '4', name: '茶颜悦色', category: '2' },
]
//分类里定义每个字段
const Category = new GraphQLObjectType({
    name: 'category',
    fields: () => ({
        id: { type: GraphQLString },
        name: { type: GraphQLString },
        products: {
            type: new GraphQLList(Product),//每个分类下是一个数组,而不是一个对象
            resolve(parent) {//parent代表上一层的查询结果
                return products.filter(item => item.category === parent.id)
            }
        }
    })
})
//产品里定义每个字段
const Product = new GraphQLObjectType({
    name: 'product',
    fields: () => ({
        id: { type: GraphQLString },
        name: { type: GraphQLString }
    })
})
//查询接口
const RootQuery = new GraphQLObjectType({
    name: 'root',
    fields: {
        getCategories: {
            type: new GraphQLList(Category),
            args: {
            },
            resolve(parent, args) {
                return categories
            }
        }
    }
})
module.exports = new GraphQLSchema({
    query: RootQuery,//查询
    mutation: RootMutation//修改
})

1.以上是一个schema文件, 导出了一个GraphQLSchema的实例,GraphQLSchema接收两个参数,一个是query,一个是mutation。
2.RootQuery是一个GraphQLObjectType实例,name属性非必填,这个属性是最后生成接口文档的query接口名字。
3.fields是解析函数,在这里可以理解为查询方法。
4.getCategories就是我们定义查询的名称了。
5.getCategories中有个type,定义了你使用getCategories能查询到的所有字段。当前查询结果是一个GraphQLList类型,也就是一个数组类型,数组的每一项就是Category。
6.resolve中的内容为查询返回的数据。

客户端对应的查询语法:

query{
  getCategories {
    id,
    name,
    products{
      id,
      name,
    }
  }
}

1.首先进行第一层解析,查询类型是query同时需要它的子query名是getCategories。
2.使用getCategories的Resolver获取解析数据,第一层解析完毕
3.之后对第一层解析的返回值,进行第二层解析,当前getCategories还包含3个属性需要查询,分别是id、name、product。
3.1 id和name在type Category中为标量类型,直接返回getCategories中的对应属性值。id,name的解析结束。
3.2product在type Category类型中为对象类型,于是Granphql尝试使用Category的Resolver获取数据,当前field解析完毕。以此类推,直到所有的查询值都为标量的时候,查询结束。
因此上例中的查询结果为:

{
  "data": {
    "getCategories": [
      {
        "id": "1",
        "name": "吃的",
        "products": [
          {
            "id": "1",
            "name": "浪味仙"
          }
        ]
      },
      {
        "id": "2",
        "name": "喝的",
        "products": [
          {
            "id": "4",
            "name": "茶颜悦色"
          }
        ]
      }
    ]
  }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,525评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,203评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,862评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,728评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,743评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,590评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,330评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,244评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,693评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,885评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,001评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,723评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,343评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,919评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,042评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,191评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,955评论 2 355