Lex's Blog

漫谈 GraphQL

本篇文章不会去过多的讨论技术细节,只是从核心概念上去说明 GraphQL 解决了什么问题,是如何怎么做到的

GraphQL 设计思路

GraphQL 是什么?

现在只需要知道,对于前端来说,它是一种资源请求方式,可以用来代替 RESTful 风格的 API 请求。

那为什么要去代替 RESTful 的 API 请求呢?因为遇到了一些问题

  1. 数据获取
  2. 数据校验

数据获取

前端的多个功能可能会依赖一份数据源的不同部分

  1. 后端需要频繁的修改字段获取逻辑
  2. 不管前端功能中实际用到了几个字段,接口都会返回所有的字段
    • 对于字段很多的接口,网络传输的效率会有很大差别

按需获取字段

把需要的字段传进去

获取不同表的字段

把表名传进去

同时查询不同的表

重构传输结构

不同的数据源 & 数据处理

数据源:数据库、redis、ES、RPC 等等

数据处理:多数情况从数据源拿到的数据,还需要进行逻辑处理才会返回

嵌套查询 / 子查询

子查询:先执行父级的查询,再用父级的信息查找子级

重构传输结构

封装、分层

数据校验

拿到的数据和 API 文档不一致

  • 文档写的是 number,返回的是 string
  • 文档写的不会为空,返回了空
    • 99% 不为空,1% 为空

可不可以如果返回的和需要的不一致,就直接报错,接口 500,这样可以更快的定位问题(更好的甩锅

问题:

  • 数据类型是数据源决定的,前端是决定不了的,所以不应该前端去写(写了反而再坑后端)

类型定义

总结

GraphQL 官方设计

GraphQL | A query language for your API

类型定义

内置根类型

Query 类型定义Query 查询

字段解析

resolves  中找到对应  type  的各字段解析方法,如果字段的类型不是标量类型(基础类型),就递归对 type  进行解析

内置标量类型:Int、Float、String、Boolean、ID

N + 1 问题

考虑上述代码示例,如果 1 条回答中,有 20 条评论,各 fetch  方法分别会被调用多少次?

fetchAnswer: 1
fetchReviews: 1
fetchUser: 21

如果我们查询的是 1 个回答列表,列表中有 20 条回答,每个回答有 20 条评论,上述方法有会被调用多少次?

fetchAnswer: 1
fetchReviews: 20
fetchUser: 420

问题

  1. 过多的 IO
  2. 重复请求

遇事不决加抽象

简单实现

const arr = new Set()
let flag = true

const fetchUser = (user_id) => {
  arr.add(user_id)

  if (flag) {
    flag = false

    setTimeout(() => {
      batch_fetch_user([...arr])

      arr.clear()
      flag = true
    })
  }
}

封装

class Loader {
  constructor(callback) {
    this.arr = new Set()
    this.flag = true
    this.callback = callback
  }

  load(key) {
    this.arr.add(key)

    if (this.flag) {
      this.flag = false

      setTimeout(() => {
        this.callback([...this.arr])

        this.arr.clear()
        this.flag = true
      })
    }
  }
}

DataLoader

const DataLoader = require("dataloader")

const userLoader = new DataLoader((ids) => {
  console.log(ids)
  // `SELECT * FROM user WHERE id IN (${ids.join(',')})`
  // rpc.client.fetchUsers(ids)
  return Promise.resolve(["123"])
})

Array(20)
  .fill(1)
  .map((_, i) => userLoader.load(i))

Array(20)
  .fill(1)
  .map((_, i) => userLoader.load(0))

总结

  • 解析字段时都应该使用 dataloader
  • 要求后端提供批量查询的 RPC 方法

Code First Schema

类型语言的问题

重复的写 schema 和 类型(结构)定义,并且两者又是很像的

GraphQL Schema

type Answer {
  id: Int
  content: String
}

Go

type Answer struct {
  id int
  content string
}

Rust

struct Answer {
  id: i32
  content: string
}

TS

class Answer {
  id: Number
  content: string
}

Schema First

先写 schema,生成类型(结构)

Code First

先写类型(结构),生成 schema

调用式

结构式

JS

libstarmodal
graphql-js19.7k两种都支持:code first(调用式)
apollo-server13.5kschema first
type-graphql7.9kcode first(结构式)
graphql-yoga7.8kschema first
nestjs60.4k / 1.3k两种都支持:code first(结构式)

Go

libstarmodal
graphql-go9.5kcode first(调用式)
gqlgen9.3kschema first

Rust

libstarmodal
juniper5.3kcode first(结构式)
async-graphql3kcode first(结构式)

觉得有帮助?给个 Star 支持一下吧!

Star on GitHub

On this page