RestFul的缺陷
假设,此时我们有两个资源分别是Book和Author,这两个资源对应的ER图如下
POST /books
GET /books/{id}
POST /authors
GET /authors/{id}
我们有一个需求,需要查询id=1的图书信息!
那我们的请求地址是这样的
GET /books/1
返回结果是这样的
[cc][{
“id”: 1,
“bookname”: Harry Potter,
“price”: 56.00,
“author_id”: 2
}][/cc]
这时候前端MM拿到这个结果后,傻了眼!这里怎么能直接返回author_id呢,难道直接把author_id显示在界面上么?不可能啊,界面上要显示的是author_name才行!
OK,那么在这种情况下,有两种方式可以解决问题!
(1) 跟后端沟通,让他增加一个接口
VO(View Object):视图对象,用于展示层,它的作用是把某个指定页面(或组件)的所有数据封装起来。
那可以让后端封装一个接口,后端帮你把数据拼装好,提供API如下
GET /bookVOs/1
这样直接返回的结果就是
[cc]
[{
“id”: 1,
“bookname”: Harry Potter,
“price”: 56.00,
“author_name”: J. K. Rowling
}][/cc]
OK,回到正题,这样做的缺点主要有两个
前后端强耦合在一起,前端界面发生变动,后端VO对象跟着一起变
如果BookVO对象在手机端、PC端、APP端的显示内容都不一样,你可能在项目中会有BookPcVO类、BookH5VO类、BookAppVO类,VO类大大膨胀!
(2) 自己做适配
这个也很简单,前端获得结果后,取出author_id: 2这条记录,然后再去调用地址
Graphql的出现
OK,首先你要声明一下,你的图结构,嗯,用的就是Graphql的语法啦,像下面这样
[cc]type Book {
id: Int
bookname: String
authors: [Author]
}
type Author {
id: Int
name: String
age: String
}
type Query {
getBook(id: Int): Book
}
type Mutation {
addComment(input: AddCommentInput): Comment
}
input AddCommentInput { … }
schema {
query: Query
}[/cc]
对上面的语句进行一下解释。这里一共声明了三个类Book、Author、Query!其实,Book和Author大家都可以猜都出来是指啥,需要注意的一点是authors: [Author]这个地方,用了一个[]的语法,这代表Author是一个数组!
唯一需要说明的是,Query是什么鬼?
Query在这儿表明了该类型是这张图的入口,也就是根节点!我们的查询必须从根节点里的属性开始!这里我为了便于说明,只列了一个属性,也就是getBook,该属性是入口,必须从入口开始查!
这里还有一个Resolver的概念,就是说,GragphQL解析到getBook方法的时候,方法体内容是啥?是在Resovler中定义的,我这里就不贴配置了,感兴趣的可以自己去官网阅读!
OK,接下来就是第二个问题,怎么查?
根据上面我说的,只能从根开始,根是getBook,那语句怎么写呢?如下所示
那你想加个字段,显示作者名字呢?直接像下面这么写
一些疑问
(1)java语言中,对Graphql的支持如何?
在java中,有个jar包为graphql-java-tools提供了对Java的支持。
另外,考虑到现在大多是springboot项目,有大神封装好了starter包,供你们的springboot项目使用!
只需在项目中引入
[cc lang=”xml”]
com.graphql-java
graphql-spring-boot-starter
4.0.0
com.graphql-java
graphql-java-tools
4.3.0
[/cc]
路由处理器(Route Handlers)vs. 解析器(Resolvers)
首先使用Express实现一个hello world:
app.get('/hello', function (req, res){
res.send('Hello World!')
})
可以看到一个REST API请求的的生命周期:
- 服务器收到请求并提取出HTTP方法名(比如这里就是GET方法)与URL路径
- API框架找到提前注册好的、请求路径与请求方法都匹配的代码
- 该段代码被执行,并得到相应结果
- API框架对结果进行序列化,添加上适当的状态码与响应头后,返回给客户端
GraphQL差不多也是这样工作的,我们来看下这个对应的hello world例子:
const resolvers = {
Query: {
hello:() => {
return'Hello world!';
},
},
};
//我们看到,这里并没有针对某个URL路径提供函数,而是把Query类型中的hello
字段映射到一个函数上了。 在GraphQL中这样的函数我们称之为解析器(Resolver)。
然后我们就可以这样发起一个查询:
query {
hello
}
// 如下响应 { "hello": "Hello, world!" }
至此,总结一下服务器对一个GraphQL请求的执行过程:
- 服务器收到HTTP请求,取出其中的GraphQL查询
- 遍历查询语句,调用里面每个字段所对应的Resolver。在这个例子里,只有Query这个类型中的一个字段hello
- Resolver函数被执行并返回相应结果
- GraphQL框架把结果根据查询语句的要求进行组装