初步了解graphQL(二)

上一篇大概只写了最简单的一部分,像是请求结构什么的,这一篇继续写一点相关的东西。

个人感觉,这套规范如果是从头开始使用的话倒还是很好的,相比 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 是用来传递请求并且返回结果到页面上的一个组合工具,但一般只在调试和开发过程中使用,生产环境下默认被禁用。