Getting Started with GraphQL and Spring Boot – 开始使用GraphQL和Spring Boot

最后修改: 2017年 8月 22日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.介绍

GraphQL is a relatively new concept from Facebook, billed as an alternative to REST for Web APIs.

GraphQL是Facebook提出的一个相对较新的概念,被誉为Web API的REST的替代方案。

In this tutorial, we’ll learn how to set up a GraphQL server using Spring Boot so that we can add it to existing applications or use it in new ones.

在本教程中,我们将学习如何使用Spring Boot设置GraphQL服务器,以便将其添加到现有的应用程序或在新的应用程序中使用。

2. What Is GraphQL?

2.什么是GraphQL??

Traditional REST APIs work with the concept of Resources that the server manages. We can manipulate these resources in some standard ways, following the various HTTP verbs. This works very well as long as our API fits the resource concept but quickly falls apart when we need to deviate from it.

传统的REST API是以服务器管理的资源的概念来工作的。我们可以按照各种HTTP动词,以一些标准的方式操作这些资源。只要我们的API符合资源的概念,就能很好地工作,但当我们需要偏离它的时候,就会很快崩溃。

This also suffers when the client needs data from multiple resources simultaneously, such as requesting a blog post and comments. Typically, this is solved by having the client make multiple requests or having the server supply extra data that might not always be required, leading to larger response sizes.

当客户端同时需要来自多个资源的数据时,这也会受到影响,例如请求一个博客文章和评论。通常情况下,这可以通过让客户端发出多个请求或让服务器提供可能并不总是需要的额外数据来解决,从而导致更大的响应规模。

GraphQL offers a solution to both of these problems. It allows the client to specify exactly what data it desires, including navigating child resources in a single request and allows for multiple queries in a single request.

GraphQL为这两个问题提供了一个解决方案。它允许客户端准确地指定它所需要的数据,包括在单个请求中导航子资源,并允许在单个请求中进行多次查询。

It also works in a much more RPC manner, using named queries and mutations instead of a standard mandatory set of actions. This works to put the control where it belongs, with the API developer specifying what’s possible and the API consumer specifying what’s desired.

它还以更多的RPC方式工作,使用命名的查询和突变,而不是标准的强制性动作集。这样做的目的是将控制权放在它所属的地方,由API开发人员指定可能的内容,由API消费者指定所需的内容。

For example, a blog might allow the following query:

例如,一个博客可能允许以下查询。

query {
    recentPosts(count: 10, offset: 0) {
        id
        title
        category
        author {
            id
            name
            thumbnail
        }
    }
}

This query will:

这个查询将。

  • request the ten most recent posts
  • for each post, request the ID, title, and category
  • for each post, request the author, returning the ID, name, and thumbnail

In a traditional REST API, this either needs 11 requests, one for the posts and 10 for the authors or needs to include the author details in the post details.

在传统的REST API中,这要么需要11个请求,一个用于帖子,10个用于作者,要么需要在帖子细节中包括作者的详细信息。

2.1. GraphQL Schemas

2.1. GraphQL模式

The GraphQL server exposes a schema describing the API. This schema consists of type definitions. Each type has one or more fields, each taking zero or more arguments and returning a specific type.

GraphQL服务器公开了一个描述API的模式。这个模式由类型定义组成。每个类型有一个或多个字段,每个字段接受零个或多个参数并返回一个特定的类型。

The graph is derived from the way these fields are nested with each other. Note that the graph doesn’t need to be acyclic, cycles are perfectly acceptable, but it is directed. The client can get from one field to its children, but it can’t automatically get back to the parent unless the schema defines this explicitly.

该图是由这些字段相互嵌套的方式得出的。请注意,该图不需要是无环的,循环是完全可以接受的,但它是有方向的。客户端可以从一个字段到它的子字段,但它不能自动回到父字段,除非模式明确定义了这一点。

An example GraphQL Schema for a blog may contain the following definitions describing a Post, the Author of the post, and a root query to get the most recent posts on the blog:

一个博客的GraphQL模式的例子可能包含以下定义,描述一个帖子,帖子的作者,以及一个根查询,以获得博客上最近的帖子。

type Post {
    id: ID!
    title: String!
    text: String!
    category: String
    author: Author!
}

type Author {
    id: ID!
    name: String!
    thumbnail: String
    posts: [Post]!
}

# The Root Query for the application
type Query {
    recentPosts(count: Int, offset: Int): [Post]!
}

# The Root Mutation for the application
type Mutation {
    createPost(title: String!, text: String!, category: String, authorId: String!) : Post!
}

The “!” at the end of some names indicates that it’s a non-nullable type. Any type that doesn’t have this can be null in the response from the server. The GraphQL service handles these correctly, allowing us to safely request child fields of nullable types.

一些名字后面的”!”表示它是一个不可归零的类型。任何没有这个的类型在服务器的响应中都可能是空的。GraphQL服务正确地处理了这些,使我们能够安全地请求可归零类型的子字段。

The GraphQL Service also exposes the schema using a standard set of fields, allowing any client to query for the schema definition ahead of time.

GraphQL服务还使用一套标准的字段来公开模式,允许任何客户端提前查询模式定义。

This allows the client to automatically detect when the schema changes and allows clients to adapt dynamically to how the schema works. One incredibly useful example is the GraphiQL tool, which allows us to interact with any GraphQL API.

这允许客户端自动检测模式的变化,并允许客户端动态地适应模式的工作方式。一个非常有用的例子是GraphiQL工具,它允许我们与任何GraphQL API交互。

3. Introducing GraphQL Spring Boot Starter

3.介绍GraphQL Spring Boot Starter

The Spring Boot GraphQL Starter offers a fantastic way to get a GraphQL server running in a very short time. Using autoconfiguration and an annotation-based programming approach, we need only write the code necessary for our service.

Spring Boot GraphQL Starter为在很短的时间内运行GraphQL服务器提供了绝佳的方法。利用自动配置和基于注解的编程方法,我们只需编写服务所需的代码。

3.1. Setting up the Service

3.1.设置服务

All we need for this to work is the correct dependencies:

我们所需要的是正确的依赖关系,这样才能工作。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-graphql</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Because GraphQL is transport-agnostic, we’ve included the web starter in our config. This exposes the GraphQL API over HTTP using Spring MVC on the default /graphql endpoint. Other starters can be used for other underlying implementations, such as Spring Webflux.

由于GraphQL与传输无关,我们在配置中包含了web启动器。这通过使用Spring MVC在默认的/graphql端点上通过HTTP暴露GraphQL API。其他启动器可用于其他底层实现,如Spring Webflux。

We can also customize this endpoint in our application.properties file if necessary.

如果有必要,我们也可以在我们的application.properties文件中定制这个端点。

3.2. Writing the Schema

3.2.编写模式</b

The GraphQL Boot starter works by processing GraphQL Schema files to build the correct structure and then wires special beans to this structure. The Spring Boot GraphQL starter automatically finds these schema files.

GraphQL Boot启动器通过处理GraphQL模式文件来建立正确的结构,然后将特殊的Bean连接到这个结构。Spring Boot GraphQL启动器会自动找到这些模式文件

We need to save these “.graphqls” or “.gqls” schema files under src/main/resources/graphql/** location, and Spring Boot will pick them up automatically. As usual, we can customize the locations with spring.graphql.schema.locations and the file extensions with spring.graphql.schema.file-extensions config properties.

我们需要将这些”.graphqls“或”.gqls“模式文件保存在src/main/resources/graphql/**位置下,Spring Boot会自动接收它们。像往常一样,我们可以用spring.graphql.schema.location自定义位置,用spring.graphql.schema.file-extensions配置属性自定义文件扩展。

The one requirement is that there must be exactly one root query and up to one root mutation. Unlike the rest of the schema, we can’t split this across files. This is a limitation of the GraphQL Schema definition, not the Java implementation.

有一个要求是,必须正好有一个根查询和最多一个根突变。与模式的其他部分不同,我们不能将其分割到不同的文件中。这是GraphQL模式定义的一个限制,而不是Java实现的限制。

3.3. Root Query Resolver

3.3.根查询解析器

The root query needs to have specially annotated methods to handle the various fields in this root query. Unlike the schema definition, there’s no restriction that there only be a single Spring bean for the root query fields.

根查询需要有专门的注释方法来处理这个根查询中的各个字段。与模式定义不同,没有限制根查询字段只能有一个Spring Bean。

We need to annotate the handler methods with @QueryMapping annotation and place these inside standard @Controller components in our application. This registers the annotated classes as data fetching components in our GraphQL application:

我们需要@QueryMapping注解来注解处理方法,并将这些方法放在我们应用程序中的标准@Controller组件内。这就把注释的类注册为我们GraphQL应用程序中的数据获取组件。

@Controller
public class PostController {

    private PostDao postDao;

    @QueryMapping
    public List<Post> recentPosts(@Argument int count, @Argument int offset) {
        return postDao.getRecentPosts(count, offset);
    }
}

The above defines the method recentPosts, which we’ll use to handle any GraphQL queries for the recentPosts field in the schema defined earlier. Additionally, the method must have parameters annotated with @Argument that correspond to the corresponding parameters in the schema.

上面定义了方法recentPosts,我们将用它来处理前面定义的模式中recentPosts字段的任何GraphQL查询。此外,该方法必须有用@Argument注释的参数,与模式中的相应参数相对应。

It can also optionally take other GraphQL-related parameters, such as GraphQLContext, DataFetchingEnvironment, etc., for access to the underlying context and environment.

它还可以选择其他与GraphQL相关的参数,如GraphQLContext,DataFetchingEnvironment,等,用于访问底层上下文和环境。

The method must also return the correct return type for the type in the GraphQL scheme, as we’re about to see. We can use any simple types, String, Int, List, etc., with the equivalent Java types, and the system just maps them automatically.

该方法还必须为GraphQL方案中的类型返回正确的返回类型,正如我们即将看到的。我们可以使用任何简单的类型,String, Int, List,等,与之对应的Java类型,系统只是自动映射它们。

3.4. Using Beans to Represent Types

3.4.使用Bean来表示类型

Every complex type in the GraphQL server is represented by a Java bean, whether loaded from the root query or from anywhere else in the structure. The same Java class must always represent the same GraphQL type, but the name of the class isn’t necessary.

GraphQL服务器中的每个复杂类型都由一个Java Bean表示,无论是从根查询还是从结构中的其他地方加载。相同的Java类必须始终代表相同的GraphQL类型,但该类的名称不是必须的。

Fields inside the Java bean will directly map onto fields in the GraphQL response based on the name of the field:

Java Bean内的字段将根据字段的名称直接映射到GraphQL响应中的字段:

public class Post {
    private String id;
    private String title;
    private String category;
    private String authorId;
}

Any fields or methods on the Java bean that don’t map onto the GraphQL schema will be ignored but won’t cause problems. This is important for field resolvers to work.

Java Bean上没有映射到GraphQL模式的任何字段或方法将被忽略,但不会导致问题。这对字段解析器的工作很重要。

For example, here, the field authorId doesn’t correspond to anything in the schema we defined earlier, but it will be available to use for the next step.

例如,在这里,字段authorId并不对应于我们先前定义的模式中的任何内容,但它将可用于下一步。

3.5. Field Resolvers for Complex Values

3.5.复杂值的字段解析器

Sometimes, the value of a field is non-trivial to load. This might involve database lookups, complex calculations, or anything else. The @SchemaMapping annotation maps the handler method to a field with the same name in the schema and uses it as the DataFetcher for that field.

有时,一个字段的值的加载是不简单的。这可能涉及到数据库查询、复杂的计算或其他任何事情。@SchemaMapping注解将处理方法映射到模式中具有相同名称的字段,并将其作为该字段的DataFetcher。

@SchemaMapping
public Author author(Post post) {
    return authorDao.getAuthor(post.getAuthorId());
}

Importantly, if the client doesn’t request a field, then the GraphQL Server won’t do the work to retrieve it. This means that if a client retrieves a Post and doesn’t ask for the author field, the author() method above won’t be executed, and the DAO call won’t be made.

重要的是,如果客户端没有请求一个字段,那么GraphQL服务器将不会进行检索工作。这意味着,如果客户端检索了一个Post,但没有询问author字段,上面的author()方法将不会被执行,DAO调用也不会被进行。

Alternatively, we can also specify the parent type name, and the field name in the annotation:

另外,我们也可以在注释中指定父类型名称和字段名称。

@SchemaMapping(typeName="Post", field="author")
public Author getAuthor(Post post) {
    return authorDao.getAuthor(post.getAuthorId());
}

Here, the annotation attributes are used to declare this as the handler for the author field in the schema.

这里,注释属性被用来声明这是模式中author字段的处理程序。

3.6. Nullable Values

3.6.可置换值

The GraphQL Schema has the concept that some types are nullable and others aren’t.

GraphQL模式有一个概念,即有些类型是可归零的,有些则不是。

We handle this in the Java code by directly using null values. Conversely, we can use the new Optional type from Java 8 directly for nullable types, and the system will do the correct thing with the values.

我们在Java代码中通过直接使用null值来处理这个问题。相反,我们可以直接使用Java 8中新的Optional类型来处理nullable类型,系统会对这些值做正确的处理。

This is very useful, as it means that our Java code is more obviously the same as the GraphQL schema from the method definitions.

这非常有用,因为这意味着我们的Java代码从方法定义上与GraphQL模式更明显地相同。

3.7. Mutations

3.7.突变

So far, everything we’ve done has been about retrieving data from the server. GraphQL also has the ability to update the data stored on the server through mutations.

到目前为止,我们所做的一切都是为了从服务器上检索数据。GraphQL也有能力通过突变来更新存储在服务器上的数据。

From the code’s point of view, there’s no reason that a Query can’t change data on the server. We could easily write query resolvers that accept arguments, save new data, and return those changes. Doing this will cause surprising side effects for the API clients and is considered bad practice.

从代码的角度来看,查询没有理由不改变服务器上的数据。我们可以很容易地写出接受参数的查询解析器,保存新数据,并返回这些变化。这样做会给API客户端带来令人惊讶的副作用,被认为是不好的做法。

Instead, Mutations should be used to inform the client that this will cause a change to the data being stored.

相反,应该使用突变来通知客户,这将导致正在存储的数据发生变化

Similar to Query, mutations are defined in the controller by annotating the handler method with @MutationMapping. The return value from a Mutation field is then treated exactly the same as from a Query field, allowing nested values to be retrieved as well:

与查询类似,突变是在控制器中通过对处理方法进行@MutationMapping注释来定义的。来自突变字段的返回值与来自查询字段的返回值的处理方式完全相同,也允许检索嵌套值。

@MutationMapping
public Post createPost(@Argument String title, @Argument String text,
  @Argument String category, @Argument String authorId) {

    Post post = new Post();
    post.setId(UUID.randomUUID().toString());
    post.setTitle(title);
    post.setText(text);
    post.setCategory(category);
    post.setAuthorId(authorId);

    postDao.savePost(post);

    return post;
}

4. GraphiQL

4.GraphiQL

GraphQL also has a companion tool called GraphiQL. This UI tool can communicate with any GraphQL Server and helps to consume and develop against a GraphQL API. A downloadable version of it exists as an Electron app and can be retrieved from here.

GraphQL还有一个名为GraphiQL的配套工具。这个UI工具可以与任何GraphQL服务器通信,并帮助消费和开发GraphQL API。它的可下载版本作为Electron应用程序存在,可以从这里检索到。

Spring GraphQL comes with a default GraphQL page that is exposed at /graphiql endpoint. The endpoint is disabled by default but it can be turned on by enabling the spring.graphql.graphiql.enabled property. This provides a very useful in-browser tool to write and test queries, particularly during development and testing.

Spring GraphQL带有一个默认的GraphQL页面,在/graphiql端点处公开。该端点默认是禁用的,但可以通过启用spring.graphql.graphiql.enabled属性来打开它。这提供了一个非常有用的浏览器内工具来编写和测试查询,特别是在开发和测试期间。

5. Summary

5.总结

GraphQL is a very exciting new technology that can potentially revolutionize how we develop Web APIs.

GraphQL是一项非常令人兴奋的新技术,有可能彻底改变我们开发Web API的方式。

Spring Boot GraphQL Starter makes it incredibly easy to add this technology to any new or existing Spring Boot application.

Spring Boot GraphQL Starter使得将这项技术添加到任何新的或现有的Spring Boot应用程序中变得异常简单。

As always, code snippets can be found on GitHub.

一如既往,代码片段可以在GitHub上找到