Graphql

511 阅读7分钟

文档

  • Schema定义规范
  • 层级定义规范
  • Apollo最佳实践

Apollo GraphQL

  • 对一些产品链复杂场景中应用,工作剖析机制

  • 架构体系

  • GraphQL 操作类型

  • Object Type & Scalar Type

  • 模式 Schema

  • 解析函数Resolver

  • 请求格式

  • 环境部署

Apollo GraphQL ==工作机制==

  • 复杂场景中 工作机制

image image

  1. API字段的定制化,按需取字段
  2. API的聚合,一次请求拿到所有的数据
  3. 后端不再需要维护接口的版本号
  4. 完备的类型校验机制,

GraphQL-体系结构

GraphQL是描述GraphQL服务器行为的规范。它是关于如何处理请求和响应(如支持的协议,服务器可以接受的数据格式,服务器返回的响应格式等)的一组准则。客户端对GraphQL的请求服务器称为查询。 GraphQL的另一个重要概念是其传输层不可知论性。它可以与任何可用的网络协议(例如TCP,websocket或任何其他传输层协议)一起使用。它对数据库也是中立的,因此您可以将其与关系数据库或NoSQL数据库一起使用。 image

- 可以使用下面列出的三种方法中的任何一种来部署GraphQL Server-

1. 具有连接数据库的GraphQL服务器

该体系结构具有带集成数据库的GraphQL Server,通常可以与新项目一起使用。收到查询后,服务器读取请求有效负载并从数据库中获取数据。这称为解决查询。返回给客户端的响应遵循官方GraphQL规范中指定的格式

image 在上图中,GraphQL服务器和数据库集成在单个节点上。客户端(桌面/移动设备)通过HTTP与GraphQL服务器通信。服务器处理该请求,从数据库中获取数据并将其返回给客户端

2. GraphQL Server集成现有系统

image

在上图中,GraphQL API充当客户端和现有系统之间的接口。客户端应用程序与GraphQL服务器通信,该服务器依次解析查询。

3. 混合方式

最后,我们可以结合以上两种方法来构建GraphQL服务器。在这种体系结构中,GraphQL服务器将解析收到的任何请求。它将从连接的数据库或集成的API检索数据。下图所示-

image

操作类型

GraphQL 的操作类型可以是 query、mutation或subscription,描述客户端希望进行什么样的操作

  • query 查询:获取数据,比如查找,CRUD 中的 R
  • mutation 变更:对数据进行变更,比如增加、删除、修改,CRUD 中的 CUD
  • substription 订阅:当数据发生更改,进行消息推送

Object Type & Scalar Type

如果一个 GraphQL 服务接受到了一个 query,那么这个 query 将从 Root Query 开始查找,找到对象类型(Object Type)时则使用它的解析函数 Resolver 来获取内容,如果返回的是对象类型则继续使用解析函数获取内容,如果返回的是标量类型(Scalar Type)则结束获取,直到找到最后一个标量类型。

  • 对象类型:用户在 schema 中定义的 type,用于描述层级或者树形数据结构。对于树形数据结构来说,叶子字段的类型都是标量数据类型。几乎所有 GraphQL 类型都是对象类型。Object 类型有一个 name 字段,以及一个很重要的 fields 字段。fields 字段可以描述出一个完整的数据结构。例如一个表示地址数据结构的 GraphQL 对象为:
const AddressType = new GraphQLObjectType({
  name: 'Address',
  fields: {
    street: { type: GraphQLString },
    number: { type: GraphQLInt },
    formatted: {
      type: GraphQLString,
      resolve(obj) {
        return obj.number + ' ' + obj.street
      }
    }
  }
});
  • 标量类型:GraphQL 中内置有一些标量类型 String、Int、Float、Boolean、ID

Schema 定义

  • 灵活而增强的Schema ,schema就是协议,规范,或者可以当他是接口文档,GraphQL规定,每一个schema有一个根(root)query和根(root)mutation。

  • 一次查询只实现一个单一目标。
  • 尽量避免使用片段,指令之类的高级特性。
  • 使用POST提交数据
  • 参数与查询语句分离
  • 所有 GraphQL schema 内的类型都必须要有唯一的名字
  • 所有 schema 内定义的类型和指令都不能以"__"(双下划线)开头命名,因为这是 GraphQL 内省系统专用。
  • 任何 GraphQL Schema 的最基本单元都是类型,GraphQL 中有 8种 类型(标量 对象 接口 联合 枚举型 输入对象 列表型 非空型)
  • 类型定义和返回值的类型必须要一致
  1. GraphQL是强类型的。也就是说,我们在定义Schema时,类似于使用SQL,显式地为每一个域定义类型的,比如:
	schema { #定义查询 
    		query: UserQuery 
	}
	type UserQuery { #定义查询的类型 
    		user(id:ID) : User #指定对象以及参数类型 
	}
	type User { #定义对象 
    		id:ID! # !表示该属性是非空项 
    		name:String 
    		age:Int 
	}
  1. 定义一个根查询
type Query {
    # 可以查询的字段和参数
    shares(start: Int = 0, limit: Int = 10, creatorId: ID): [Share!]!
    share(shareId: ID!): Share!
    commentInfo(shareId: ID!, start: Int = 0, limit: Int = 10): CommentInfo!
}
  • 查询请求的模型可以用下面的图来表示 image

Resolvers

Resolver是一组函数,可为GraphQL查询生成响应。简单来说,解析器充当GraphQL查询处理程序。GraphQL架构中的每个解析器函数都接受四个位置参数,如下所示

fieldName: (parent, args, context, info) => data;

ARGUMENTDESCRIPTION
parent包含从父字段上的解析程序返回的结果的对象
args参数传递给查询中的字段的对象
context特定查询中所有解析器共享的对象
info它包含有关查询执行状态的信息,包括字段名称,从根目录到字段的路径
  • 解析器返回结果格式
NO.参数描述
1null或undefined(这表示无法找到该对象)
2array
3promise
4scalarobject

请求格式

GraphQL 最常见的是通过 HTTP 来发送请求,如何通过 Get/Post 方式来执行下面的 GraphQL 查询呢:

query {
  me {
    name
  }
}

Get 是将请求内容放在 URL 中,Post 是在 content-type: application/json 情况下,将 JSON 格式的内容放在请求体里

# Get 方式
http://myapi/graphql?query={me{name}}

# Post 方式的请求体
{
  "query": "...",
  "operationName": "...",
  "variables": { "myVariable": "someValue", ... }
}

返回的格式一般也是 JSON 体

# 正确返回
{
  "data": { ... }
}

# 执行时发生错误
{
  "errors": [ ... ]
}

GraphQL-环境部署

  • 搭建服务端
  1. 采用 apollo-server-express 快速搭建服务端,创建一个package.json文件,其中将包含GraphQL服务器应用程序的所有依赖项。
{
   "name": "apollo-server",
   "private": true,
   "scripts": {
      "start": "nodemon --ignore data/ server.js"
   },
   
   "dependencies": {
      "apollo-server-express": "^1.4.0",
      "body-parser": "^1.18.3",
      "cors": "^2.8.4",
      "express": "^4.16.3",
      "graphql": "^0.13.2",
      "graphql-tools": "^3.1.1"
   },
   
   "devDependencies": {
      "nodemon": "1.17.1"
   }
}
##安装依赖
yarn install || npm install
  1. 定义schema 和 resolvers
const server = new ApolloServer({
  typeDefs,
  resolvers
});

创建对应的schema.graphql

type Query  {
   test: String
}

创建解析器文件 resolvers.js

const Query = {
   test: () => 'Test Success, GraphQL server is up & running !!'
}
module.exports = {Query}
  1. 创建server.js并配置Graphql
const bodyParser = require('body-parser');
const cors = require('cors');
const express = require('express');
const db = require('./db');

const port = process.env.PORT || 9000;
const app = express();

const fs = require('fs')
const typeDefs = fs.readFileSync('./schema.graphql',{encoding:'utf-8'})
const resolvers = require('./resolvers')

const {makeExecutableSchema} = require('graphql-tools')
const schema = makeExecutableSchema({typeDefs, resolvers})

app.use(cors(), bodyParser.json());

const  {graphiqlExpress,graphqlExpress} = require('apollo-server-express')
app.use('/graphql',graphqlExpress({schema}))
app.use('/graphiql',graphiqlExpress({endpointURL:'/graphql'}))

app.listen(
   port, () => console.info(
      `Server started on port ${port}`
   )
);
const express = require("express");
const { ApolloServer } = require("apollo-server-express");

const typeDefs = require("./schema");
const resolvers = require("./resolvers");

const PORT = 4000;

const app = express();

const server = new ApolloServer({
  typeDefs,
  resolvers,
  playground: {
    endpoint: `/graphql`,
    settings: {
      "editor.theme": "light"
    }
  }
});

server.applyMiddleware({ app });

app.listen(PORT, () =>
  console.log(
    `🚀 Server ready at http://localhost:${PORT}${server.graphqlPath}`
  )
);


  • 搭建客户端
  1. 项目使用vue技术栈,基于vue-apollo来编写
yarn add vue-apollo graphql apollo-boost
import Vue from "vue";
import ApolloClient from "apollo-boost";
import VueApollo from "vue-apollo";

Vue.use(VueApollo);

const apolloClient = new ApolloClient({
  // 你需要在这里使用绝对路径
  uri: "http://localhost:4000/graphql"
});

const apolloProvider = new VueApollo({
  defaultClient: apolloClient
});

new Vue({
  el: "#app",
  apolloProvider,
  render: h => h(App)
});


  1. 配置支持 .gql || .graphql 文件后缀的 webpack loader
// vue.config.js
module.exports = {
  // 支持 gql 文件
  chainWebpack: config => {
    config.module
      .rule("graphql")
      .test(/\.(graphql|gql)$/)
      .use("graphql-tag/loader")
      .loader("graphql-tag/loader")
      .end();
  }
};

一个查询获取多种结果集

query shareDetailPage($shareId: Int!, $creatorId:ID!, $start: Int!, $limit: Int = 10) {
    # 分享详情
    shareDetail: share (shareId: $shareId) {
        shareId: id
        title
        desc
        where
        logoUrl
        attchments
    }
    
    # 评论信息
    commentInfo(shareId: $shareId, start: $start, limit: $limit) {
        totalCount
        comments {
            id
            userId
            content
            commentTime
        }
    }
    
    # TA的分享
    hisShares (creatorId: $creatorId) {
        shares {
            title
            desc
            where
            startTime
        }
    }
}

fragment

  • 片段 fragment:是 GraphQL 组合拼装的基本单元,它通用选择集字段的重用得以实现,减少了文档中的重复文本。
  • 🌰 例如,我们想要获取某个用户的朋友以及和他互为朋友的人的共通信息:
query noFragments {
	user(id: 4) {
		friends(first: 10) {
  			id
  			name
  			profilePic(size: 50)
		}
		mutualFriends(first: 10) {
  			id
  			name
  			profilePic(size: 50)
		}
	}
}
// 这些重复的字段可以提取进一个 fragment(片段)中,
// 然后被父级 fragment(片段)或者 query(查询)组合:
query withFragments {
	user(id: 4) {
		friends(first: 10) {
  			...friendFields
		}
		mutualFriends(first: 10) {
  			...friendFields
		}
	}
}
fragment friendFields on User {
	id
	name
	profilePic(size: 50)
}