Luna Tech

Tutorials For Dummies.

GraphQL Key Points

2021-09-14


0. 前言

根据 GraphQL 官方 doc 记录的一些笔记。

ref: https://www.howtographql.com/

Architectural Use Cases

  1. GraphQL server with a connected database
  2. GraphQL server that is a thin layer in front of a number of third party or legacy systems and integrates them through a single GraphQL API
  3. A hybrid approach of a connected database and third party or legacy systems that can all be accessed through the same GraphQL API

怎么选择?

  1. greenfield projects - 直接 connect db
  2. 跟现有系统结合 - 可以用来 unify existing systems to simplify data fetching logic,可以把多个 datasource 结合在一起
  3. hybrid 就是两个相结合,既有直接的 GraphQL endpoint,又整合了不同 data source

1. SDL (Schema Definition Language)

GraphQL 有自己的 type system,用来 define API schema,写 schema 的 syntax 被称作 SDL (The Schema Definition Language)。

type Query {
  allPersons(last: Int): [Person!]!
  allPosts(last: Int): [Post!]!
}

type Mutation {
  createPerson(name: String!, age: Int!): Person!
  updatePerson(id: ID!, name: String!, age: String!): Person!
  deletePerson(id: ID!): Person!
}

type Subscription {
  newPerson: Person!
}

type Person {
  name: String!
  age: Int!
  posts: [Post!]!
}

type Post {
  titiel: String!
  author: Person!
}

三种 data interaction methods (Root Types)

Every GraphQL schema has three special root types: Query, Mutation, and Subscription.

  1. query (read)
  2. mutation (create, update, delete)
  3. subscription (real-time event streaming)

两种 type

In GraphQL, there are two different kinds of types.

Note: 我们可以 implement custom scalar and object type,比如 Date 就是一个常见的 custom scalar。

Interface

一般来说每个 type 都有 id field,我们可以利用这个来写 interface。

interface Node {
  id: ID!
}

type User implements Node {
  id: ID!
  name: String!
  age: Int!
}

Union

跟后面的 inline fragment 相关。

union Person = Adult | Child

2. Query Syntax

返回的一定是一个 object,key = data,里面的结构和 query 写的一样。

query and variables

我们也可以有 default variable, 比如 query HeroNameAndFriends($episode: Episode = JEDI)

query HeroNameAndFriends($episode: Episode) {
  hero(episode: $episode) {
    name
    friends {
      name
    }
  }
}

variable

{
  "episode": "JEDI"
}

result

{
  "data": {
    "hero": {
      "name": "R2-D2",
      "friends": [
        {
          "name": "Luke Skywalker"
        },
        {
          "name": "Han Solo"
        }
      ]
    }
  }
}

directives - skip and include

这两个可以通过 variable 来 dynamically change query。

比如:

query Hero($episode: Episode, $withFriends: Boolean!, $withName: Boolean!) {
  hero(episode: $episode) {
    name
    friends @include(if: $withFriends) {
      name @skip(if: $withName)
    }
  }
}

Alias

alias 可以用来改变 return data 的 key。

query HeroNameAndFriends($episode: Episode) {
  heroAlias: hero(episode: $episode) {
    name
    friends {
      name
    }
  }
}

result

{
  "data": {
    "heroAlias": {
      "name": "R2-D2",
      "friends": [
        {
          "name": "Luke Skywalker"
        },
        {
          "name": "Han Solo"
        }
      ]
    }
  }
}

Fragment

用来减少重复性的 query,create reusable component。

query getPosts {
  firstPosts: posts {
    ...postFrg
  }
  secondPosts: posts {
    ...postFrg
  }
}

fragment postFrg on Post {
  id
  title
}

我们也可以给 fragment pass variable。

query HeroComparison($first: Int = 3) {
  leftComparison: hero(episode: EMPIRE) {
    ...comparisonFields
  }
  rightComparison: hero(episode: JEDI) {
    ...comparisonFields
  }
}

fragment comparisonFields on Character {
  name
  friendsConnection(first: $first) {
    totalCount
    edges {
      node {
        name
      }
    }
  }
}

Inline Fragments

假如 query 的 field 返回的是一个 interface 或者 union type,那么我们需要用 inline fragments 来获取数据。

之前的 fragment 是有名字的,这个是直接写在 query 里面的,所以是 inline fragment,两种 fragment 都有 attached type,所以都可以用。

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

Meta fields

有时候我们也不知道 query 返回的数据到底是什么 type,这种情况下我们就要用一些 metadata field 来获取相关信息。

比如 __typename 就是一个 meta field,你可以通过它来判断返回的值的 associate type.

{
  search(text: "an") {
    __typename
    ... on Human {
      name
    }
    ... on Droid {
      name
    }
    ... on Starship {
      name
    }
  }
}

data

{
  "data": {
    "search": [
      {
        "__typename": "Human",
        "name": "Han Solo"
      },
      {
        "__typename": "Human",
        "name": "Leia Organa"
      },
      {
        "__typename": "Starship",
        "name": "TIE Advanced x1"
      }
    ]
  }
}

3. Query 和 Mutation 的区别

query 可以多线程同步执行(parallel execution),mutation 是 write operation,所以是单线程执行(serial executation, one by one)。

这个很好理解,因为任何 write operation 都要考虑 data integrity (ACID properties of a transaction).

This means that if we send two incrementCredits mutations in one request, the first is guaranteed to finish before the second begins, ensuring that we don’t end up with a race condition with ourselves.


4. Subscription

假如某个 event 发生,会 push data 给到 subscription,real-time data stream。

subscription {
  newPerson {
    name
    age
  }
}
{
  "newPerson": {
    "name": "Jane",
    "age": 23
  }
}

5. Introspection

可以用来 ask server about schema。

通过 __schema meta-field 来 query type name。

query {
  __schema {
    types {
      name
    }
  }
}

__type 也是一个可以用的 meta field。

{
  __type(name: "Author") {
    name
    description
  }
}

6. Security - 控制 query frequency + complexity

https://www.howtographql.com/advanced/4-security/

  1. timeout strategy
  2. maximum query depth (By analyzing the query document’s abstract syntax tree (AST), a GraphQL server is able to reject or accept a request based on its depth.)
  3. query complexity (一条 record = 1,计算最终的复杂度,假如太大就 reject)
  4. throttling (根据 server response time 和 query complexity 来控制 request,假如请求过于频繁就要采取措施)

Auth

Authentication in GraphQL can be implemented with common patterns such as OAuth.

Ref


结语

GraphQL 不受 transport layer protocol 的限制,可以用 TCP,UDP 来建立 session。