上一篇大概只写了最简单的一部分,像是请求结构什么的,这一篇继续写一点相关的东西。
个人感觉,这套规范如果是从头开始使用的话倒还是很好的,相比 REST 请求有很多好处。但是对于不少后端结构已经很成熟的企业,改造过程可能就比较难了——可以说是前端爽了,后端苦了。因为后端要提前为前端把数据格式之类的都配置好,工作量其实不小。
基本上就是,后端会预先把可能请求的数据结构描述出来,前端访问的时候无非是字段之间的拼接组合,按照自己的需求返回即可。
类型语言 Schema Language
type Character {
name: String!
appearsIn: [Episode!]!
}
感觉有点像 golang 的结构体——先写字段名称,再写字段类型。类型名后加 ! 表示字段非空,用中括号 [] 表示数组。
字段也可以带参数:
type Starship {
id: ID!
name: String!
length(unit: LengthUnit = METER): Float
}
查询与变更类型(Query / Mutation)
GraphQL 中有一些预定义的标量类型,比如 Int、String、Float、Boolean、ID 等,也可以自己定义字段类型。
简单来说,数据的流向是:首先同步到数据库,然后数据库内容同步到 Elasticsearch,之后 GraphQL 会对每个字段进行解析,主要是解析字段的名称和类型。
请求方式
大致的访问方式和 REST 差不多,但 GraphQL 似乎只有读(query)和写(mutation)两种操作。向 URL 传入的请求就是一个字符串,用大括号之类的符号进行分隔。
片段的自身引用是不可以的,会造成结果无边界。
查询字段只能查询类型上已定义的字段,否则会报错。
如果返回的类型不是标量或枚举类型,也就是说是一个对象,那么需要指明具体需要的字段——返回一个整体的话,必须列出所有字段,不能只返回对象本身。(前端一定要知道自己需要哪些字段,后端一定要提前按需求配好,不存在”不确定”的情况。)
如果一个字段是标量,那么它内部就不会再有字段,这种情况下用括号去请求也没有作用。
条件片段(Fragment)
在请求中实现可选字段,可以用带成立条件的片段:
hero {
name
...DroidFields
}
fragment DroidFields on Droid {
primaryFunction
}
上面的片段只有在 Character 为 Droid 的时候才会在请求中成立,其他情况下是隐藏的。
不过这样使用具名字段有些浪费,不如直接使用内联片段:
hero {
name
... on Droid {
primaryFunction
}
}
这样只使用一次,避免浪费。
字段执行与解析
字段执行 → 相应的 resolver 被调用产生一个值。
如果字段产生的是标量值,执行完成;如果字段产生的是对象,则查询会继续执行该对象对应字段的解析器,直到生成标量值为止。
内省系统(Introspection)
可以通过内省系统来查询支持哪些查询。这样一来,即使不是自己写的 schema,也能知道它支持哪些查询:
{
__schema {
types {
name
}
}
}
# 查询开始的地方
{
__schema {
queryType {
name
}
}
}
# 查询特定的类型
{
__schema {
queryType {
name
kind
}
}
}
# 查询类型的文档
{
__type(name: "Droid") {
name
description
}
}
其他要点
- GraphQL 通过单一入口提供完整的 HTTP 服务功能,和 REST API 的多端点不同。
- JSON 用 GZIP 压缩后表现非常好。
- 默认每个字段都可以为
NULL。 - 服务器端可以做批处理(batching)和缓存。
- 本质上就是一个图(Graph)结构。
- 不要尝试一次性把整个模型都建立出来,应该在测试中逐渐扩展。
GraphiQL 与 REST API
GraphiQL 是用来传递请求并且返回结果到页面上的一个组合工具,但一般只在调试和开发过程中使用,生产环境下默认被禁用。