Graphql+SpringBoot看这一篇就够了

「这是我参与2022首次更文挑战的第7天,活动详情查看:2022首次更文挑战」。

3分钟集成GraphQL

GraphQL是一个从服务端检数据的查询语言。某种程度上,是REST、SOAP、或者gRPC的替代品。

假设我们要从在线商店的后台查询一本特定书记的详细信息。

使用GraphQL,你可以发送以下的查询到服务端来获取id为“book-1”的书籍的详细信息。

{
  bookById(id: "book-1"){
    id
    name
    pageCount
    author {
      firstName
      lastName
    }
  }
}
复制代码

这不是JSON,虽然看起来很像。他是一个GraphQL查询语句。语句的含义是:

  • 查询指定id的一本书
  • 返回书籍的id、name、pageCount、author
  • 对于author字段,还需要包含firstName和lastName

返回结果是一个正常的JSON格式:

{
  "bookById":
  {
    "id":"book-1",
    "name":"Harry Potter and the Philosopher's Stone",
    "pageCount":223,
    "author": {
      "firstName":"Joanne",
      "lastName":"Rowling"
    }
  }
}
复制代码

GraphQL一个非常重要的特性--它是静态类型的:服务端明确的知道你可以查询的每个对象的类型,客户端可以从服务端获取“schema”。Schema描述了哪些查询是可能的,哪些字段能被获取到。(注意:我们这里说的Schema指的是GraphQL Schema,他与其他Schema比如JSON Schema获取DataBase Schema无关)

上面查询的Schema如下所示:

type Query {
  bookById(id: ID): Book
}

type Book {
  id: ID
  name: String
  pageCount: Int
  author: Author
}

type Author {
  id: ID
  firstName: String
  lastName: String
}
复制代码

这个教程将重点介绍如何使用Schema在java中实现一个GraphQL服务。

GraphQL Java概述

GraphQL Java是GraphQL的Java服务端的实现。在GraphQL Java Github org上有几个仓库。最重要的一个是GraphQL Java Engine(github.com/graphql-jav…

GraphQL Java Engine只关心查询的执行。它不处理任何HTTP或者JSON相关的事。基于这些方面,我们将用GraphQL Java Spring Boot适配器,它负责通过SpringBoot基于HTTP暴露我们的API。

创建一个GraphQL Java服务主要的步骤:

  1. 定义GraphQL Schema
  2. 决定一个查询如何获取到数据

示例API:获取书的详情

我们的示例项目将会写一个简单的接口:获取指定书记的详细信息。虽然这不是一个全面的API,但对于本教程来说足够了。

创建一个SpringBoot项目

导入3个包:

        <dependency>
            <groupId>com.graphql-java</groupId>
            <artifactId>graphql-java</artifactId>
            <version>17.3</version>
        </dependency>
        <dependency>
            <groupId>com.graphql-java</groupId>
            <artifactId>graphql-java-spring-boot-starter-webmvc</artifactId>
            <version>2.0</version>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>31.0.1-jre</version>
        </dependency>
复制代码

前2个是GraphQL Java和GraphQL Java Spring。Guava不是必要的,但是它能让我们更方便点。

Schema

在src/main/resources下面创建一个新文件schema.graphqls,写入一下内容:

type Query {
  bookById(id: ID): Book
}

type Book {
  id: ID
  name: String
  pageCount: Int
  author: Author
}

type Author {
  id: ID
  firstName: String
  lastName: String
}
复制代码

这个Schema定义了一个顶级字段(在Query类型中):bookById,它返回指定熟记的详情。

它也定义了类型Book,包含字段:id,name,pageCount,author。author的类型是Author,这个在Book类型之后也定义了。

上面用来描述Schema的专用语言叫做Schema Definition Language 或者叫DSL。更多细节可以查看:graphql.org/learn/schem…

这个文件完成功后,我们就得使用它。

我们创建一个新的类GraphQLProvider,定义一个init方法,这个方法用来创建GraphQL实例:

@Component
public class GraphQLProvider {

    private GraphQL graphQL;

    @Bean
    public GraphQL graphQL() {
        return graphQL;
    }

    @PostConstruct
    public void init() throws IOException {
        URL url = Resources.getResource("schema.graphqls");
        String sdl = Resources.toString(url, Charsets.UTF_8);
        GraphQLSchema graphQLSchema = buildSchema(sdl);
        this.graphQL = GraphQL.newGraphQL(graphQLSchema).build();
    }

    private GraphQLSchema buildSchema(String sdl) {
      // TODO: 我们稍后将在这创建Schema
    }
}
复制代码

我们使用Guava的Resources去读取之前创建的文件。接下来创建GraphQLSchema和GraphQL实例。这个GraphQL实例通过带有@Bean注解的graphQL()方法作为一个Spring Bean被暴露。GraphQL Java Spring适配器将使用GraphQL实例让我们的schema生效,通过HTTP,默认url: /graphql。

我们还需要做的就是实现buildSchema方法,该方法创建GraphQLSchema实例,连接sdl和DataFetcher。

@Autowired
GraphQLDataFetchers graphQLDataFetchers;

private GraphQLSchema buildSchema(String sdl) {
    TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(sdl);
    RuntimeWiring runtimeWiring = buildWiring();
    SchemaGenerator schemaGenerator = new SchemaGenerator();
    return schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiring);
}

private RuntimeWiring buildWiring() {
    return RuntimeWiring.newRuntimeWiring()
            .type(newTypeWiring("Query")
                    .dataFetcher("bookById", graphQLDataFetchers.getBookByIdDataFetcher()))
            .type(newTypeWiring("Book")
                    .dataFetcher("author", graphQLDataFetchers.getAuthorDataFetcher()))
            .build();
}
复制代码

TypeDefinitionRegistry是schema文件的解析版本。SchemaGenerator将TypeDefinitionRegistry和RuntimeWiring结合起来,生成GraphQLSchema。

buildWiring方法使用graphQLDataFetchers bean去注册2个DataFetcher

  • 一个是通过指定id检索book
  • 一个是获取指定book的author

DataFetcher和如何实现GraphQLDataFetchers bean在下部分内容说明。

创建GraphQLGraphQLSchema实例总体过程如下所示: 创建过程

DataFetchers

也许GraphQL Java server最重要的概念就是DataFetcher:在查询执行的时候,一个DataFetcher获取一个字段的数据。

当GraphQL Java在执行一个查询时,它会为查询语句中的每个字段调用合适的DataFetcher。一个DataFetcher是一个接口,其中只有一个方法,发发只有一个DataFetcherEnvironment参数:

public interface DataFetcher<T> {
    T get(DataFetchingEnvironment dataFetchingEnvironment) throws Exception;
}
复制代码

重点:schema中的每个字段都有一个DataFethcer与之关联。如果你没有给某个字段定义一个DataFetcher,那么会使用默认的PropertyDataFetcher。我们稍后会消息讨论这点。

我们接下来创建一个新类GraphQLDataFetchers,它包含books和authors的样例list。

完整的实现如下所示:

@Component
public class GraphQLDataFetchers {

    private static List<Map<String, String>> books = Arrays.asList(
            ImmutableMap.of("id", "book-1",
                    "name", "Harry Potter and the Philosopher's Stone",
                    "pageCount", "223",
                    "authorId", "author-1"),
            ImmutableMap.of("id", "book-2",
                    "name", "Moby Dick",
                    "pageCount", "635",
                    "authorId", "author-2"),
            ImmutableMap.of("id", "book-3",
                    "name", "Interview with the vampire",
                    "pageCount", "371",
                    "authorId", "author-3")
    );

    private static List<Map<String, String>> authors = Arrays.asList(
            ImmutableMap.of("id", "author-1",
                    "firstName", "Joanne",
                    "lastName", "Rowling"),
            ImmutableMap.of("id", "author-2",
                    "firstName", "Herman",
                    "lastName", "Melville"),
            ImmutableMap.of("id", "author-3",
                    "firstName", "Anne",
                    "lastName", "Rice")
    );

    public DataFetcher getBookByIdDataFetcher() {
        return dataFetchingEnvironment -> {
            String bookId = dataFetchingEnvironment.getArgument("id");
            return books
                    .stream()
                    .filter(book -> book.get("id").equals(bookId))
                    .findFirst()
                    .orElse(null);
        };
    }

    public DataFetcher getAuthorDataFetcher() {
        return dataFetchingEnvironment -> {
            Map<String,String> book = dataFetchingEnvironment.getSource();
            String authorId = book.get("authorId");
            return authors
                    .stream()
                    .filter(author -> author.get("id").equals(authorId))
                    .findFirst()
                    .orElse(null);
        };
    }
}
复制代码

数据源

我们从这个类中的静态列表里获取books和authors,这会帮助你理解GraphQL对数据来源没有任何要求。这是GraphQL的优势:数据可以来自内存里的静态数据,来自数据库或者外部服务。

Book DataFetcher

我们第一个方法getBookByIdDataFetcher返回一个DataFetcher的实现--使用DataFetcherEnvironment返回指定的book。这个例子的意思是,我们需要从bookById字段拿到id参数,然后找到这个id对应的book。如果没有找到,返回null。

String bookId = dataFetchingEnvironment.getArgument("id");中的“id”指的是在schema中的query字段bookById

type Query {
  bookById(id: ID): Book
}
复制代码

Author DataFetcher

第二个方法getAuthorDataFetcher,返回一个DataFetcher的实现--获取指定book的author。和book的DataFetcher方法比较,这个我们没有获取参数,但是我们有一个book的实例。通过getSource方法可以获取到有效的父类字段,然后得到DataFetcher结果。这是一个重要的概念:GraphQL中每个字段的DataFetcher都是以自顶向下的方式被调用,父类的结果是子类DataFetcherEnvironmentsource属性。

我们接下来使用前面获取的book拿到authorId,然后使用和查询书相同的方式查询指定的author。

Default DataFetchers

我们只实现了2个DataFetchers。正如前面提及的,如果你没有实现,那么就会使用默认的PropertyDataFetcher。按我们的例子来讲,意思就是说Book.id,Book.name,Book.pageCount,Author.id,Author.firstname,Author.lastName都有一个默认的PropertyDataFetcher分别与之关联。

PropertyDataFetcher通过多种方式尝试在Java对象上获取属性。例子中java.util.Map很容易通过key查找属性。这对我们来说很好,因为book和Author的Maps的keys和schema中字段是一模一样的。比如,schema中我们定义Book类型的字段pageCount,book的DataFetcher返回的Map中有key pageCount. 因为字段名字和Map中的key一样,所以PropertyDateFetcher才能起作用。

让我们假设一下,我们有一个字段不匹配,book Map中的key是totalPages,而不是pageCount。这将会导致所有book的pageCount值为null,因为PropertyDataFetcher无法获取到正确的值。为了解决这个问题,你需要为Book.pageCount去注册一个新的DataFetcher,所下所示:

// In the GraphQLProvider class
private RuntimeWiring buildWiring() {
    return RuntimeWiring.newRuntimeWiring()
            .type(newTypeWiring("Query")
                    .dataFetcher("bookById", graphQLDataFetchers.getBookByIdDataFetcher()))
            .type(newTypeWiring("Book")
                    .dataFetcher("author", graphQLDataFetchers.getAuthorDataFetcher())
                    // This line is new: we need to register the additional DataFetcher
                    .dataFetcher("pageCount", graphQLDataFetchers.getPageCountDataFetcher()))
            .build();
}

// In the GraphQLDataFetchers class
// Implement the DataFetcher
public DataFetcher getPageCountDataFetcher() {
    return dataFetchingEnvironment -> {
        Map<String,String> book = dataFetchingEnvironment.getSource();
        return book.get("totalPages");
    };
}
复制代码

试用API

这就是构建GraphQL API所需要的全部内容。在运行SpringBoot项目后,使用http://localhost:8080/graphql地址下能看到API了。

尝试和查看GraphQL API最简单的方式是使用GraphQL Playground(www.graphql-java.com/tutorials/g…

启动之后你会要求输入一个URL,输入:http://localhost:8080/graphql

之后,你就可以查询我们之前写的样例API。

graphql-java-kickstart

上面所讲的,引用了2个jar包,graphql-java是GraphQL的java实现形式。graphql-java-spring-boot-starter-webmvc是为了支持SpringWeb方式请求。

而graphql-java-kickstart是在这之上,不仅包含graphql-java,还有graphql-java-tools、graphql-java-servlet。支持了图形界面,我们只需要引入这一个依赖就可以了。

        <dependency>
            <groupId>com.graphql-java-kickstart</groupId>
            <artifactId>graphql-spring-boot-starter</artifactId>
            <version>12.0.0</version>
        </dependency>
复制代码

开启GraphIQL和PlayGround

这两个都是graphql语法执行图形界面。

开启方式如下:

graphql:
    graphiql:
        enabled: true
    playground:
        enabled: true
复制代码

创建schema

和之前所讲一样,需要创建schema文件,后缀必须是.graphqls

type Query {
  bookById(id: String): Book
}

type Book {
  id: String
  name: String
  pageCount: Int
  author: Author
}

type Author {
  id: String
  firstName: String
  lastName: String
}

复制代码

创建对应的Object

Schema中定义的每一个数据都需要有一个对应的Java对象:

@Data
class Book{
    private String id;
    private String name;
    private Integer pageCount;
    private Author author;
}

@Data
class Author{
    private String id;
    private String firstName;
    private String lastName;
}
复制代码

创建Resolver

和之前不一样的是,这个不需要我们写详细的解析步骤,只需要写一个Resolver。

@Component
public class BookResolver implements GraphQLQueryResolver {

    public Book bookById(String id){
        Book book = new Book();
        book.setId("book1");
        book.setName("test");
        return book;
    }
}
复制代码

@Component注解是为了被Spring发现。GraphQLQueryResolver接口是为了标记为Schema的解析类。方法名字必须和schema中定义的查询名字一样。

启动SpringBoot

启动SpringBoot后,浏览器访问:http://localhost:8080/playground。就会进入图形界面 在这里插入图片描述

左边有2个按钮,DOCS和SCHEMA。DOCS会显示你定义的查询,以及可以获取到的详细信息。SCHEMA会显示你定义的SCHEMA。 在这里插入图片描述

在这里插入图片描述

PostMan发起请求

如下所示,请求地址为:localhost:8080/graphql 请求方式为POST 参数为{"query":"graphql语句"} 在这里插入图片描述

优势

  • 前端不需要多次请求,一次请求就可以获取所需数据
  • 前端自己决定需要获取的数据
  • API文件是及时更新的

官方页面

语法:graphql.cn/learn/

源码:github.com/graphql-jav…

Supongo que te gusta

Origin juejin.im/post/7066694688090095652
Recomendado
Clasificación