许多人认为GraphQL仅适用于前端和JavaScript,它在Java等后端技术中没有定位,但事实确实如此。
还经常将GraphQL与REST进行比较,但是这种比较是否合理?
首先,让我开始回答其中最重要的问题。 什么是GraphQL?
如果您查看官方网站,将会看到类似的内容
“ GraphQL是API的查询语言,并且是服务器端运行时,用于通过使用为数据定义的类型系统来执行查询。 GraphQL不受任何特定数据库或存储引擎的束缚,而是由您现有的代码和数据支持。”
实际上应该说的是
GraphQL是一个规范,仅此而已。
要记住这一点很重要,因为作为开发人员,我们将使用GraphQL的实现。 一些实现已经或多或少地实现了GraphQL规范中的功能。 有许多语言的实现,例如JavaScript,Java,PHP,Go和其他语言。 每天都有不同语言和现有语言的新实现。
如果您来自Java背景并且有很多REST API,那么您首先会感兴趣的是GraphQL与多年来开发的Traditional REST API有何不同。
让我将其放在一个简单的博客的上下文中,该博客由博客文章,博客文章的作者组成,并且可以在博客文章中添加评论。
从数据库的角度来看,这意味着我们有三个表
让我们假设前端是只读的,并从Traditional REST API获取数据,然后将数据呈现给用户。 如果我们要构建这种传统的REST API,则可能最终会得到类似以下的代码
@RestController public class SimpleRestController { @RequestMapping (path= "/authors" ) public List getAllAuthors() { ... } @RequestMapping (path= "/authors/{id}" ) public Author getAuthorById( @PathVariable String id) { ... } @RequestMapping (path= "/posts" ) public List getAllPosts( @RequestParam (value= "author_id" , required = false ) String authId) { ... } @RequestMapping (path= "/comments" ) public List getAllComments( @RequestParam (value= "post_id" , required = false ) String postId) { ... } }
因此,在这种情况下,如果我们想显示包含作者信息和评论的帖子,我们首先需要致电
- /帖子
获取所有帖子,然后找到我们想要的帖子,查看authorId是什么,然后调用
- / authours / <帖子中的ID>
之后,我们需要致电
- / comments?post_id = <相关帖子的ID>
以获取该帖子的所有评论。
显然,这不是最佳方法。 当然,在这种情况下,我们所有人都会做的就是看好我们API的用例,并牢记这一点来优化端点和响应。 也许我们会将评论嵌入帖子,作者信息或类似内容中。 或者,由于某种原因,如果我们认为没问题,也许我们不会改变任何事情。 无论如何,我们将决定用户可以呼叫哪些端点,以及他们将获得什么样的响应。
确切地说,这是GraphQL的最大区别。 对于GraphQL,通常只有一个端点,例如
- / graphql
该端点将获取对您的API的所有请求,并发送回所有响应。
起初听起来有点奇怪。 最简单的方法是拥有完整的示例代码。 我将使用一个这样的示例中的代码片段。 要获取完整的代码,只需点击以下URL :https://github.com/vladimir-dejanovic/simple-springboot-graphql-mongo-conftalk-demo
要记住的重要一点是,在GraphQL中,一切都始于架构。 如果我们转到上面的示例,博客文章,GraphQL模式可能看起来像这样:
type Author { id: ID! name: String! posts: [Post] } type Post { id: ID! title: String! body: String createdBy: Author! comments: [Comment] } type Comment { id: ID! createdBy: Author! belongsTo: Post! text: String } schema { query: Query } type Query { allPosts: [Post] allAuthors: [Author] }
我们从定义类型开始,对于将为表创建的POJO,类型几乎可以是1到1。 首先,我们输入一个名称,然后输入。 字符' ! '具有特殊含义,表示该字段是必填字段。 如果字段具有此字符并且不存在响应,则它将是无效响应,并且GraphQL将不会将响应发送回去,但会发送适当的错误。
关于模式要记住的重要一点是,所有请求和响应都将使用模式进行验证。 如果请求未通过架构验证,则服务器将不执行任何工作。 另外,如果响应未通过架构验证,则不会将其发送到客户端。
如果您选中“作者”类型,您将看到它具有“帖子数组”类型的字段帖子。 另外,Post具有类型为Author和comments的createdBy字段,其类型为Comment的Array。 这些字段在POJO的中不存在
Author.java public class Author { private final String id; private final String name; .....get/set } Post.java public class Post { private final String id; private String authorId; private final String title; private final String body; ...get/set }
类似的是注释类型,我稍后会再讲。 定义类型之后,我们可以进入GraphQL模式的核心
schema { query: Query }
这是我们定义与用户互动的地方。 我们说用户可以使用下面定义的Query类型的查询来读取数据。
type Query { allPosts: [Post] allAuthors: [Author] }
Query是一种特殊类型,因为我们在DB中没有此数据,这实际上是传统思维方式中的端点。
如果您是从GitHub链接下载代码,进行编译并启动的,则可以转到http:// localhost:8080 / 。 然后,您将看到名为GraphiQL的漂亮用户界面。 您可以使用GraphiQL来玩GraphQL API
为了获得所有带有ID,标题和正文的帖子,只需将其输入GraphiQL
query { allPosts { id title body } }
响应应如下所示
{ "data" : { "allPosts" : [ { "id" : "59f4c12e7718af0b1e001072" , "title" : "Who is Ed Wong" , "body" : "Edward Wong Hau Pepelu .....” }, . . . . }
例如,如果我们对身体不感兴趣,我们可以输入这样的内容
query { allPosts { id title } }
那么响应将是这样的
{ "data" : { "allPosts" : [ { "id" : "59f4c12e7718af0b1e001072" , "title" : "Who is Ed Wong" , }, . . . . }
如您所见,当涉及到GraphQL时,用户在响应中并不总是获得相同的预定义字段集。 用户可以选择说出哪些字段应该发回,哪些不应该。
允许这样做的Java代码不是那么大。 首先,我们需要定义扩展SimpleGraphQLServlet的 Servlet。
public class GraphQLEntryPoint extends SimpleGraphQLServlet { public GraphQLEntryPoint(PostRepository postRepository, AuthorRepository authRepository, CommentRepository commentRepository) { super (buildSchema(postRepository, authRepository, commentRepository)); } private static GraphQLSchema buildSchema(PostRepository postRepository, AuthorRepository authRepository, CommentRepository commentRepository) { return SchemaParser .newParser() .file( "schema.graphqls" ) .resolvers( new Query(postRepository, authRepository), new PostResolver(authRepository, commentRepository), new AuthorResolver(postRepository), new CommentResolver(authRepository, postRepository)) .build() .makeExecutableSchema(); } }
在这里,我创建模式解析器,该解析器打开我的GraphQL模式文件,之后添加解析器,然后调用build和makeExecutableSchema方法。
这里的重要部分是解析器。 解析器是GraphQL将用于解决用户请求的类。
首先,最重要的是Query类。 它与模式中的Query类型具有相同的名称并非偶然。 这就是java GraphQL实现如何从架构中知道哪个类对应于查询逻辑的。 您可以使用任何喜欢的名称,只要该类具有相同的名称即可,但是,这意味着新人们也需要知道该名称,因此请保持标准,并且对于只读使用Query。
这是类查询的代码
public class Query implements GraphQLRootResolver { private final PostRepository postRepository; private final AuthorRepository authRepo; public List<Post> allPosts() { return postRepository.findAll(); } public List<Author> allAuthors() { return authRepo.findAll(); } }
它实现了GraphQLRootResolver ,并且您可以看到GraphQL模式中的每一行都有一个方法。
有一个叫allPost方法,该方法返回后的名单,也有方法allAuthors返回作者列表。 为了使我们的API能够正常工作,这就是所有这些。
如果您返回到GraphiQL并输入像这样的输入
query { allPosts { id title createdBy { name } } }
响应将是这样的
{ "data" : { "allPosts" : [ { "id" : "59f4c12e7718af0b1e001072" , "title" : "Who is Ed Wong" , "createdBy" : { "name" : "Ed Wong” } }, . . . ] }
您会突然得到所有数据,而这不是Post pojo的一部分。 正如我们所看到的,Query类并没有做任何魔术,它只是返回Post类型的普通pojo列表。 那么,createdBy字段的作者信息从何而来?
为此,我们需要查看另一个解析器PostResolver,以使其更加精确,因此让我们看一下它的代码
public class PostResolver implements GraphQLResolver<Post> { private final AuthorRepository authRepository; private final CommentRepository commentRepository; public Author createdBy(Post post) { return authRepository.findOne(post.getAuthorId()); } public List<Comment> comments(Post post) { return commentRepository.findByPostId(post.getId()); } }
PostResolver实现了GraphQLResolver ,我们不得不说是哪种类型,在这种情况下,是Post类型 。 如您所见,Post中存在模式中的所有字段,但Pojo Post中不存在。 有一个createdBy方法,该方法采用Post类型的参数并返回Author。
此外,还有方法注释 ,该方法注释也采用Post类型的参数并返回Comment列表。
这就是全部,这就是我在代码中使用的GraphQL的java实现如何知道如何解析pojo中不存在的字段的方式。 在pojo的情况下,这非常简单,如果用户请求该字段,则只需调用适当的get方法,对于其他字段,必须为实现GraphQLResolver的类型提供解析器,并且需要一种具有正确签名和返回类型的方法。
如您所见,与我们一直以来创建的传统REST API相比,使用GraphQL,用户可以更好地控制他/她将获取哪些数据以及采用哪种格式。 因此,从用户的角度来看,这当然具有更好的用户体验,因为它具有更大的灵活性。 但是,这也意味着后端需要完成许多工作,因此系统在高负载下仍能正常运行。
在传统的REST API中,作为开发人员,我们完全控制用户与端点的交互方式,他们将获得什么样的响应以及用户请求将遵循的路径在我们的代码中。 如我们所见,使用GraphQL不再是这种情况。 我们知道的是,用户将点击解析器,而不是如何或通过哪个路径。 因此,优化困难得多。
幸运的是,并不是所有的东西都丢失了,我们仍然可以使用许多旧的技巧来解决这些新的/旧的问题。 例如,如果采用传统的REST API,解决高性能问题的一种方法是拥有一个带有端点的控制器,调用服务,然后该服务将承担繁重的工作。 在此设置中,我们可以缓存所有对服务的调用,并以这种简单的方式获得良好的性能。 我们可以用GraphQL做类似的事情,唯一的区别是控制器将调用服务,而不是控制器调用服务。
使用GraphQL,问题可能会更加棘手,但是,可以结合使用一些过去的技巧来使用过去的许多技术。 当然,每天都会出现许多解决问题的新方法。
我只在这里向您展示了如何读取数据,您当然也可以创建/编辑/修改数据,并使用GraphQL进行更多操作。 与GraphQL在构建API中提供的功能时,我与您分享的内容只是从头开始。
您需要记住的重要一点是,尽管GraphQL相对较新,但没有它也可以实现它提供的所有功能。 但是,在这种情况下,您将需要考虑允许用户做什么以及他们如何将请求发送到您的API。 对于GraphQL,其他人已经考虑过了,而您所要做的就是实现它。
最后,GraphQL API是REST API,这是高级REST API,具有许多功能和特性,因此更加精确。 这就是为什么问自己的问题,这是一件好事,您是否真的需要GraphQL提供的功能,并且会为您的API和为此API构建域的域增加更多的问题或解决方案。 也许GraphQL正是您所需要的,但也许又是旧的传统REST API所需要的。
资源资源
- 代码示例https://github.com/vladimir-dejanovic/simple-springboot-graphql-mongo-conftalk-demo
- GraphQL Java实现https://github.com/graphql-java/graphql-java
- 弗拉基米尔·德雅诺维奇(Vladimir Dejanovic)在摩洛哥Devoxx上的Talk GraphQL vs传统REST API https://www.youtube.com/watch?v=2FH93GaoIto
翻译自: https://www.javacodegeeks.com/2017/12/gentle-intro-graphql-java-world.html