GraphQL的认识与使用

前言

GraphQL已经被越来越多的开源项目作为业务接口的开发规范,而在此之前流行的是restful接口。那么GraphQL是什么?GraphQL相比restful有哪些好处?本文带你初窥GraphQL的门道,看看你们的业务是否适合使用GraphQL。

GraphQL是什么

GraphQL = Graph + QL (query language)

GraphQL是一种图表化(graph)的查询语言(query language),用于定义API的查询语法。GraphQL只定义api查询语法和数据规范,没有限制前端和后端的类型,也没有限制存储层的实现。

可以类比SQL,SQL仅定义了一套数据操作与查询语言的规范,Mysql,oracle等各大数据库开放了符合SQL标准的接口,当然你可以自己开发一个使用SQL查询的业务接口。

官网解释:

GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data

GraphQL既是一种用于API的查询语言也是一个满足你数据查询的运行时

GraphQL的组成结构

1. schema

GraphQL通过schema定义api和数据结构,schema通常以文件形式存在,包含一系列的对象定义。对象结构为类json格式的字符串。

根据总结,对象定义的格式遵守以下规则:

${对象类型} ${对象名称} {
  #字段
  ${字段名称}: ${字段类型}
  #字段类型为数组
  ${字段名称}: [${字段类型}]
  #字段类型为非空对象
  ${字段名称}: ${字段类型}!
  #带参数的字段
  ${字段名称} (${对象名称} : ${对象类型}): ${字段类型}
}

GraphQL定义了一些标量类型,可作为对象的字段定义。标量类型有:Int、Float、String、Boolean、ID

其中ID类型是GraphQL定义的唯一标识符,其本质上也是字符串。

GraphQL支持枚举类型enum,枚举类型定义示意:

enum Gender {
  male
  female
}

GraphQL的对象类型主要分为两类,数据对象和接口对象,分别用于定义接口的输入输出和接口实体。

数据对象

  • type

自定义名称的类型(关键字除外),用于定义接口查询的返回对象类型以及内联对象类型。

type Man {
  name: String
  age: Int
  gender: Gender
}
  • interface

接口类型interface,和type对象定义基本一致。

interface Human {
  name: String
  age: Int
  gender: Gender
}

对象可以通过implements申明实现了某个接口对象,实现对象必须包含接口对象的所有字段,方便对相同业务属性进行抽象。

type Man implements Human {
  name: String
  age: Int
  gender: Gender
}
  • input

输入类型input本质上也是一个type类型,是作为Mutation接口的输入参数。换言之,想要定义一个修改接口(add,update,delete)的输入参数对象,就必须定义一个input输入类型。

input ManInput {
  name: String
  age: Int
  gender: Gender
}

值得一提的是,在input对象中的对象也必须是input类型,这可能是一个比较局限的设计。

接口对象

接口对象分为数据查询(select)接口和数据操作(add,update,delete)接口,分别用Query关键字和Mutation关键字来定义。下面举两个例子:

定义一个名为search的查询接口,返回对象为Human类型:

type Query {
  search(name: String): Human
}

定义一个名为add的数据插入方法,方法参数为Human类型,返回对象也为Human类型:

type Mutation {
  add(human: Human): Human
}

Mutation类型不对数据的操作含义进行区分,不像SQL一样,有ADD、UPDATE、DELETE,统一使用Mutation进行定义,具体的操作含义在接口的实现中完成。

2. query

通过schema对api进行定义之后,后续发起的graphql api请求都必须严格遵守这个定义。特别的,在graphql的api请求中可以指定返回对象的部分而非全部的字段,从而减少一些不必要的数据传输。

GraphQL的api请求只有Query和Mutation两种,下面简单介绍下Query和Mutation的查询语法。

简单query语句

//调用search接口,传入一个名为name的字符串参数,期望的返回类型是Human对象中的age字段
query {
  search(name: "马云"): {
    age
  }
}

带参数的graphql api请求附带一个专门用于描述请求参数json数据,json的key和api参数名称对应。

带对象参数的query语句

//调用search接口,传入一个名为human的Human对象参数,期望的返回值是Human对象中的age字段
query queryHuman($human: Human) {
  search(human: $human): {
    age
  }
}

//graphql的参数,以json格式给出,对应类型Human
graphql variables
{
  "human": {
    "name": "马云"
  }
}

带对象参数的Mutation语句

//调用add接口,传入一个名为human的Human对象参数,期望的返回值是human对象的三个字段
mutation addHuman($human: Human) {
  add(human: $human): {
    name
    age
    gender
  }
}

//graphql的参数,以json格式给出,对象的结构和schema定义必须一致
graphql variables

{
  "human": {
    "name": "马云",
    "age": 50,
    "gender": "male"
  }
}

3. datafetchers

datafetcher其实是graphql-java中的一个组件,其作用是查询和操作业务数据,并提供api的返回值。datafetcher会通过显式的申明和query绑定起来,从而实现api和数据层的交互。

任何一门语言实现graphql都需要类似datafetcher这样一个组件来实现api和数据层的交互。

GraphQL的认识与使用_第1张图片

GraphQL能带来什么好处?

使用GraphQL能带来什么好处呢?我总结主要有以下几点:

  1. 强一致性

graphql的协议规定了api和schema的定义必须保持一致,这种文档先行的开发模式,使得项目的架构更加清晰,并且使得接口本身就多了一层校验。

  1. 减少数据冗余

graphql的api在请求时可以指定返回的字段,返回的字段和预期是一致的。这一特性可以让接口在不同的场景下返回必需的字段,减少部分网络开销,并且使得接口的通用性更高。

  1. 自文档性

得益于graphql的强一致性,使得graphql的schema本身就可以作为api接口文档使用。配合graphql的一些第三方库,可以做到api文档的在线调试和可视化展示,变成真正的图化(graph)查询语言(query language)。

graphiql插件的在线调试api功能

GraphQL的认识与使用_第2张图片

graphql voyager的图形化api查看

GraphQL的认识与使用_第3张图片

GraphQL有什么缺点?

来说说我认为graphql的主要缺点吧:

  1. 简单问题复杂化

为了实现graphql充分的规范性,也加重了一些开发成本(虽然已经有第三方库可以做到快速开发),任何一个api在开发时都有明确的两步走:1.在schema定义接口和对象 2.实现datafecher,哪怕是一个最简单的get请求。

  1. 不方便统一控制

graphql不像restful接口,有清晰的层级划分(/a/b/c的结构),graphql的api只能使用接口名称进行区分,接口名称不像层级结构那么容易管理。通常在restful结构下,我们可以通过拦截器对符合某一规则的接口路径进行统一处理(比如对/restful/*的接口进行身份认证),而graphql的请求路径是在同一个url下的,这种处理逻辑就比较难以实现了。

GraphQL框架支持

  1. graphql-js

graphql官方的js实现,可以快速的搭建js端的graphql服务器,和进行graphql请求。

官方文档地址:

https://graphql.cn/graphql-js/

  1. graphql-java

graphql官方的java实现,前文也介绍过了,是基于datafetcher的架构,代码比较容易理解,缺点是开发量较大。

官方文档地址:

https://www.graphql-java.com/documentation/v16/

使用graphql java开发graphql接口的demo


public static void main(String[] args) {

	//1. 定义schema,这里是字符串直接给出了schema
	String schema = "type Query{hello: String}";

	SchemaParser schemaParser = new SchemaParser();
	TypeDefinitionRegistry typeDefinitionRegistry = schemaParser.parse(schema);

	//2. 定义datafetcher,并和query进行绑定;这里的datafetcher会返回一个固定值world
	RuntimeWiring runtimeWiring = newRuntimeWiring()
		.type("Query", builder ->
			builder.dataFetcher("hello", new StaticDataFetcher("world")))
		.build();

	SchemaGenerator schemaGenerator = new SchemaGenerator();
	GraphQLSchema graphQLSchema = schemaGenerator.makeExecutableSchema(typeDefinitionRegistry, runtimeWiring);

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

	//3. 查询时指定查询名hello,不带任何参数,也不指定返回值
	ExecutionResult executionResult = build.execute("{hello}");

	System.out.println(executionResult.getData().toString());
	// Prints: {hello=world}
}
  1. graphql-kick-start

第三方实现的一个开源graphql java库,优点是能快速接入,配合springboot开发,几乎不需要写代码,并且集成了很多方便的插件如 graphiql,graphql voyager,可以实现真正的图化查询。

官方github地址:

https://github.com/graphql-java-kickstart/graphql-spring-boot

使用graphql-kick-start的graphql servlet插件快速开发graphql接口的demo

#启用 graphql servlet
graphql.servlet.enabled=true
#graphql servlet 使用的url
graphql.servlet.mapping=/graphql
#schema文件路径
graphql.tools.schema-location-pattern=person.schema
person.schema

type Query {
  personByName(name: String): Person
}

type Person {
  id: Int
  name: String
}
//GraphQLQueryResolver的实现类中的方法会自动和schema中的query绑定
@Component
public class PostQuery implements GraphQLQueryResolver {

    public Person personByName(String name) {
        PersonEntity personEntity = personMapper.findByName(name);
        return new Person(personEntity);
    }
}

GraphQL的用途

基于graphql的种种特性,以及graphql的库支持的程度来看(似乎官方也认定graphql是用于前后端接口开发的,并未提供除了js之外的客户端实现),目前而言,graphql似乎只适用于前后端之间的接口开发。所幸的是,graphql和restful并不冲突,前端接口可以直接剥离出来,使用graphql规范重新实现,且不影响原有接口。

综上所述,graphql主要用于实现前后端之间的接口,且有graphql本身的强一致性带来了很多规范上的优势。

你可能感兴趣的:(GraphQL的认识与使用)