GraphQL入门 GraphQL入门有这一篇就足够了

GraphQL入门有这一篇就足够了

 

 

一、GraphQL是什么?


关于GraphQL是什么,网上一搜一大堆。根据官网的解释就是一种用于 API 的查询语言

GraphQL 是由 Facebook 创造的用于描述复杂数据模型的一种查询语言。这里查询语言所指的并不是常规意义上
的类似 sql 语句的查询语言,而是一种用于前后端数据查询方式的规范。
官网(中文): https://graphql.cn/
规范地址: http://spec.graphql.cn/


引用官方文档的一句话:

ask exactly what you want.

二、为什么要使用GraphQL?

分析 RESTful 存在的问题
RESTful 是我们已经很熟悉的用于 api 通信的规范,如这样:
GET http://127.0.0.1/user/1 # 查询
POST http://127.0.0.1/user # 新增
PUT http://127.0.0.1/user # 更新
DELETE http://127.0.0.1/user # 删除
# 请求
GET http://127.0.0.1/user/1001
# 响应:
{
id : 1001 ,
name : " 张三 " ,
age : 20 ,
address : " 北京市 " ,
……
}
这样似乎是没有问题的,如果,对于这次请求,我只需要 id name 属性,其他的属性我都不需要,如果我依然拿
到的是全部的属性,这是不是一种资源浪费?
还有这样的一种场景,就是一次请求不能满足需求,需要有多次请求才能完成,像这样:
# 查询用户信息
GET http://127.0.0.1/user/1001
# 响应:
{
id : 1001 ,
name : " 张三 " ,
age : 20 ,
address : " 北京市 " ,
……
}
# 查询用户的身份证信息
GET http://127.0.0.1/card/8888
# 响应:
{
id : 8888 ,
name : " 张三 " ,
cardNumber : "999999999999999" ,
address : " 北京市 " ,
……
}
查询用户以及他的身份证信息,需要进行 2 次查询才能够完成,这样对于前端等接口的使用方是很不友好的,试想
一下,如果查询信息有 10 个,是不是要发起 10 次请求才能完成?

在实际工作中都会有这种情景出现:比如说我需要展示一个游戏名的列表,可接口却会把游戏的详细玩法,更新时间,创建者等各种各样的 (无用的) 信息都一同返回。

问了后端,原因大概如下:

  • 原来是为了兼容PC端和移动端用同一套接口
  • 或者在整个页面,这里需要显示游戏的标题,可是别的地方需要显示游戏玩法,避免多次请求我就全部返回
  • 或者是因为有时候项目经理想要显示“标题+更新时间”,有时候想要点击标题展开游戏玩法等等需求,所以把游戏相关的信息都一同返回

简单说就是:

  • 兼容多平台导致字段冗余
  • 一个页面需要多次调用 API 聚合数据
  • 需求经常改动导致接口很难为单一接口精简逻辑

有同学可能会说那也不一定要用GraphQL啊,比方说第一个问题,不同平台不同接口不就好了嘛

http://api.xxx.com/web/getGameInfo/:gameID
http://api.xxx.com/app/getGameInfo/:gameID
http://api.xxx.com/mobile/getGameInfo/:gameID

或者加个参数也行

http://api.xxx.com/getGameInfo/:gameID?platfrom=web

这样处理的确可以解决问题,但是无疑加大了后端的处理逻辑。

这个时候我们会想,接口能不能不写死,把静态变成动态?

回答是可以的,这就是GraphQL所做的!

进一步了解GraphQL

GraphQL 很好的解决了 RESTful 在使用过程中的不足,接下来,我们进一步了解下它。
6.3.1 、按需索取数据,避免浪费
当请求中只有 name 属性时,响应结果中只包含 name 属性,如果请求中添加 appearsIn 属性,那么结果
中就会返回 appearsIn 的值。
6.3.2 、一次查询多个数据
一次请求,不仅查询到了 hero 数据,而且还查询到了 friends 数据。节省了网络请求次数。
6.4 GraphQL 查询的规范
GraphQL 定义了一套规范,用来描述语法定义,具体参考: http://graphql.cn/learn/queries/
说明:仅仅是规范,并不是具体实现,需要各种语言进行实现。
下面我们会讲一些基本的规范,更多的规范查看: http://graphql.cn/learn/queries/
6.4.1 、字段( Fields
GraphQL 的查询中,请求结构中包含了所预期结果的结构,这个就是字段。并且响应的结构和请求结构基本一
致,这是 GraphQL 的一个特性,这样就可以让请求发起者很清楚的知道自己想要什么。

三、GraphQL尝尝鲜——(GraphQL简单例子)


下面是用GraphQL.jsexpress-graphql搭建一个的普通GraphQL查询(query)的例子,包括讲解GraphQL的部分类型和参数,

1. 先跑个hello world

  1. 新建一个graphql文件夹,然后在该目录下打开终端,执行npm init --y初始化一个packjson文件。
  2. 安装依赖包:npm install --save -D express express-graphql graphql
  3. 新建schema.js文件,填上下面的代码
//schema.js
const {
      GraphQLSchema,
      GraphQLObjectType,
      GraphQLString,
    } = require('graphql');
const queryObj = new GraphQLObjectType({
    name: 'myFirstQuery',
    description: 'a hello world demo',
    fields: {
        hello: {
            name: 'a hello world query',
            description: 'a hello world demo',
            type: GraphQLString,
            resolve(parentValue, args, request) {
                return 'hello world !';
            }
        }
    }
});
module.exports = new GraphQLSchema({
  query: queryObj
});

这里的意思是新建一个简单的查询,查询名字叫hello,会返回字段hello world !,其他的是定义名字和查询结果类型的意思。

  1. 同级目录下新建server.js文件,填上下面的代码
// server.js
const express = require('express');
const expressGraphql = require('express-graphql');
const app = express();

const schema = require('./schema');
app.use('/graphql', expressGraphql({
	schema,
	graphiql: true
}));

app.get('/', (req, res) => res.end('index'));

app.listen(8000, (err) => {
  if(err) {throw new Error(err);}
  console.log('*** server started ***');
});

这部分代码是用express跑起来一个服务器,并通过express-graphqlgraphql挂载到服务器上。

  1. 运行一下node server,并打开http://localhost:8000/

这里写图片描述

如图,说明服务器已经跑起来了
打开http://localhost:8000/graphql,是类似下面这种界面说明已经graphql服务已经跑起来了!
这里写图片描述
在左侧输入 (graphql的查询语法这里不做说明)

{
  hello
}

点击头部的三角形的运行按钮,右侧就会显示你查询的结果了
这里写图片描述

2. 不仅仅是hello world

先简单讲解一下代码:

const queryObj = new GraphQLObjectType({
    name: 'myFirstQuery',
    description: 'a hello world demo',
    fields: {}
});

GraphQLObjectType是GraphQL.js定义的对象类型,包括namedescription 和fields三个属性,其中namedescription 是非必填的。fields是解析函数,在这里可以理解为查询方法

hello: {
            name: 'a hello world query',
            description: 'a hello world demo',
            type: GraphQLString,
            resolve(parentValue, args, request) {
                return 'hello world !';
            }
        }

对于每个fields,又有name,description,type,resolve参数,这里的type可以理解为hello方法返回的数据类型,resolve就是具体的处理方法。

说到这里有些同学可能还不满足,如果我想每次查询都想带上一个参数该怎么办,如果我想查询结果有多条数据又怎么处理?

下面修改schema.js文件,来一个加强版的查询(当然,你可以整理一下代码,我这样写是为了方便阅读)

const {
      GraphQLSchema,
      GraphQLObjectType,
      GraphQLString,
      GraphQLInt,
      GraphQLBoolean
    } = require('graphql');

const queryObj = new GraphQLObjectType({
    name: 'myFirstQuery',
    description: 'a hello world demo',
    fields: {
        hello: {
            name: 'a hello world query',
            description: 'a hello world demo',
            type: GraphQLString,
            args: {
                name: {  // 这里定义参数,包括参数类型和默认值
                    type: GraphQLString,
                    defaultValue: 'Brian'
                }
            },
            resolve(parentValue, args, request) { // 这里演示如何获取参数,以及处理
                return 'hello world ' + args.name + '!';
            }
        },
        person: {
            name: 'personQuery',
            description: 'query a person',
            type: new GraphQLObjectType({ // 这里定义查询结果包含name,age,sex三个字段,并且都是不同的类型。
                name: 'person',
                fields: {
                  name: {
                    type: GraphQLString
                  },
                  age: {
                    type: GraphQLInt
                  },
                  sex: {
                    type: GraphQLBoolean
                  }
                }
            }),
            args: {
                name: {
                    type: GraphQLString,
                    defaultValue: 'Charming'
                }
            },
            resolve(parentValue, args, request) {
                return {
                    name: args.name,
                    age: args.name.length,
                    sex: Math.random() > 0.5
                };
            }
        }
    }
});

module.exports = new GraphQLSchema({
  query: queryObj 
});

重启服务后,继续打开http://localhost:8000/graphql,在左侧输入

{
  hello(name:"charming"),
  person(name:"charming"){
    name,
    sex,
    age
  }
}

右侧就会显示出:
这里写图片描述

你可以在左侧仅输入person方法的sexage两个字段,这样就会只返回sexage的信息。动手试一试吧!

{
  person(name:"charming"){
    sex,
    age
  }
}

当然,结果的顺序也是按照你输入的顺序排序的。

定制化的数据,完全根据你查什么返回什么结果。这就是GraphQL被称作API查询语言的原因。

四、GraphQL实战


下面我将搭配koa实现一个GraphQL查询的例子,逐步从简单koa服务到mongodb的数据插入查询,再到GraphQL的使用,最终实现用GraphQL对数据库进行增删查改。

项目效果大概如下:
这里写图片描述

有点意思吧?那就开始吧~
先把文件目录建构建好

1. 初始化项目

  • 初始化项目,在根目录下运行npm init --y
  • 然后安装一些包:npm install koa koa-static koa-router koa-bodyparser --save -D
  • 新建configcontrollersgraphqlmongodbpublicrouter这几个文件夹。装逼的操作是在终端输入mkdir config controllers graphql mongodb public router回车,ok~

2. 跑一个koa服务器
新建一个server.js文件,写入以下代码

// server.js
import Koa from 'koa'
import Router from 'koa-router'
import bodyParser from 'koa-bodyparser'

const app = new Koa()
const router = new Router();
const port = 4000

app.use(bodyParser());

router.get('/hello', (ctx, next) => {
  ctx.body="hello world"
});


app.use(router.routes())
   .use(router.allowedMethods());

app.listen(port);

console.log('server listen port: ' + port)

执行node server跑起来服务器,发现报错了:
这里写图片描述

这是正常的,这是因为现在的node版本并没有支持es6的模块引入方式。

百度一下就会有解决方案了,比较通用的做法是用babel-polyfill进行转译。

详细的可以看这一个参考操作:How To Enable ES6 Imports in Node.JS

具体操作是:新建一个start.js文件,写入:

// start.js
require('babel-register')({
    presets: [ 'env' ]
})
require('babel-polyfill')
require('./server.js')

安装相关包:npm install --save -D babel-preset-env babel-polyfill babel-register

修改package.json文件,把"start": "start http://localhost:4000 && node start.js"这句代码加到下面这个位置:
这里写图片描述

运行一下npm run start,打开http://localhost:4000/hello,结果如图:
这里写图片描述
说明koa服务器已经跑起来了。

那么前端页面呢?

(由于本文内容不是讲解前端,所以前端代码自行去github复制)

  • 在public下新建index.html文件和js文件夹,代码直接查看我的项目public目录下的 index.html 和 index-s1.js 文件
  • 修改server.js,引入koa-static模块。koa-static会把路由的根目录指向自己定义的路径(也就是本项目的public路径)
//server.js
import Koa from 'koa'
import Router from 'koa-router'
import KoaStatic from 'koa-static'
import bodyParser from 'koa-bodyparser'

const app = new Koa()
const router = new Router();
const port = 4000

app.use(bodyParser());

router.get('/hello', (ctx, next) => {
  ctx.body="hello world"
});


app.use(KoaStatic(__dirname + '/public'));
app.use(router.routes())
   .use(router.allowedMethods());

app.listen(port);

console.log('server listen port: ' + port)

打开http://localhost:4000/,发现是类似下面的页面:
这里写图片描述

这时候页面已经可以进行简单的交互,但是还没有和后端进行数据交互,所以是个静态页面。

3. 搭一个mongodb数据库,实现数据增删改查

注意: 请先自行下载好mongodb并启动mongodb。

a. 写好链接数据库的基本配置和表设定

config文件夹下面建立一个index.js,这个文件主要是放一下链接数据库的配置代码。

//  config/index.js
export default {
  dbPath: 'mongodb://localhost/todolist'
}

mongodb文件夹新建一个index.js和 schema文件夹, 在 schema文件夹文件夹下面新建list.js

mongodb/index.js下写上链接数据库的代码,这里的代码作用是链接上数据库

// mongodb/index.js
import mongoose from 'mongoose'
import config from '../config'

require('./schema/list')

export const database = () => {
  mongoose.set('debug', true)

  mongoose.connect(config.dbPath)

  mongoose.connection.on('disconnected', () => {
    mongoose.connect(config.dbPath)
  })
  mongoose.connection.on('error', err => {
    console.error(err)
  })

  mongoose.connection.on('open', async () => {
    console.log('Connected to MongoDB ', config.dbPath)
  })
}

mongodb/schema/list.js定义表和字段:

//mongodb/schema/list.js
import mongoose from 'mongoose'

const Schema = mongoose.Schema
const ObjectId = Schema.Types.ObjectId

const ListSchema = new Schema({
  title: String,
  desc: String,
  date: String,
  id: String,
  checked: Boolean,
  meta: {
    createdAt: {
      type: Date,
      default: Date.now()
    },
    updatedAt: {
      type: Date,
      default: Date.now()
    }
  }
})

ListSchema.pre('save', function (next) {// 每次保存之前都插入更新时间,创建时插入创建时间
  if (this.isNew) {
    this.meta.createdAt = this.meta.updatedAt = Date.now()
  } else {
    this.meta.updatedAt = Date.now()
  }
  next()
})
mongoose.model('List', ListSchema)

b. 实现数据库增删查改的控制器

建好表,也链接好数据库之后,我们就要写一些方法来操作数据库,这些方法都写在控制器(controllers)里面。

controllers里面新建list.js,这个文件对应操作list数据的控制器,单独拿出来写是为了方便后续项目复杂化的模块化管理。

// controllers/list.js
import mongoose from 'mongoose'
const List = mongoose.model('List')
// 获取所有数据
export const getAllList = async (ctx, next) => {
  const Lists = await List.find({}).sort({date:-1}) // 数据查询
  if (Lists.length) {
    ctx.body = {
      success: true,
      list: Lists
    }
  } else {
    ctx.body = {
      success: false
    }
  }
}
// 新增
export const addOne = async (ctx, next) => {
  // 获取请求的数据
  const opts = ctx.request.body
  
  const list = new List(opts)
  const saveList = await list.save() // 保存数据
  console.log(saveList)
  if (saveList) {
    ctx.body = {
      success: true,
      id: opts.id
    }
  } else {
    ctx.body = {
      success: false,
      id: opts.id
    }
  }
}
// 编辑
export const editOne = async (ctx, next) => {
  const obj = ctx.request.body
  let hasError = false
  let error = null
  List.findOne({id: obj.id}, (err, doc) => {
  	if(err) {
  		hasError = true
  		error = err
  	} else {
  		doc.title = obj.title;
  		doc.desc = obj.desc;
  		doc.date = obj.date;
  		doc.save();
  	}
  })
  if (hasError) {
  	ctx.body = {
      success: false,
      id: obj.id
    }
  } else {
  	ctx.body = {
	  success: true,
    id: obj.id
	}
  }
}

// 更新完成状态
export const tickOne = async (ctx, next) => {
  const obj = ctx.request.body
  let hasError = false
  let error = null
  List.findOne({id: obj.id}, (err, doc) => {
  	if(err) {
  		hasError = true
  		error = err
  	} else {
  		doc.checked = obj.checked;
  		doc.save();
  	}
  })
  if (hasError) {
  	ctx.body = {
      success: false,
      id: obj.id
    }
  } else {
  	ctx.body = {
	  success: true,
    id: obj.id
	}
  }
}

// 删除
export const delOne = async (ctx, next) => {
  const obj = ctx.request.body
  let hasError = false
  let msg = null
  List.remove({id: obj.id}, (err, doc) => {
  	if(err) {
  		hasError = true
  		msg = err
  	} else {
  		msg = doc
  	}
  })
  if (hasError) {
  	ctx.body = {
      success: false,
      id: obj.id
    }
  } else {
  	ctx.body = {
  	  success: true,
      id: obj.id
  	}
  }
}

c. 实现路由,给前端提供API接口

数据模型和控制器都已经设计好了,下面就利用koa-router路由中间件,来实现请求的接口。

我们回到server.js,在上面添加一些代码。如下:

// server.js
import Koa from 'koa'
import Router from 'koa-router'
import KoaStatic from 'koa-static'
import bodyParser from 'koa-bodyparser'
import {database} from './mongodb' 
import {addOne, getAllList, editOne, tickOne, delOne} from './controllers/list' 

database() // 链接数据库并且初始化数据模型

const app = new Koa()
const router = new Router();
const port = 4000

app.use(bodyParser());

router.get('/hello', (ctx, next) => {
  ctx.body = "hello world"
});

// 把对请求的处理交给处理器。
router.post('/addOne', addOne)
      .post('/editOne', editOne)
      .post('/tickOne', tickOne)
      .post('/delOne', delOne)
      .get('/getAllList', getAllList)

app.use(KoaStatic(__dirname + '/public'));
app.use(router.routes())
   .use(router.allowedMethods());

app.listen(port);

console.log('server listen port: ' + port)

上面的代码,就是做了:

1. 引入mongodb设置、list控制器,
2. 链接数据库
3. 设置每一个设置每一个路由对应的我们定义的的控制器。

安装一下mongoose:npm install --save -D mongoose

运行一下npm run start,待我们的服务器启动之后,就可以对数据库进行操作了。我们可以通过postman来模拟请求,先插几条数据:
这里写图片描述
查询全部数据:
这里写图片描述

d. 前端对接接口

前端直接用ajax发起请求就好了,平时工作中都是用axios的,但是我懒得弄,所以直接用最简单的方法就好了。

引入了JQuery之后,改写public/js/index.js文件:略(项目里的public/index-s2.js的代码)

项目跑起来,发现已经基本上实现了前端发起请求对数据库进行操作了。
至此你已经成功打通了前端后台数据库,可以不要脸地称自己是一个小全栈了!

不过我们的目的还没有达到——用grapql实现对数据的操作!

4. 用grapql实现对数据的操作

GraphQL 的大部分讨论集中在数据获取(query),但是任何完整的数据平台也都需要一个改变服务端数据的方法。
REST 中,任何请求都可能最后导致一些服务端副作用,但是约定上建议不要使用 GET 请求来修改数据。GraphQL 也是类似 —— 技术上而言,任何查询都可以被实现为导致数据写入。然而,建一个约定来规范任何导致写入的操作都应该显式通过变更(mutation)来发送。

简单说就是,GraphQL用mutation来实现数据的修改,虽然mutation能做的query也能做,但还是要区分开这连个方法,就如同REST中约定用GET来请求数据,用其他方法来更新数据一样。

a. 实现查询
查询的话比较简单,只需要在接口响应时,获取数据库的数据,然后返回;

const objType = new GraphQLObjectType({
  name: 'meta',
  fields: {
    createdAt: {
      type: GraphQLString
    },
    updatedAt: {
      type: GraphQLString
    }
  }
})
let ListType = new GraphQLObjectType({
  name: 'List',
  fields: {
    _id: {
      type: GraphQLID
    },
    id: {
      type: GraphQLString
    },
    title: {
      type: GraphQLString
    },
    desc: {
      type: GraphQLString
    },
    date: {
      type: GraphQLString
    },
    checked: {
      type: GraphQLBoolean
    },
    meta: {
      type: objType
    }
  }
})
const listFields = {
  type: new GraphQLList(ListType),
  args: {},
  resolve (root, params, options) {
    return List.find({}).exec() // 数据库查询
  }
}
let queryType = new GraphQLObjectType({
    name: 'getAllList',
    fields: {
      lists: listFields,
    }
})

export default new GraphQLSchema({
  query: queryType
})

把增删查改都讲完再更改代码~
b. 实现增删查改

一开始说了,其实mutationquery用法上没什么区别,这只是一种约定。
具体的mutation实现方式如下:

const outputType = new GraphQLObjectType({
  name: 'output',
  fields: () => ({
    id:     { type: GraphQLString},
    success:   { type: GraphQLBoolean },
  })
});

const inputType = new GraphQLInputObjectType({
  name: 'input',
  fields: () => ({
    id:          { type: GraphQLString },
    desc:        { type: GraphQLString },
    title:       { type: GraphQLString },
    date:        { type: GraphQLString },
    checked:        { type: GraphQLBoolean }
  })
});
let MutationType = new GraphQLObjectType({
  name: 'Mutations',
  fields: () => ({
    delOne: {
      type: outputType,
      description: 'del',
      args: {
        id: { type: GraphQLString }
      },
      resolve: (value, args) => {
      	console.log(args)
      	let result = delOne(args)
        return result
      }
    },
    editOne: {
      type: outputType,
      description: 'edit',
      args: {
        listObj: { type: inputType }
      },
      resolve: (value, args) => {
      	console.log(args)
      	let result = editOne(args.listObj)
        return result
      }
    },
    addOne: {
      type: outputType,
      description: 'add',
      args: {
        listObj: { type: inputType }
      },
      resolve: (value, args) => {
      	console.log(args.listObj)
      	let result = addOne(args.listObj)
        return result
      }
    },
    tickOne: {
      type: outputType,
      description: 'tick',
      args: {
        id: { type: GraphQLString },
        checked: { type: GraphQLBoolean },
      },
      resolve: (value, args) => {
      	console.log(args)
      	let result = tickOne(args)
        return result
      }
    },

  }),
});

export default new GraphQLSchema({
  query: queryType,
  mutation: MutationType
})

c. 完善其余代码

在实现前端请求Graphql服务器时,最困扰我的就是参数以什么样的格式进行传递。后来在Graphql界面玩Graphql的query请求时发现了其中的诀窍…

关于前端请求格式进行一下说明:
这里写图片描述
如上图,在玩Graphql的请求时,我们就可以直接在控制台network查看请求的格式了。这里我们只需要模仿这种格式,当做参数发送给Graphql服务器即可。

记得用反引号: `` ,来拼接参数格式。然后用data: {query: params}的格式传递参数,代码如下:

let data = {
            query: `mutation{
                      addOne(listObj:{
                        id: "${that.getUid()}",
                        desc: "${that.params.desc}",
                        title: "${that.params.title}",
                        date: "${that.getTime(that.params.date)}",
                        checked: false
                      }){
                        id,
                        success
                      }
                    }`
          }
          $.post('/graphql', data).done((res) => {
            console.log(res)
            // do something
          })

最后更改server.jsrouter/index.jscontrollers/list.jspublic/index.js改成github项目对应目录的文件代码即可。

完整项目的目录如下:
在这里插入图片描述

 

五、后记

对于Vue开发者,可以使用vue-apollo使得前端传参更加优雅~

6.4 GraphQL 查询的规范
GraphQL 定义了一套规范,用来描述语法定义,具体参考: http://graphql.cn/learn/queries/
说明:仅仅是规范,并不是具体实现,需要各种语言进行实现。
下面我们会讲一些基本的规范,更多的规范查看: http://graphql.cn/learn/queries/
6.4.1 、字段( Fields
GraphQL 的查询中,请求结构中包含了所预期结果的结构,这个就是字段。并且响应的结构和请求结构基本一
致,这是 GraphQL 的一个特性,这样就可以让请求发起者很清楚的知道自己想要什么。
6.4.2 、参数( Arguments
在查询数据时,离不开传递参数,在 GraphQL 的查询中,也是可以传递参数的,语法: ( 参数名 : 参数值 )
6.4.3 、别名( Aliases
如果一次查询多个相同对象,但是值不同,这个时候就需要起别名了,否则 json 的语法就不能通过了。
 
6.4.4 、片段( Fragments
查询对的属相如果相同,可以采用片段的方式进行简化定义。
6.5 GraphQL Schema 和类型规范
Schema 是用于定义数据结构的,比如说, User 对象中有哪些属性,对象与对象之间是什么关系等。
参考官网文档: http://graphql.cn/learn/schema/
6.5.1 Schema 定义结构
6.5.2 、标量类型( Scalar Types
GraphQL 规范中,默认定义了 5 种类型:
Int :有符号 32 位整数。
Float :有符号双精度浮点值。
String UTF 8 字符序列。
Boolean true 或者 false
ID ID 标量类型表示一个唯一标识符,通常用以重新获取对象或者作为缓存中的键。
规范中定义的这 5 种类型,显然是不能满足需求的,所以在各种语言实现中,都有对类型进行了扩充,也就是
GraphQL 支持自定义类型,比如在 graphql-java 实现中增加了: Long Byte 等。
6.5.3 、枚举类型
枚举类型是一种特殊的标量,它限制在一个特殊的可选值集合内。
6.5.4 、接口( interface
跟许多类型系统一样, GraphQL 支持接口。一个接口是一个抽象类型,它包含某些字段,而对象类型必须包含这
些字段,才能算实现了这个接口。
schema { # 定义查询
   query: UserQuery
}
type UserQuery { # 定义查询的类型
user(id:ID) : User # 指定对象以及参数类型
}
type User { # 定义对象
id:ID! # ! 表示该属性是非空项
name:String
age:Int
}
 
enum Episode { # 定义枚举
NEWHOPE
EMPIRE
JEDI
}
type Human {
id: ID!
name: String!
appearsIn: [Episode]! # 使用枚举类型
homePlanet: String
}
例如,你可以用一个 Character 接口用以表示《星球大战》三部曲中的任何角色:
 
7 GraphQL Java 实现
通过前面的讲解,我们对 GraphQL 有了解,官方只是定义了规范并没有做实现,就需要有第三方来进行实现了,关
GraphQL java 实现有几种,我们选择使用官方推荐的实现: graphql-java ,我们通过该实现就可以编写
GraphQL 的服务端了。
官网: https://www.graphql-java.com/
github https://github.com/graphql-java/graphql-java
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
}
7.1 、开始使用
7.1.1 、创建工程
导入依赖:
<dependencies>
        <dependency>
            <groupId>com.graphql-java</groupId>
            <artifactId>graphql-java</artifactId>
            <version>11.0</version>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>
    </dependencies>
说明: graphql-java 包并没有发布到 maven 中央仓库,需要配置第三方仓库才能使用。
setting.xml 文件里进行配置:
7.1.2 、创建 User 对象
 
 
public class User {
 
private Long id ;
private String name ;
private Integer age ;
public User () {
}
 
# 对应的 User 定义如下
schema { # 定义查询
query: UserQuery
}
type UserQuery { # 定义查询的类型
user : User # 指定对象以及参数类型
}
type User { # 定义对象
id:Long! # ! 表示该属性是非空项
name:String
Java 实现:
age:Int
}
 
 
 

public class GraphQLDemo {

    public static void main(String[] args) {

        /**
         * type User { #定义对象}
         */
        GraphQLObjectType userObjectType = newObject()
                .name("User")
                .field(newFieldDefinition().name("id").type(GraphQLLong))
                .field(newFieldDefinition().name("name").type(GraphQLString))
                .field(newFieldDefinition().name("age").type(GraphQLInt))
                .build();

        /**
         * user : User #指定对象以及参数类型
         */
        GraphQLFieldDefinition userFieldDefinition = newFieldDefinition()
                .name("user")
                .type(userObjectType)
                .argument(newArgument().name("id").type(GraphQLLong).build())
                .dataFetcher(environment -> {
                    Long id = environment.getArgument("id");
                    // 查询数据库了
                    // TODO 先模式实现
                    return  new User(id, "张三:"+id, 20+id.intValue(), null);
                })
//                .dataFetcher(new StaticDataFetcher(new User(1L, "张三", 20)))
                .build();

        /**
         * type UserQuery { #定义查询的类型}
         */
        GraphQLObjectType userQueryObjectType = newObject()
                .name("UserQuery")
                .field(userFieldDefinition)
                .build();

        /**
         * schema { #定义查询 }
         */
        GraphQLSchema graphQLSchema = GraphQLSchema.newSchema().query(userQueryObjectType).build();

        GraphQL graphQL = GraphQL.newGraphQL(graphQLSchema).build();

        String query = "{user(id:100){id,name}}";
        ExecutionResult result = graphQL.execute(query);

        System.out.println("query:" + query);
//        System.out.println(result.getErrors());
//        System.out.println(result.getData());

        System.out.println(result.toSpecification());

    }
}
 

 
查询字符串: {User(id:1){id,name}}
错误: []
结果: {User = {id = 1 , name = 张三 _1}}
----------------
查询字符串: {User(id:1){id,name,age}}
错误: []
结果: {User = {id = 1 , name = 张三 _1, age = 21 }}
推荐使用: SDL 方法。
7.2.1 、创建 user.graphqls 文件

public class GraphQLSDLDemo {

    public static void main(String[] args) throws IOException {

        // 读取GraphQL文件,进行解析
        String fileName = "user.graphqls";
        String fileContent = IOUtils.toString(GraphQLSDLDemo.class.getClassLoader().getResource(fileName), "UTF-8");
        TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(fileContent);

        // 解决的是数据的查询
        RuntimeWiring wiring = RuntimeWiring.newRuntimeWiring()
                .type("UserQuery", builder ->
                        builder.dataFetcher("user", environment -> {
                            Long id = environment.getArgument("id");
                            Card card = new Card("123456789", id);
                            return new User(id, "张三:" + id, 20 + id.intValue(), card);
                        })
                )
                .build();

        // 生成Schema
        GraphQLSchema graphQLSchema = new SchemaGenerator().makeExecutableSchema(typeRegistry, wiring);

        // 根据Schema对象生成GraphQL对象
        GraphQL graphQL = GraphQL.newGraphQL(graphQLSchema).build();

        String query = "{user(id:100){id,name,age,card{cardNumber}}}";
        ExecutionResult result = graphQL.execute(query);

        System.out.println("query:" + query);
        System.out.println(result.toSpecification());

    }
}

public class Card {

    private String cardNumber;
    private Long userId;

    public Card() {
    }

    public Card(String cardNumber, Long userId) {
        this.cardNumber = cardNumber;
        this.userId = userId;
    }

    public String getCardNumber() {
        return cardNumber;
    }

    public void setCardNumber(String cardNumber) {
        this.cardNumber = cardNumber;
    }

    public Long getUserId() {
        return userId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }
}

 

user.graphqls

 

schema {
    query: UserQuery
}
type UserQuery {
    user(id:Long) : User
}
type User {
    id:Long!
    name:String
    age:Int
    card:Card
}

type Card{
    cardNumber:String
    userId:Long
}

六、参考文献

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值