EasyuseofGraphQL介绍GraphQL是Facebook开发的一套数据查询解决方案,我们先看看官方的定义:GraphQL是一种针对你的API的查询语言,是一种用于执行的服务端运行时使用您为数据定义的类型系统进行查询。GraphQL不依赖于任何特定的数据库或存储引擎,而是由您现有的代码和数据支持。翻译是:GraphQL是API查询语言的一部分,它提供了一个服务器端运行时解决方案,用于查询您使用类型系统定义的数据。GraphQL不依赖于特定的数据库或存储引擎,而是支持您现有的代码和数据。有两个关键点:一个查询语言服务器端运行时解决方案网上能找到的文章往往对第一个点描述得比较详细,这个点确实比较吸引人。但是对于第二个关键点,如何实现这个查询机制就不好找了。以一个简单的博客为例假设我们的博客有如下两张表:user表中的数据:uidnameavatar1Tomhttps://pre00.deviantart.net/...2Jerryhttps://vignette.wikia.nocook...post表中的数据in(考虑到允许用户修改头像,所以post表中没有多余的作者信息,只有作者ID):pidtitlecontentauthorId1fooxxx12baryyy2然后,界面大致是上下两栏模式,上部分是帖子标题、内容等;下半部分是作者姓名、头像等。我们来看看resuful和GraphQL方案的实现对比。如果restful接口采用restful方案,我们通常会设计如下两个接口:查询帖子内容:GET/posts/:id查询作者信息:GET/users/:id然后,前端先调用拉取内容的接口的帖子,并获得返回结果类似于以下内容:GET/posts/1{"code":0,"reason":"success","data":{"pid":1,"title":"foo","content":"xxx","authorId":1}}然后根据上面结果中的authorId,调用拉取用户信息的接口获取作者的相关信息:GET/users/1{“代码”:0,“原因”:“成功”,“数据”:{“uid”:1,“名称”:“汤姆”,“头像”:“https://pre00.deviantart.net/2930/th/pre/i/2014/182/a/2/tom_cat_by_1997ael-d7ougoa.png"}}在Web前端这样调用问题不大,但是遇到App时,由于绘图接口是集成的,在绘制接口之前需要调用两个restful接口。随着需求的变化,本页面可能还会显示评论、评论者的头像等;这会导致这里需要调用的接口越来越多,以便app更快地渲染这个接口。来得慢一点。GraphQL方法采用GraphQL方法。我们首先需要定义数据的类型:Userdefinition:#userschematypeUser{uid:ID!名称:字符串!avatar:String!}Postdefinition:#postschematypePost{pid:ID!标题:字符串!内容:字符串!author:User!}Querydefinition:typeQuery{post(id:ID):Post}然后我们根据接口要求写查询语句,因为接口需要同时显示post内容和作者信息,所以会有是下面的GraphQL查询语句:query{post(id:1){pidtitlecontentauthor{uidnameavatar}}}因为在数据定义中,post下的author成员是User类型的,所以我们只需要传一个查询能够获取绘制界面所需的数据:{"data":{"post":{"pid":"1","title":"foo","content":"xxx","作者”:{“uid”:“1”,“姓名”:“汤姆”,“头像”:“https://pre00.deviantart.net/2930/th/pre/i/2014/182/a/2/tom_cat_by_1997ael-d7ougoa.png"}}}}看到这里,想必大家也能体会到GraphQL查询语言的酷炫了,但是网上的大部分资料往往还在继续介绍这种查询语言的更多语法,但是对于如何executequeriesontheserverside不过介绍的不够深入。给出的简单示例甚至是上述数据结构中的每个成员变量都需要编写相应的解析器函数来查询的情况。针对上述GraphQL服务端解决方案的痛点,笔者在进行相关探索后,封装了一个更易用的npm库(easy-graphql)。easy-graphql设计了一套约定,让开发更加方便和规范:SQRS-Schemas,即数据的类型定义Q-Query,即对外提供的查询接口R-Resolvers,即函数如何查询数据的实现使用步骤1按照SQR约定创建一个目录。按照上面的约定创建一个目录结构,在指定的目录下存放相应的文件,比如上面的博客例子。我们创建的目录格式如下:创建一个graphql目录作为根目录,在graphql下创建schemas和resolvers两个子目录,分别用于存放数据类型定义文件和对应的查询解决方案实现函数文件。创建query.graphqls文件,用于定义对外提供的查询接口。#查询外部提供的接口定义文件├──resolvers#如何查询数据函数实现文件所在目录│├──post_resolver.js│└──user_resolver.js└──schemas#数据类型所在目录定义文件位于├──post_schema.graphqls└──user_schema.graphqls2.创建数据类型定义(模式)文件。文件存放在graphql/schemas目录下,命名规则:xxx_schema.graphqlspost和用户数据类型定义上面已经讲过,这里不再赘述3.在graphql目录,命名:query.graphqls4.在graphql/reslvers目录下创建一个数据查询功能实现(reslver)文件,命名规则:xxx_resolver.js下面是如何实现上面提到的post内容和作者信息的查询(resolvers/post_reslver.js):'usestrict'constfakeDB=require('../../fakeDB');functionfetchPostById(root,{id},ctx){//post查询,第二个参数是查询语句中传入的letpid=parseInt(id);rreturnfakeDB.getPostById(pid);}//post下author字段查询函数functionfetchUserByAuthorId(root,args,ctx){//执行post的数据查询后,如果需要author字段,这个函数会再次被调用,root参数为上一步查询到的post数据letuid=root.authorId;returnfakeDB.getUserById(uid);}constpostReolvers={Query:{post:fetchPostById,},Post:{//解析嵌套数据结构的函数author:fetchUserByAuthorId,},};module.exports=postReolvers;5.初始化并创建一个新的easy-graphql对象:constpath=require('path');consteasyGraphqlModule=require('easy-graphql');constbasePath=path.join(__dirname,'graphql');consteasyGraphqlObj=neweasyGraphqlModule(basePath);可视化IDE调试对于使用node.js的开发,GraphQL提供了一个可视化的图形Web界面来编写和调试查询语句express插件:express-graphqlKOA插件:koa-graphqleasy-graphql与express-graphql一起使用:constexpress=require('express');constgraphqlHTTP=require('express-graphql');constallSchema=easyGraphqlObj.getSchema();//使用express-graphqlmiddlewareapp.use('/graphql',graphqlHTTP({schema:allSchema,graphiql:true,}));然后就可以直接在浏览器中访问对应的url,打开可视化IDE调试了,现在的界面,效果如下图:使用上面直接界面形式的中间件方案,我们已经可以实现能够对外提供GraphQL查询,但是往往我们的项目已经有了约定好的返回数据结构,比如:{"code":0,"reason":"success","data":{...}但是直接使用插件形式无法自定义返回的数据结构,所以easy-graphql提供了直接执行GraphQL查询的StatementAPI:/***dotheGraphQLqueryexecute*@param{*}requestObj-GraphQLqueryobject{query:"..."}*@param{*}context-[optional]querycontext*@returns{Promise}-GraphQLexecutepromise*/queryGraphQLAsync(requestObj,{context})以express框架为例,我们可以为前端调用实现一个接口:constbodyParser=require('body-parser');app.use(bodyParser.json());//用于解析application/jsonapp.use(bodyParser.urlencoded({e扩展:真}));//用于解析application/x-www-form-urlencodedapp.post('/restful',async(req,res)=>{letqueryObj=req.body;letresult;try{//使用你的restful服务结果=awaiteasyGraphqlObj.queryGraphQL(queryObj,{context:req});}catch(err){console.error(err);res.json({code:-1,reason:"GraphQLerror"});返回;}res.json({代码:0,原因:“成功”,数据:result.data,});});完整示例完整代码示例请到gayhub上的test目录查看,欢迎大家为本项目点赞!参考资料GraphQLApolloGraphQL简介
