GraphQL Key Points
2021-09-14
0. 前言
根据 GraphQL 官方 doc 记录的一些笔记。
ref: https://www.howtographql.com/
Architectural Use Cases
- GraphQL server with a connected database
- 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
- A hybrid approach of a connected database and third party or legacy systems that can all be accessed through the same GraphQL API
怎么选择?
- greenfield projects - 直接 connect db
- 跟现有系统结合 - 可以用来 unify existing systems to simplify data fetching logic,可以把多个 datasource 结合在一起
- 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.
- query (read)
- mutation (create, update, delete)
- subscription (real-time event streaming)
两种 type
In GraphQL, there are two different kinds of types.
- Scalar types represent concrete units of data. The GraphQL spec has five predefined scalars: as String, Int, Float, Boolean, and ID.
- Object types have fields that express the properties of that type and are composable. Examples of object types are the User or Post 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 来获取数据。
- hero query 返回的是 Character type,它可能是 Human 也可能是 Droid,我们根据返回的 concrete type 来确定获取的 field 到底是什么
- 当 concrete type = Droid,就获取 primaryFunction
- 当 concrete type = Human,就获取 height
之前的 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).
- Atomic
- Consistent
- Isolation
- Durable
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
- timeout strategy
- 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.)
- query complexity (一条 record = 1,计算最终的复杂度,假如太大就 reject)
- throttling (根据 server response time 和 query complexity 来控制 request,假如请求过于频繁就要采取措施)
Auth
Authentication in GraphQL can be implemented with common patterns such as OAuth.
结语
GraphQL 不受 transport layer protocol 的限制,可以用 TCP,UDP 来建立 session。