一种用于 API 的查询语言。前端
GraphQL是一种新的API标准,它提供了一种更高效、强大和灵活的数据提供方式。它是由Facebook开发和开源,目的是为了解决因前端交互不断变化,与后端接口须要同步修改的痛点。java
通常开发中,后端服务为前端提供接口会有两种考虑方式:git
对于第一种状况,前端的体验是比较好的,一个页面只须要等待请求一次接口的时间,但当页面发生变化的时候,后端接口的维护成本是比较高的,并且随之带来的新老接口的兼容也是不能忽视的问题。 对于第二种状况,后端的接口是相对固定的,可是前端每每就须要一个页面请求不少个接口,才能知足页面展现的须要,用户须要为此等待较长的时间,用户体验不高。github
为了解决上面的问题,GraphQL是一种很是好的解决方案。GraphQL由后端按照定义好的标准Schema的方式提供接口,就能够不用再改变。而前端根据本身页面的须要,自行构造json查询相应数据,服务端也只会为前端返回json里所描述的信息。当前端页面发生变化的时候,前端只须要修改本身的查询json便可,后端能够彻底无感。这就是GraphQL所带来的好处,双方只依赖标准的Schema进行开发,再也不依赖于彼此。sql
所有代码均可以在此下载。数据库
能够先按照官方的开发文档进行学习,里面提到的代码片断并不彻底,Github上面有完整的代码,能够做为补充。编程
首先是开发服务端,我参照了官方文档中的例子。第一步须要先定义好咱们的全部实体类,放入schema中,我项目中文件名为myschema.graphqls
,放在java的resource目录下。json
schema {
query: QueryType
mutation: MutationType
}
type QueryType {
hero(episode: Episode): Character
human(id : String) : Human
droid(id: ID!): Droid
}
type MutationType {
wirte(text: String!): String!
}
enum Episode {
NEWHOPE
EMPIRE
JEDI
}
interface Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
}
type Human implements Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
homePlanet: String
}
type Droid implements Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
primaryFunction: String
}
复制代码
schema中QueryType表明了查询类型,MutationType表明着写入类型。 咱们须要把咱们所用到的全部实体类都定义在此处(枚举和接口也是支持的),这个文件就是未来要交给前端去理解的内容,是咱们全部接口的生成依据。swift
定义好schema以后,第二步就是编写DataFetcher和Resolver。后端
在此Demo中,由于Character是一个接口,因此须要提供一个Character的Resolver:
val characterTypeResolver: TypeResolver = TypeResolver { env -> val id = env.getObject<Map<String, Any>>()["id"] when { // humanData[id] != null -> StarWarsSchema.humanType // droidData[id] != null -> StarWarsSchema.droidType humanData[id] != null -> env.schema.getType("Human") as GraphQLObjectType droidData[id] != null -> env.schema.getType("Droid") as GraphQLObjectType else -> null } } 复制代码
这里的逻辑比较简单粗暴,是判断humanData里是否能找到这个id,若是找到,就认为是humanData,不然去droidData中找。实际项目中咱们的逻辑应该要更严谨一些。
由于咱们第一步定义了schema,因此没有歧义的类型均可以从schema中进行推断,只有像接口这种不能推断的类型才须要Resolver。若是咱们没有schema文件,那么就须要为每一个实体类都编写Resolver,项目中StarWarsSchema这个文件就是定义了全部的类型以及解析方式。具体项目中,这两种方式能够二选其一,我我的推荐是用myschema.graphqls
这样的方式去定义,毕竟语义清晰,便于维护。
接下来就是如何提供接口了。
读取graphql的schema文件:
@Throws(IOException::class) private fun readSchemaFileContent(): String { val classPathResource = ClassPathResource("myschema.graphqls") classPathResource.inputStream.use { inputStream -> return CharStreams.toString(InputStreamReader(inputStream, Charsets.UTF_8)) } } 复制代码
提供Fetcher和Resolver:
private fun buildRuntimeWiring(): RuntimeWiring { return RuntimeWiring.newRuntimeWiring() // this uses builder function lambda syntax .type("QueryType") { typeWiring -> typeWiring .dataFetcher("hero", StaticDataFetcher (StarWarsData.artoo)) .dataFetcher("human", StarWarsData.humanDataFetcher) .dataFetcher("droid", StarWarsData.droidDataFetcher) .dataFetcher("field", StarWarsData.fieldFetcher) } .type("Human") { typeWiring -> typeWiring .dataFetcher("friends", StarWarsData.friendsDataFetcher) } // you can use builder syntax if you don't like the lambda syntax .type("Droid") { typeWiring -> typeWiring .dataFetcher("friends", StarWarsData.friendsDataFetcher) } // or full builder syntax if that takes your fancy .type( newTypeWiring("Character") .typeResolver(StarWarsData.characterTypeResolver) .build() ) .type( newTypeWiring("Episode") .enumValues(StarWarsData.episodeResolver) .build() ) .build() } 复制代码
生成GraphQLSchema:
@Throws(IOException::class) fun graphQLSchema(): GraphQLSchema { val schemaParser = SchemaParser() val schemaGenerator = SchemaGenerator() val schemaFileContent = readSchemaFileContent() val typeRegistry = schemaParser.parse(schemaFileContent) val wiring = buildRuntimeWiring() return schemaGenerator.makeExecutableSchema(typeRegistry, wiring) } 复制代码
提供查询接口:
@RequestMapping("/api") @ResponseBody fun api(@RequestBody body: String): String { val turnsType = object : TypeToken<Map<String, Any>>() {}.type var map: Map<String, Any> = Gson().fromJson(body, turnsType) var query = map["query"]?.toString() var params = map["variables"] as? Map<String, Any> var build: GraphQL? = null try { build = GraphQL.newGraphQL(graphQLSchema()).build() } catch (e: IOException) { e.printStackTrace() } var input = ExecutionInput.newExecutionInput().query(query) if (params != null) { input = input.variables(params!!) } val executionResult = build!!.execute(input.build()) // Prints: {hello=world} var result = mutableMapOf<String, Any>() result["data"] = executionResult.getData<Any>() return Gson().toJson(result) } 复制代码
完成以上几步,前端就能够经过/api接口来请求数据了。其中query是放咱们的查询json,variables是放json里面须要用到的一些参数。
咱们能够看到,graphql的类帮咱们作了不少事,咱们只须要写好schema,提供好数据的解析方式和查询结果便可。前端的任何方式组合查询,graphql都会分别调用咱们写好的fetcher,自动组装数据并返回。
为了测试咱们的接口,能够经过浏览器访问一些测试的json来检验,Github上面的单元测试代码能够方便的拿到咱们想要的json进行测试。
我仅用iOS写了一个Demo,Android用法应该相似,就再也不赘述。
第一步是先安装Apollo的Pod。
pod 'Apollo', '~> 0.9.4' 复制代码
而后是生成schema.json,这个schema.json就是根据以前服务端定义的schema和各类Resolver的信息,自动生成的一个json文件,专门给前端使用。首先服务端还须要新写如下接口:
@RequestMapping("/graphql") @ResponseBody fun graphql(): String { var ghql = IntrospectionQuery.INTROSPECTION_QUERY var build: GraphQL? = null try { build = GraphQL.newGraphQL(graphQLSchema()).build() } catch (e: IOException) { e.printStackTrace() } val executionResult = build!!.execute(ghql) // Prints: {hello=world} return Gson().toJson(executionResult.getData<Any>()) } 复制代码
而后浏览器请求该接口,能够获得一个json,该json就是schema.json的全部内容。须要注意的是,json中有一句:
defaultValue":"\"No longer supported\""
复制代码
里面的两个转义的引号必定不能去掉。
而后在项目的Build Phases中加入如下自动执行的脚本:
APOLLO_FRAMEWORK_PATH="$(eval find $FRAMEWORK_SEARCH_PATHS -name "Apollo.framework" -maxdepth 1)" if [ -z "$APOLLO_FRAMEWORK_PATH" ]; then echo "error: Couldn't find Apollo.framework in FRAMEWORK_SEARCH_PATHS; make sure to add the framework to your project." exit 1 fi cd "${SRCROOT}/${TARGET_NAME}" $APOLLO_FRAMEWORK_PATH/check-and-run-apollo-cli.sh codegen:generate --passthroughCustomScalars --queries="$(find . -name '*.graphql')" --schema=schema.json API.swift 复制代码
随后咱们能够把想查询的json也放入文件中,例如simpleQuery.graphql:
query HeroNameQuery {
hero {
name
}
}
复制代码
切记要把它和schema.json放在同一个目录下。
以后只须要编译,咱们便能在这个目录下看到新生成一个API.swift文件,把它引入工程。这个文件包含了graphql为咱们生成的全部查询所要用到的类。
在想要查询的地方只须要这么使用便可:
let query1 = HeroNameQueryQuery() apollo.fetch(query: query1) { result, error in let hero = result?.data?.hero print(hero?.name ?? "") } 复制代码
GraphQL带来的好处是服务端与客户端的接口解耦,固然也有一些局限,例如对性能的影响。若是全是内存级的数据查询还好,不然若是是SQL数据库,而且结构与结构之间有关联,就比较吃性能了。例如产品和订单,订单关联一个产品,若是是普通接口,一个sql的join就能够查出产品和订单两个实体的全部信息。但用GraphQL,就会有两个查询sql须要执行,一个是根据id查产品,一个是根据id查订单,再把两者的数据组合返回给前端。
固然,若是这样相似的数据作一级缓存,也是能够解决的,可是毕竟给服务端仍是带来了很多的麻烦,在写数据查询接口的时候,就并不能只考虑某一个实体了,而是要思考这个实体和其余实体之间可能的联系,是否要作缓存,是否会有和其余实体同时被查询的可能性。
另外,要服务端人员把接口全都转变成GraphQL的方式也是一个很大的挑战,不只是对编程的思惟上,对整个服务端架构都是会有很大的影响的,须要慎重评估。
但毋庸置疑的是,GraphQL的出现必定很是受人喜好,特别是在前端不断变化的时代,它在将来的前景不可估量。
全部的项目代码均可以在此下载。