开始使用GraphQL Java和Spring Boot

这是一篇为想要用Java搭建GraphQL服务器的小伙伴们准备的教程。须要你有必定的Spring Boot和Java开发相关知识,虽然咱们简要介绍了GraphQL,可是本教程的重点是用Java开发一个GraphQL服务器。java

三分钟介绍GraphQL

GraphQL是一门从服务器检索数据的查询语言。在某些场景下能够替换REST、SOAP和gRPC。让咱们假设咱们想要从一个在线商城的后端获取某一个本书的详情。git

你使用GraphQL往服务器发送以下查询去获取id为"123"的那本书的详情:github

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

这不是一段JSON(尽管它看起来很是像),而是一条GraphQL查询。它基本上表示:web

  • 查询某个特定id的书
  • 给我那本书的id、name、pageCount、author
  • 对于author我想知道firstName和lastName

响应是一段普通JSON:spring

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

静态类型是GraphQL最重要的特性之一:服务器明确地知道你想要查询的每一个对象都是什么样子的而且任何client均可以"内省"于服务器并请求"schema"。schema描述的是查询多是哪些状况而且你能够拿到哪些字段。(注意:当说起schema时,咱们常常指的是"GraphQL Schema",而不是像"JSON Schema"或者"Database Schema")数据库

上面说起的查询的schema是这样描述的:json

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

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

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

这篇教程将关注于如何用Java实现一个有着这种schema的GraphQL服务器。后端

咱们仅仅触及了GraphQL的一些基本功能。更多内容能够去官网查看 graphql.github.io/learn/服务器

GraphQL Java 预览

GraphQL Java是GraphQL的Java(服务器)实现。GraphQL Java Github org中有几个Git仓库。其中最重要的一个是GraphQL Java 引擎,它是其余全部东西的基础。mvc

GraphQL Java引擎自己只关心执行查询。它不处理任何HTTP或JSON相关主题。所以,咱们将使用GraphQL Java Spring Boot adapter,它经过Spring Boot在HTTP上暴露API。

建立GraphQL Java服务器的主要步骤以下:

  1. 定义GraphQL Schema。
  2. 决定如何获取须要查询的实际数据。

咱们的示例API:获取图书详细信息

咱们的示例应用程序将是一个简单的API,用于获取特定书籍的详细信息。这个API并非很全面,但对于本教程来讲已经足够了。

建立一个Spring Boot应用程序

建立Spring应用程序的最简单方法是使用start.spring.io/上的“Spring Initializr”。

选择:

  • Gradle Project
  • Java
  • Spring Boot 2.1.x

对于咱们使用的项目元数据:

  • Group: com.graphql-java.tutorial
  • Artifact: book-details

至于dependency(依赖项),咱们只选择Web。

点击Generate Project,你就可使用Spring Boot app了。全部后面提到的文件和路径都是与这个Generate Project相关的。

咱们在build.gradledependencies部分为咱们的项目添加了三个依赖项:

前两个是GraphQL Java和GraphQL Java Spring,而后咱们还添加了Google Guava。Guava并非必须的,但它会让咱们的生活更容易一点。

依赖项看起来是这样的:

dependencies {
    implementation 'com.graphql-java:graphql-java:11.0' // NEW
    implementation 'com.graphql-java:graphql-java-spring-boot-starter-webmvc:1.0' // NEW
    implementation 'com.google.guava:guava:26.0-jre' // NEW
    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
复制代码

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,它包含了:idnamepageCountauthorauthor属于Author类型,在Book以后定义。

上面显示的用于描述模式的特定于域的语言称为模式定义语言或SDL。更多细节能够在这里找到。

一旦咱们有了这个文件,咱们就须要经过读取文件并解析它,而后添加代码来为它获取数据,从而“让它活起来”。

咱们在com.graphqljava.tutorial.bookdetails包中建立了一个新的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: we will create the schema here later 
    }
}
复制代码

咱们使用Guava资源从类路径读取文件,而后创GraphQLSchemaGraphQL实例。这个GraphQL实例经过使用@Bean注解的GraphQL()方法做为Spring Bean暴露出去。GraphQL Java Spring适配器将使用该GraphQL实例,使咱们的schema能够经过默认url/GraphQL进行HTTP访问。

咱们还须要作的是实现buildSchema方法,它建立GraphQLSchema实例,并链接代码来获取数据:

@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将TypeDefinitionRegistryRuntimeWiring结合起来,实际生成GraphQLSchema

buildRuntimeWiring使用graphQLDataFetchersbean来注册两个Datafetchers:

  • 一个是检索具备特定ID的图书。
  • 一个是为特定的书找到做者。

下一节将解释DataFetcher以及如何实现GraphQLDataFetchersbean。

总的来讲,建立GraphQLGraphQLSchema实例的过程是这样的:

explain

DataFetchers

GraphQL Java服务器最重要的概念多是Datafetcher:在执行查询时,Datafetcher获取一个字段的数据。

当GraphQL Java执行查询时,它为查询中遇到的每一个字段调用适当的DatafetcherDataFetcher是一个只有一个方法的接口,带有一个类型的参数DataFetcherEnvironment:

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

重要提示:模式中的每一个字段都有一个与之关联的DataFetcher。若是没有为特定字段指定任何DataFetcher,则使用默认的PropertyDataFetcher。咱们稍后将更详细地讨论这个问题。

咱们正在建立一个新的类GraphQLDataFetchers,其中包含图书和做者的示例列表。

完整的实现是这样的,咱们将很快详细研究它:

@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);
        };
    }
}
复制代码

数据源

咱们从类中的静态列表中获取图书和做者。这只是为了本教程而作的。理解GraphQL并不指定数据来自何处是很是重要的。这就是GraphQL的强大之处:它能够来自内存中的静态列表、数据库或外部服务。

Book DataFetcher

咱们的第一个方法getBookByIdDataFetcher返回一个DataFetcher实现,该实现接受一个DataFetcherEnvironment并返回一本书。在本例中,这意味着咱们须要从bookById字段获取id参数,并找到具备此特定id的图书。

String bookId = dataFetchingEnvironment.getArgument("id");中的"id"为schema中bookById查询字段中的“id”:

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

Author DataFetcher

第二个方法getAuthorDataFetcher返回一个Datafetcher,用于获取特定书籍的做者。与前面描述的book DataFetcher相比,咱们没有参数,可是有一个book实例。来自父字段的DataFetcher的结果能够经过getSource得到。这是一个须要理解的重要概念:GraphQL中每一个字段的Datafetcher都是以自顶向下的方式调用的,父字段的结果是子Datafetcherenvironmentsource属性。

而后,咱们使用先前获取的图书获取authorId,并以查找特定图书的相同方式查找特定的做者。

Default DataFetchers

咱们只实现了两个Datafetcher。如上所述,若是不指定一个,则使用默认的PropertyDataFetcher。在咱们的例子中,它指的是Book.idBook.nameBook.pageCountAuthor.idAuthor.firstNameAuthor.lastName都有一个默认的PropertyDataFetcher与之关联。

PropertyDataFetcher尝试以多种方式查找Java对象上的属性。以java.util.Map为例, 它只是按键查找属性。这对咱们来讲很是好,由于book和author映射的键与schema中指定的字段相同。例如,在咱们为图书类型定义的schema中,字段pageCount和book DataFetcher返回一个带有键pageCountMap。由于字段名与Map中的键相同(“pageCount”),PropertyDateFetcher正常工做。

让咱们假设咱们有一个不匹配,book Map有一个键是totalPages而不是pageCount。这将致使每本书的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");
        };
    }
...
复制代码

这个DataFetcher将经过在book Map中查找正确的键来解决这个问题。(一样:在咱们的示例中不须要这个,由于咱们没有命名不匹配)

试用API

这就是构建一个可工做的GraphQL API所需的所有内容。在启动Spring Boot应用程序以后,能够在http://localhost:8080/graphql上使用API。

尝试和探索GraphQL API的最简单方法是使用GraphQL Playground的工具。下载并运行它。

启动以后,你将被要求输入一个URL,输入http://localhost:8080/graphql

以后,你能够查询咱们的示例API,您应该会获得咱们在开始时提到的结果。它应该是这样的:

demo

完整的示例源代码和更多信息

完整的项目和完整的源代码能够在这里找到:github.com/graphql-jav…

有关GraphQL Java的更多信息能够在文档中找到。

对于任何问题, 咱们也有spectrum chat 接受讨论。

对于直接的反馈,您也能够在咱们的GraphQL Java Twitter account账户上找到咱们。

原文连接:Getting started with GraphQL Java and Spring Boot

译文链接:开始使用GraphQL Java和Spring Boot

翻译:TomorJM

相关文章
相关标签/搜索