我们知道 GraphQL 使用 Schema 来描述数据,并通过制定和实现 GraphQL 规范 定义了支持 Schema 查询的 DSQL (Domain Specific Query Language,领域特定查询语言)。Schema 帮助将复杂的业务模型数据抽象拆分成细粒度的基础数据结构,而 DSQL 的实现则赋予了前端开发者自由组织和定制请求数据的能力。如果以一张图来表示的话,可以将 GraphQL 看做一条以 通用基础业务数据模型 为基础、将传统后端服务和前端页面紧密且自由地联系在一起的纽带。

为什么 GraphQL 的 Schema 能够表示出服务器所支持的复杂业务模型数据,GraphQL 的 Query 又是怎样赋予前端开发者对数据的定制能力,本文将通过分析和理解 GraphQL 的设计来和大家一起探讨解答这些问题。

1.GraphQL 的设计

GraphQL 由以下组件构成:

  • 类型系统(Type System)
  • 查询语言(Query Language)
  • 执行语义(Execution Semantics)
  • 静态验证(Static Validation)
  • 类型检查(Type Introspection)

作为将数据模型和具体接口实现解耦的 DSL,GraphQL 的基础组件,也是它最重要的组件之一就是类型系统。

1.1 类型系统

可以将 GraphQL 的类型系统分为标量类型(Scalar Types,标量类型)和其他高级数据类型,标量类型即可以表示最细粒度数据结构的数据类型,可以和 JavaScript 的原始类型对应。GraphQL 规范目前规定支持的标量类型有:

  • Int :整数,对应 JavaScript 的 Number
  • Float :浮点数,对应 JavaScript 的 Number
  • String :字符串,对应 JavaScript 的 String
  • Boolean :布尔值,对应 JavaScript 的 Boolean
  • ID :ID 值,是一个序列化后值唯一的字符串,可以视作对应 ES 2015 新增的 Symbol

Scalar Types 的 JavaScript 参考实现代码可以查看 这里 。

其他高级数据类型包括:

  • Object :对象

    用于描述层级或者树形数据结构。对于树形数据结构来说,叶子字段的类型都是标量数据类型。几乎所有 GraphQL 类型都是对象类型。Object 类型有一个 name 字段,以及一个很重要的 fields 字段。fields 字段可以描述出一个完整的数据结构。例如一个表示地址数据结构的 GraphQL 对象为:

    const AddressType = new GraphQLObjectType({
    name: 'Address',
    fields: {
    street: { type: GraphQLString },
    number: { type: GraphQLInt },
    formatted: {
    type: GraphQLString,
    resolve(obj) {
    return obj.number + ' ' + obj.street
    }
    }
    }
    });
  • Interface :接口

    接口用于描述多个类型的通用字段,例如一个表示实体数据结构的 GraphQL 接口为:

    const EntityType = new GraphQLInterfaceType({
    name: 'Entity',
    fields: {
    name: { type: GraphQLString }
    }
    });
  • Union :联合

    联合类型用于描述某个字段能够支持的所有返回类型以及具体请求真正的返回类型,例如一个表示宠物(可以是猫或者狗)的 GraphQL 联合类型为:

    const PetType = new GraphQLUnionType({
    name: 'Pet',
    types: [DogType, CatType],
    resolveType(value) {
    if (value instanceof Dog) {
    return DogType;
    }
    if (value instanceof Cat) {
    return CatType;
    }
    }
    });
  • Enum :枚举

    用于表示可枚举数据结构的类型,例如表示 RGB 色值的 GraphQL 枚举类型为:

    const RGBType = new GraphQLEnumType({
    name: 'RGB',
    values: {
    RED: { value: 0 },
    GREEN: { value: 1 },
    BLUE: { value: 2 },
    }
    });
  • Input Object :输入对象

    是为了查询(query)而定义的数据类型,不直接重用 Object 类型是因为 Object 的字段可能存在循环引用,或者字段引用了不能作为查询输入对象的接口和联合类型。参考实现中 Input Object 的定义代码为:

    export type GraphQLInputType =
    GraphQLScalarType |
    GraphQLEnumType |
    GraphQLInputObjectType |
    GraphQLList<GraphQLInputType> |
    GraphQLNonNull<
    GraphQLScalarType |
    GraphQLEnumType |
    GraphQLInputObjectType |
    GraphQLList<GraphQLInputType>
    >; export function isInputType(type: ?GraphQLType): boolean {
    const namedType = getNamedType(type);
    return (
    namedType instanceof GraphQLScalarType ||
    namedType instanceof GraphQLEnumType ||
    namedType instanceof GraphQLInputObjectType
    );
    }

    可以看到,Object、Interface 和 Union 三种类型是不能作为输入对象类型的。

  • List :列表

    列表是其他类型的封装,通常用于对象字段的描述。例如下面 PersonType 类型数据的 parents 和 children 字段:

    const PersonType = new GraphQLObjectType({
    name: 'Person',
    fields: () => ({
    parents: { type: new GraphQLList(Person) },
    children: { type: new GraphQLList(Person) },
    })
    });
  • Non-Null :不能为 Null

    Non-Null 强制类型的值不能为 null,并且在请求出错时一定会报错。可以用于必须保证值不能为 null 的字段。例如数据库的行的 id 字段不能为 null:

    const RowType = new GraphQLObjectType({
    name: 'Row',
    fields: () => ({
    id: { type: new GraphQLNonNull(GraphQLString) }
    })
    });

还有一种重要的数据类型,即 schema 类型,它描述了后端服务器能够提供的数据支持。这里先暂时不介绍,因为它涉及 GraphQL 的其他组件,等全部介绍完我们再来看 GraphQL 中 schema 的 具体实现 。

1.2 查询语言

类型系统对应我们开头提到的 Schema,是对服务器端数据的描述,而查询语言则解耦了前端开发者与后端接口的依赖。前端开发者利用查询语言可以自由地组织和定制系统能够提供的业务数据。

GraphQL 的一个查询请求被称为一份 query 文档(query document),即 GraphQL 服务能够解析验证并执行的一串请求字符串。query 由操作(Operation)和片段(Fragments)组成。一个 query 可以包含多个操作和片段。只有包含操作的 query 才会被 GraphQL 服务执行。但是不包含操作,只有片段的 query 也会被 GraphQL 服务解析验证,这样一份片段就可以在多个 query 文档内使用。

只包含一个操作的 query 可以不带操作名称或者使用简写形式(即 query 关键字加操作名)。query 包含多个操作时,所有操作都必须带上名称。

操作(Operations)

GraphQL 规范支持两种操作:

  • query:仅获取数据(fetch)的只读请求
  • mutation:获取数据后还有写操作的请求

在官方提供的参考实现中我们会发现还支持一种操作 subscription ,这是为了处理订阅更新这种比较复杂的实时数据更新场景而设计的操作,不过目前这种操作还处于试验阶段,不建议在生产环境中使用。

查询请求的模型可以用下面的图来表示:

选择集合(Selection Sets)

选择集合表示当前选中的数据内容,格式为:

{
Field // 字段名
FragmentSpread // 片段展开
InlineFragment // 内联片段
}

关于选择集合的使用,可以参考 graphql-js 的代码 。参考实现代码在 这里 。

字段(Field)

字段格式为:

alias:name(argument:value)

其中 alias 是字段的别名,即结果中显示的字段名称。

name 为字段名称,对应 schema 中定义的 fields 字段名。

argument 为参数名称,对应 schema 中定义的 fields 字段的参数名称。

value 为参数值,值的类型对应标量类型的值。

例如这样的请求: http://yunhe.taobao.com/?query={banner{backgroundURL:bg,biaoti:slogan}} 
backgroundURL 就是 bg 字段的别名。

片段(Fragment)

片段是 GraphQL 的主要组合数据结构,通过片段可以重用重复的字段选择,减少 query 中的重复内容。片段又分为 FragmentSpread 和 InlineFragment。例如没有片段时需要这样编写 query:

query noFragments {
user(id: 4) {
friends(first: 10) {
id
name
profilePic(size: 50)
}
mutualFriends(first: 10) {
id
name
profilePic(size: 50)
}
}
}

query 中存在下列重复的选择集合:

{
id
name
profilePic(size: 50)
}

可以用片段简化为:

query withFragments {
user(id: 4) {
friends(first: 10) {
...friendFields
}
mutualFriends(first: 10) {
...friendFields
}
}
} fragment friendFields on User {
id
name
profilePic(size: 50)
}

使用片段时需要加上 ... 操作符表示展开片段内容。

内联片段示例如下:

query inlineFragmentTyping {
profiles(handles: ["zuck", "cocacola"]) {
handle
... on User {
friends {
count
}
}
... on Page {
likers {
count
}
}
}
}

指令(Directives)

指令要解决的是 query 执行时字段参数无法覆盖的情况,例如引入或者忽略某个字段。指令为 GraphQL 执行添加了更多的信息。

指令实例如下:

query hasConditionalFragment($condition: Boolean) {
...maybeFragment @include(if: $condition)
} fragment maybeFragment on Query {
me {
name
}
}

include 指令表示只有在 if 参数为 true 时才引入片段表示的字段。

skip 指令表示在 if 参数为 true 时忽略片段中的字段。

熟悉了 类型系统 和 查询语言 我们就可以用 GraphQL 来实现应用层的数据请求了。其他三个 GraphQL 组件更偏向于 DSL 的实现和原理,因此本文不再做详细介绍,感兴趣的同学可以对照 规范 和 参考实现 自己研究。

2.总结

GraphQL 是在应用层对业务数据模型的抽象,是对数据请求定制的 DSQL,它解除了接口和数据之间的绑定,对业务数据结构做了抽象和整理,业务逻辑中的数据依赖于底层数据库结构,并且可以由具体业务场景来定制,不同的业务场景只要基于同样一套基础业务数据模型就可以得到复用,在我看来,这才是 GraphQL 带来的最大改变和收益。

graphql 新API 开发方式的更多相关文章

  1. 一种不错的 BFF Microservice GraphQL/REST API 层的开发方式

    云原生(Cloud Native)Node JS Express Reactive 微服务模板 (REST/GraphQL) 这个项目提供了完整的基于 Node JS / Typescript 的微服 ...

  2. 【转】RestQL:现代化的 API 开发方式

    原文:http://tech.meituan.com/koa-restql.html RestQL:现代化的 API 开发方式 李鑫 ·2016-08-12 11:26 koa-restql 已经在  ...

  3. GraphQL&DSL&API网关

    车联网服务non-RESTful架构改造实践   导读 在构建面向企业项目.多端的内容聚合类在线服务API设计的过程中,由于其定制特点,采用常规的restful开发模式,通常会导致大量雷同API重复开 ...

  4. Odoo 二次开发教程(五)-新API的介绍与应用

    [关于odoo新API的介绍,Internet上资料很少,或者不够完整详实,这会对初学者造成很大的困惑,本篇的目的就是希望能帮助新手了解新API的大概] odoo 新api的实现是借助于python装 ...

  5. ES6的新API如Promise,Proxy,Array.form(),Object.assign()等,Babel不能转码, 使用babel-polyfill来解决

    Babel默认只转换新的JavaScript句法(syntax),而不转换新的API,比如Iterator.Generator.Set.Maps.Proxy.Reflect.Symbol.Promis ...

  6. Hadoop生态圈-Kafka的新API实现生产者-消费者

         Hadoop生态圈-Kafka的新API实现生产者-消费者 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任.

  7. 放弃winform的窗体吧,改用html作界面,桌面应用程序UI的新的开发方式。

    做过很多winform项目,都为winform控件头疼不已.想实现一些漂亮的样子总是很难.我这里列举几个缺点: 1.winform控件大多是 绝对布局 ,你需要给出准确的坐标.那么在实现居中效果就会很 ...

  8. GraphQL & REST API

    GraphQL & REST API GraphQL https://mp.weixin.qq.com/s/X-jm7jLXWmMmLBVgHfkRiQ https://webapplog.c ...

  9. hbase java api样例(版本1.3.1,新API)

    hbase版本:1.3.1 目的:HBase新API的使用方法. 尝试并验证了如下几种java api的使用方法. 1.创建表 2.创建表(预分区) 3.单条插入 4.批量插入 5.批量插入(客户端缓 ...

随机推荐

  1. [css]【转载张鑫旭】我是如何对网站CSS进行架构的

    一.写在前面的 都是自己积累形成的一些东西,可能带有明显的个人印记.不是专业内容,不是权威指南,只是展示一点自己的观点,借此希望能与各位优秀的同行交流看法,见解.以得到进步与提高. 二.我所知的一些过 ...

  2. 解决:“java.lang.IllegalArgumentException: error at ::0 can't find referenced pointcut myMethod”问题!

    Spring版本:2.5.6 AspectJ是Spring自带的lib. Jdk版本:1.7.0_17 在配置没问题的情况下,报:java.lang.IllegalArgumentException: ...

  3. express-generator安装时出错,最后用VPS解决

    npm install -g express-generator npm ERR! Linux 3.10.0-229.el7.x86_64npm ERR! argv "/usr/local/ ...

  4. golang获取数据表转换为json通用方法

    package main import ( "database/sql" "fmt" "log" "net/http" ...

  5. Probit回归模型

    Probit模型也是一种广义的线性模型,当因变量为分类变量时,有四种常用的分析模型: 1.线性概率模型(LPM)2.Logistic模型3.Probit模型4.对数线性模型 和Logistic回归一样 ...

  6. 终于解决了IE8不支持数组的indexOf方法,array的IndexOf方法

    /* 终于解决了IE8不支持数组的indexOf方法 */ if (!Array.prototype.indexOf) { Array.prototype.indexOf = function (el ...

  7. C#泛型List的用法

    C#泛型List的用法 来源:C#学习    发布时间:2014/1/4 一.List<T>命名空间: System.Collections.Generic(程序集:mscorlib) 二 ...

  8. bzoj1211: prufer序列 | [HNOI2004]树的计数

    题目大意: 告诉你树上每个节点的度数,让你构建出这样一棵树,问能够构建出树的种树 这里注意数量为0的情况,就是 当 n=1时,节点度数>0 n>1时,所有节点度数相加-n!=n-2 可以通 ...

  9. iOS移动下上传图片失败解决 (上传多图,带其他参数)

    项目中有一个主要的功能,就是上传图片,结结果移动真的是很奇怪,WiFi,联通,电信都没有问题的情况下,居然在移动下不行,真的是很头疼.不过好在最后是解决了 项目的网络请求我是采用ASIHttpRequ ...

  10. win7下用python3.3获取cable modem的设备信息

    毕业一年多了,一直做cable modem的测试,总是觉得在国内这一行的人才很少,想找个师傅真的很不容易. 苦闷了许久之后,终于决定,自己去写点东西,万一就找到同行了呢? 下面就是本小姐写的第一篇博客 ...