14 graphql学习笔记 - xiaoxin01/Blog GitHub Wiki

GraphQL核心概念

https://segmentfault.com/a/1190000014131950

mutation

定义

一种用于 API 的查询语言

GraphQL 既是一种用于 API 的查询语言也是一个满足你数据查询的运行时。 GraphQL 对你的 API 中的数据提供了一套易于理解的完整描述,使得客户端能够准确地获得它需要的数据,而且没有任何冗余,也让 API 更容易地随着时间推移而演进,还能用于构建强大的开发者工具。

入门

查询和变更

字段(Fields)

参数(Arguments)

别名(Aliases)

片段(Fragments)

操作名称(Operation Name)

变量(Variables)

指令(Directives)

变更(Mutations)

mutation定义的类型用input修饰,如

input AccountInput {
    id: ID
    Age: Int
    Name: String
}

type mutation {
    updateAccount(id: ID!, input: AccountInput): Account
}

注:graphql要求至少要有一个query,否则不会列出mutation方法

内联片段(Inline Fragments)

Schema 和类型

类型系统(Type System)

因为一个 GraphQL 查询的结构和结果非常相似,因此即便不知道服务器的情况,你也能预测查询会返回什么结果。但是一个关于我们所需要的数据的确切描述依然很有意义,我们能选择什么字段?服务器会返回哪种对象?这些对象下有哪些字段可用?这便是引入 schema 的原因。

类型语言(Type Language)

“GraphQL schema language” —— 它和 GraphQL 的查询语言很相似,让我们能够和 GraphQL schema 之间可以无语言差异地沟通。

对象类型和字段(Object Types and Fields)

type Character {
  name: String!
  appearsIn: [Episode!]!
}

参数(Arguments)

type Starship {
  id: ID!
  name: String!
  length(unit: LengthUnit = METER): Float
}

所有参数必须具名传递

查询和变更类型(The Query and Mutation Types)

schema {
    query: Query
    mutation: Mutation
}

每一个 GraphQL 服务都有一个 query 类型,可能有一个 mutation 类型。

标量类型(Scalar Types)

GraphQL 自带一组默认标量类型:

  • Int:有符号 32 位整数。
  • Float:有符号双精度浮点值。
  • String:UTF‐8 字符序列。
  • Boolean:true 或者 false。
  • ID:ID 标量类型表示一个唯一标识符,通常用以重新获取对象或者作为缓存中的键。ID 类型使用和 String 一样的方式序列化;然而将其定义为 ID 意味着并不需要人类可读型。

我们可以定义一个 Date 类型:

scalar Date

然后就取决于我们的实现中如何定义将其序列化、反序列化和验证。例如,你可以指定 Date 类型应该总是被序列化成整型时间戳,而客户端应该知道去要求任何 date 字段都是这个格式。

枚举类型(Enumeration Types)

enum Episode {
  NEWHOPE
  EMPIRE
  JEDI
}

列表和非空(Lists and Non-Null)

type Character {
  name: String!
  myField: [String!]
  myField2: [String]!
}

myField: [String!],这表示数组本身可以为空,但是其不能有任何空值成员。用 JSON 举例如下:

myField: null // 有效
myField: [] // 有效
myField: ['a', 'b'] // 有效
myField: ['a', null, 'b'] // 错误

myField2: [String]!,这表示数组本身不能为空,但是其可以包含空值成员:

myField2: null // 错误
myField2: [] // 有效
myField2: ['a', 'b'] // 有效
myField2: ['a', null, 'b'] // 有效

接口(Interfaces)

interface Character {
  id: ID!
  name: String!
  friends: [Character]
  appearsIn: [Episode]!
}

type Human implements Character {
  id: ID!
  name: String!
  friends: [Character]
  appearsIn: [Episode]!
  starships: [Starship]
  totalCredits: Int
}

type Droid implements Character {
  id: ID!
  name: String!
  friends: [Character]
  appearsIn: [Episode]!
  primaryFunction: String
}

如果要查询一个只存在于特定对象类型上的字段,你需要使用内联片段:

query HeroForEpisode($ep: Episode!) {
  hero(episode: $ep) {
    name
    ... on Droid {
      primaryFunction
    }
  }
}

联合类型(Union Types)

union-types

输入类型(Input Types)

input ReviewInput {
  stars: Int!
  commentary: String
}

你可以像这样在变更(mutation)中使用输入对象类型:

mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
  createReview(episode: $ep, review: $review) {
    stars
    commentary
  }
}
{
  "ep": "JEDI",
  "review": {
    "stars": 5,
    "commentary": "This is a great movie!"
  }
}

验证

validation

执行

GraphQL 查询中的每个类型的每个字段都由一个 resolver 函数支持,该函数由 GraphQL 服务器开发人员提供。当一个字段被执行时,相应的 resolver 被调用以产生下一个值。

如果字段产生标量值,例如字符串或数字,则执行完成。如果一个字段产生一个对象,则该查询将继续执行该对象对应字段的解析器,直到生成标量值。GraphQL 查询始终以标量值结束。

Human: {
  name(obj, args, context, info) {
    return obj.name
  }
}

在这个例子中,对 name 字段的处理非常的清晰,name 字段对应的解析器被调用的时候,解析器回调函数的 obj 参数是由上层回调函数生成的 new Human 对象。在这个案例中,我们希望 Human 对象会拥有一个 name 属性可以让我们直接读取。

事实上,许多 GraphQL 库可以让你省略这些简单的解析器,假定一个字段没有提供解析器时,那么应​​该从上层返回对象中读取和返回和这个字段同名的属性。

内省

我们有时候会需要去问 GraphQL Schema 它支持哪些查询。GraphQL 通过内省系统让我们可以做到这点!

{
  __schema {
    types {
      name
    }
  }
}
{
  __schema {
    queryType {
      name
    }
  }
}
{
  __type(name: "Droid") {
    name
    kind
  }
}
{
  __type(name: "Droid") {
    name
    fields {
      name
      type {
        name
        kind
      }
    }
  }
}

introspection

GraphQL核心概念

https://segmentfault.com/a/1190000014131950

contructing type

将graphsql的schema定义,从string方式转变为类型定义的方式,虽然代码量会上升,但是多了类型检查,便于维护。

const schema = buildSchema(`
    type Account {
        id: ID
        name: String
        age: Int
    }
    type Query{
        account(username: String): Account
    }
`)

上述代码转为3部分:

var AccountType = new graphql.GraphQLObjectType({
    name: 'Account',
    fields: {
        id: {type: graphql.GraphQLID},
        name: {type: graphql.QraphQLString},
        age: {type: graphql.GraphQLInt}
    }
})
var queryTYpe = new graphql.GraphQLObjectTpe({
    name: 'Query'
    fields: {
        account: {
            type: AccountType,
            args: {
                id: {type: graphql.GraphQLID}
            },
            resolve: function(_, {id}) {
                return {
                    id: 1,
                    name: "test",
                    age: 18
                }
            }
        }
    }
})
var query = new graphql.GraphQLSchema({query: queryType});

n+1 query

如下的query, authors Resolver 先进行 1 次query获取n个authors,然后books Resolver 再进行 n 次query,获取每本书的名字,这就是 n+1 query

{
    query {
        authors {
            name
            books {
                name
            }
        }
    }
}

如何解决?dataloader

主要思想为,减少数据库请求。

dataloader会定义一个时间段,在这个时间段内发起的多个请求,经过处理之后会合并为单个请求,比如 getBookByID(1) 和 getBookByID(2) 会合并为一个 getBooksByIDs(1, 2) 请求

dataloader github

go语言实现:

dataloader.v5

默认的时间段为16毫秒:

func NewBatchedLoader(batchFn BatchFunc, opts ...Option) *Loader {
	loader := &Loader{
		batchFn:  batchFn,
		inputCap: 1000,
		wait:     16 * time.Millisecond,
	}
⚠️ **GitHub.com Fallback** ⚠️