第2课 GraphQL服务搭建

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第2课 GraphQL服务搭建相关的知识,希望对你有一定的参考价值。

参考技术A

在 GraphQL(一):GraphQL介绍 中讲到目前已经有很多平台完成了GraphQL实现,这里以Java平台为例,介绍GraphQL服务的搭建。

graphql-java 是GraphQL的Java实现,它实现了GraphQL的执行,但是没有任何关于HTTP或者JSON的处理,因此在接入SpringBoot时还需要 graphql-java-spring 的支持。官方的 案例 就是使用这两个jar包完成的。

在官方的 案例 中,我们需要实例化一个GraphQL实例:

这样的实现需要我们了解较多graphql-java的底层细节,比如:TypeDefinitionRegistry、RuntimeWiring、SchemaGenerator等,同时还需要硬编码字符串。

同样,在实现数据注入时,也需要硬编码:

于是就有了 graphql-spring-boot-starter + graphql-java-tools 搭建GraphQL的方案。

graphql-java-tools 能够从GraphQL的模式定义 .graphqls 文件构建出对应的Java的POJO类型对象(graphql-java-tools将读取classpath下所有以 .graphqls为后缀名的文件,创建GraphQLSchema对象),同时为我们屏蔽了graphql-java的底层细节,它本身依赖graphql-java。

graphql-spring-boot-starter 是辅助SpringBoot接入GraphQL的库,它本身依赖graphql-java和 graphql-java-servlet (将GraphQL服务发布为通过HTTP可访问的Web服务,封装了一个GraphQLServlet接收GraphQL请求,并提供 Servlet Listeners 功能)。

接下来我们将实现一个基于 graphql-spring-boot-starter + graphql-java-tools 搭建GraphQL服务的Demo。

对应的SpringBoot版本是1.5.6

以及对应的Dao、Service、teacher.xml等

这里的模型最好和Java Bean一致,如果Java bean中有多余的字段,将被忽略,不会抛出异常。

这是公开API的地方,按照GraphQL的规范,Query、Mutation、Subscription三种查询类型需要放在各自的节点下(这里暂时不考虑订阅):

graphql-java-tools为我们屏蔽了底层细节,我们只需要继承以下几个类完成数据注入即可:

Resolver完成的是数据的注入,也就是对*.graphqls文件中的type的字段的数据进行注入,注入需要满足以下规则:

比如我们我们根据学校Id查询学校的API:

我们在schema.graphqls中定义的类型有与之对应的Java Bean,这些Java Bean都提供了getField方法,因此不需要额外实现Resolver,有时候,在type中定义的类型的某个字段数据的获取比较麻烦,不是简单的getField可以解决的,此时可以为此类型实现专门的字段值获取的Resolver,假设School中的master字段逻辑获取逻辑很复杂:

泛型中需要指定类型,字段数据获取的方法名称规则和常规接口的规则一致,只是需要把该类型作为参数传递到方法内,值得注意的是,如果客户端没有请求Master字段,那么getMaster方法将不会被执行。

实际上针对type中的每个Field都需要有getField,使得Graphql能够获取到数据注入到返回的结果中,如果针对此Field已经实现了Resolver,那么会优先使用Resolver来注入数据,此时可以省略掉getField(直接去掉School Bean中的master字段)不过还是建议将Java Bean和type中的Field一一对应,便于维护。

以上是针对Query的Demo,关于Mutation请查看文本的源码,这里需要说明的是我们的insertSchool和insertTeacher有些不同:

insertTeacher引入了一个新类型TeacherInput,将需要传递到服务端的数据封装起来,GraphQL的返回类型(Teacher)和输入类型(TeacherInput)是不能共用的,所以加上Input后缀加以区分,同样的,针对TeacherInput也需要有对应的Java Bean。

搭建你的第一个 GraphQL 服务器

原文:

没错,我今天就是要带着大家搭建一个简单的 服务器。我并不是想要说服你推翻原来所有代码拥抱 GraphQL——目的只是为了满足大家对它的好奇心,如果想要了解它是如何工作的,请继续往下读。

搭建 HTTP 服务器

我们需要创建一个服务器用来接收 GraphQL 查询。虽然 GraphQL 规范里并没有要求需要必须基于 HTTP 协议来实现,但是既然 是基于 JavaScript 的,所以使用 快速搭建一个 HTTP 服务器是比较合适的。

$ mkdir graphql-intro && cd ./graphql-intro $ npm install express --save $ npm install babel --save $ touch ./server.js $ touch ./index.js

创建项目目录 graphql-intro,并安装 Express 和 依赖。Babel 不是 GraphQL 必须要用的,只是用 Babel 就可以使用 的特性,这样生活会美好很多。

最终代码如下:

// index.js // by requiring `babel/register`, all of our successive `require`s will be Babel'd require('babel/register'); require('./server.js'); // server.js import express from 'express'; let app = express(); let PORT = 3000; app.post('/graphql', (req, res) => { res.send('Hello!'); }); let server = app.listen(PORT, function () { let host = server.address().address; let port = server.address().port; console.log('GraphQL listening at http://%s:%s', host, port); });

启动服务器:

$ node index.js GraphQL listening at http://0.0.0.0:3000

测试服务器是否正常工作:

$ curl -XPOST http://localhost:3000/graphql Hello!

这里我们在 /graphql 路径上提供服务,支持 HTTP POST 调用。但这并不是定死的——GraphQL 规范里并没有指定通过何种方式来与 GraphSQL 服务器通信。

创建 GraphQL Schema

服务器已经好了,这个时候就可以“添加一些 GraphQL”了。怎么理解这句话?

我们先回忆一下单个 GraphQL 请求的样子:

query getHighScore { score }

在这个请求中,GraphQL 客户端请求顶级字段 getHighScore 下面的 score 子字段。字段(Field)就是我们需要 GraphQL 服务器返回的内容。字段本身可以携带参数,例如:

query getHighScores(limit: 10) { score }

多的略去不表,我们继续:

我们需要配置 GraphQL 服务器来响应这样的请求——如何配置?使用 Schema。

构造一个 Schema 就相当于构造一棵 RESTful 的路由树。Schema 描述服务器可以返回的字段,以及返回字段所包含的类型。类型信息对于 GraphQL 来说是非常重要的,客户端可以安全地认为服务端返回的字段类型与配置的是一致的(如果不一致,就会返回错误信息)。

可以预见的是,Schema 可以定义得非常复杂。不过对我们手上这个简单 GraphQL 服务器而言,仅仅只有一个字段 count

继续运行如下命令:

$ npm install graphql --save $ npm install body-parser --save $ touch ./schema.js

这很容易理解对吧?包含了 GraphQL 『预览』技术,我们可以用来构建服务器上的 Schema,在 Schema 上进行 GraphQL 查询。 是一个简单的 Express 中间件,用来获取 GraphQL 请求中的信息。

编写 Schema:

// schema.js import { GraphQLObjectType, GraphQLSchema, GraphQLInt } from 'graphql/lib/type'; let count = 0; let schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'RootQueryType', fields: { count: { type: GraphQLInt, resolve: function() { return count; } } } }) }); export default schema;

上面的代码就是创建了一个 GraphQLSchema 实例,这个构造函数接受一个巨大的配置对象。事实上 GraphQL 类库的其他部分会来消费这个 Schema 对象,但把这个消费这个过程放在其他的文件里更加合适。
把 Schema 转成通俗的语言来讲就是:

顶级查询对象会返回一个 RootQueryType 对象,有一个 count 字段,这个字段的类型是整数。

可想而知,GraphQL 还支持其他的基础类型(字符串、列表等等),而且你可以深度嵌套自定义的复杂类型。

连接 Schema

如果不对 Schema 进行查询,它写得再漂亮也不会产生任何用处。接下来我们把 Schema 和 HTTP 服务器联系到一起:

import express from 'express'; import schema from './schema'; // new dependencies import { graphql } from 'graphql'; import bodyParser from 'body-parser'; let app = express(); let PORT = 3000; // parse POST body as text app.use(bodyParser.text({ type: 'application/graphql' })); app.post('/graphql', (req, res) => { // execute GraphQL! graphql(schema, req.body) .then((result) => { res.send(JSON.stringify(result, null, 2)); }); }); let server = app.listen(PORT, function () { var host = server.address().address; var port = server.address().port; console.log('GraphQL listening at http://%s:%s', host, port); });

任何发送给 /graphql 的 POST 的请求都会触发 GraphQL Schema 的查询。针对 GraphQL 请求我们强制采用了一种新的 content type——这并不是 GraphQL 规范的内容,但这对于那些混了 GraphQL 和老代码的服务器来说是一种好的模式。

重启服务器,访问:

$ node ./index.js // restart your server // in another shell $ curl -XPOST -H "Content-Type:application/graphql" -d 'query RootQueryType { count }' http://localhost:3000/graphql { "data": { "count": 0 } }

正确!GraphQL 还允许省略掉 query RootQueryType 前缀,不过我想尽量少展示一些 『魔法』,比如像下面这样是可行的:

$ curl -XPOST -H 'Content-Type:application/graphql' -d '{ count }' http://localhost:3000/graphql { "data": { "count": 0 } }

现在我们终于整了点可以运行的 GraphQL,接下来让我花点时间讨论一下自省特性。

让服务器是自省的

一个有意思的事实:可以编写 GraphQL 查询来询问 GraphQL 服务器都有哪些字段。这非常 。

听起来有些疯狂,你可以试试:

$ curl -XPOST -H 'Content-Type:application/graphql' -d '{__schema { queryType { name, fields { name, description} }}}' http://localhost:3000/graphql { "data": { "__schema": { "queryType": { "name": "RootQueryType", "fields": [ { "name": "count", "description": null } ] } } } }

下面是我们发出的查询,格式化了一下:

{ __schema { queryType { name, fields { name, description } } } }

一般的,每一个 GraphQL 根字段都会自动包含一个,该字段也提供了一些字段供我们查询——比如 queryType 字段。可以查到每一个字段的 meta 信息,比如字段名称等。

更有趣的是,你还可以添加更多的供人阅读的元信息,比如为字段添加descriptionisDeprecateddeprecationReason。Facebook 表示它们的工具可以使用这些元数据为开发者提供更好的开发体验(例如,如果你使用的字段已经废弃,会有一条红色的波浪线在它下面)。

给当前的 schema 添加一个 description,大家可以看得更明白一些:

let schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'RootQueryType', fields: { count: { type: GraphQLInt, // add the description description: 'The count!', resolve: function() { return count; } } } }) });

重启服务器就可以看到新加的元数据:

$ curl -XPOST -H 'Content-Type:application/graphql' -d '{__schema { queryType { name, fields { name, description} }}}' http://localhost:3000/graphql { "data": { "__schema": { "queryType": { "name": "RootQueryType", "fields": [ { "name": "count", "description": "The count!" } ] } } } }

我们旋风式的 GraphQL 介绍马上就要结束了——还差最后一个,我将给大家讲讲 mutation(修改)。

添加 Mutation

这一节是给那些不是只读读数据的人准备的。对于大部分程序来说,我们需要修改数据。GraphQL 把这些操作称为 Mutation。

Mutation 也是一种字段,只不过是有副作用的字段,因此在语法上并没什么不同。Mutation 字段必须指定返回值类型——意思也就是说,如果你修改了某个字段,也必须返回修改后的值。

如何在 Schema 中添加 Mutation?和顶级的 query 定义类似,需要定义一个顶级的 mutation 字段。

let schema = new GraphQLSchema({ query: ... mutation: // todo )}

除了添加的地方不一样,Mutation 还有什么不同的地方?我们本可以让 count字段函数更新我们的计数器,或者做一些其他的修改操作,GraphQL 也全然不知的呀。

Mutation 与 Query 不同地方就在于 GraphQL 对前者的操作更为严格,而对于 Query 并没有这种保证(实际上,GraphQL 鼓励服务器利用内在的独立的并行查询)。下面的例子来自 GraphQL 规范,一系列 Mutation 查询在服务器上必须串行执行。

{ first: changeTheNumber(newNumber: 1) { theNumber }, second: changeTheNumber(newNumber: 3) { theNumber }, third: changeTheNumber(newNumber: 2) { theNumber } }

因此,这个请求结束,theNumber 字段的值一定是 2。接下来给我们的计数器加上一个简单的 mutation,返回计数器的值:

let schema = new GraphQLSchema({ query mutation: new GraphQLObjectType({ name: 'RootMutationType', fields: { updateCount: { type: GraphQLInt, description: 'Updates the count', resolve: function() { count += 1; return count; } } } }) });

重启服务器试运行:

$ curl -XPOST -H 'Content-Type:application/graphql' -d 'mutation RootMutationType { updateCount }' http://localhost:3000/graphql { "data": { "updateCount": 1 } }

轰——数据已经更新了!你可以通过下面的查询来确认:

$ curl -XPOST -H 'Content-Type:application/graphql' -d '{ count }' http://localhost:3000/graphql { "data": { "count": 1 } }

你想查询多少次都可以——可变的状态很有趣!

从 GraphQL 来讲,我们还可以编写更好的实现,可以把计数器封装成一个语义化的类型(比如 CountValue),Query 和 Mutation 的时候都返回这个类型,这样更有意义。

总结

这是一篇快速入门的文章,介绍如何借助于 Facebook GraphQL 的 JavaScript 实现进行 GraphQL 开发。并没有涉及更多强大的特性——带参数的字段、Promise、Fragment 和指令等等,在 GraphQL 规范里包含了很多有趣的东西。我们的服务器还可以使用更多 Schema 的 API,还可以实现更多东西。你可以试着想象,一个使用像 Java 这样的强类型语言编写的 GraphQL 服务器,很可能看起来和 JavaScript 写的完全不一样。

本文出自我 48 小时的 GraphQL 使用经验——如果哪里说得不对,希望你可以不吝指出。源代码可以在这里 看到(每一个提交都是一个小的进步)。

还要感谢 RisingStack 的伙计们,他们关于 都很棒。


以上是关于第2课 GraphQL服务搭建的主要内容,如果未能解决你的问题,请参考以下文章

2-10 就业课(2.0)-oozie:1314clouderaManager的服务搭建

用 GraphQL 快速搭建服务端 API

技术博客 | 用 GraphQL 快速搭建服务端 API

第十期基于 ApolloKoa 搭建 GraphQL 服务端

CoolBlog开发笔记第2课:搭建开发环境

第 0 课 Golang环境搭建