GraphQL 是由 Facebook 在 2012 提出,2015 開源,2018 移交給 GraphQL 基金會。他跟 SQL 不同,並不是隸屬於某一種資料庫的查詢語言,GraphQL 也不是圖形資料庫,他單純是一種簡化 web API 的開發的方法。一般來說在網頁開發時,如果需要使用後端資料庫的資料,就會撰寫 rest 或 web service 的介面,根據資料對應產生許多 rest API,當頁面內的資料來源越多,rest API 就越複雜。GraphQL 透過資料定義,能夠用統一的 rest 介面,提供查詢多樣化資料的功能,也能避免大量冗餘資料傳給網頁。
GraphQL 除了查詢功能,支援了讀取、寫入、資料變更訂閱的功能。
以下根據 Getting started with GraphQL Java and Spring Boot 初步了解 GraphQL
專案目標
根據這個 GraphQL 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 的 GraphQL Server,可透過網頁 API 用以下語法查詢
{
bookById(id: "book-1"){
id
name
pageCount
author {
firstName
lastName
}
}
}
得到的查詢結果為
{
"bookById":
{
"id":"book-1",
"name":"Harry Potter and the Philosopher's Stone",
"pageCount":223,
"author": {
"firstName":"Joanne",
"lastName":"Rowling"
}
}
}
Project
首先產生一個 Java Maven Project,修改 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.graphqljava.tutorial</groupId>
<artifactId>bookdetails</artifactId>
<version>1.0.0</version>
<dependencies>
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java</artifactId>
<version>11.0</version>
</dependency>
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java-spring-boot-starter-webmvc</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>26.0-jre</version>
</dependency>
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java-spring-boot-starter-webmvc</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.1.2.RELEASE</version>
</dependency>
<!-- testing facilities -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.1.2.RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
schema.graphqls
將 GraphQL 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
}
GraphQLProvider.java
利用 schema.graphqls,製作 com.graphqljava.tutorial.bookdetails.GraphQLProvider.java
package com.graphqljava.tutorial.bookdetails;
import com.google.common.base.Charsets;
import com.google.common.io.Resources;
import graphql.GraphQL;
import graphql.schema.GraphQLSchema;
import graphql.schema.idl.RuntimeWiring;
import graphql.schema.idl.SchemaGenerator;
import graphql.schema.idl.SchemaParser;
import graphql.schema.idl.TypeDefinitionRegistry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.io.IOException;
import java.net.URL;
import static graphql.schema.idl.TypeRuntimeWiring.newTypeWiring;
@Component
public class GraphQLProvider {
@Autowired
GraphQLDataFetchers graphQLDataFetchers;
private GraphQL graphQL;
@PostConstruct
public void init() throws IOException {
// 以 Resources 讀取 schema.graphqls 產生 GraphQLSchema, GraphQL
URL url = Resources.getResource("schema.graphqls");
String sdl = Resources.toString(url, Charsets.UTF_8);
GraphQLSchema graphQLSchema = buildSchema(sdl);
this.graphQL = GraphQL.newGraphQL(graphQLSchema).build();
}
// 實作 buildSchema 產生 GraphQLSchema
private GraphQLSchema buildSchema(String sdl) {
TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(sdl);
RuntimeWiring runtimeWiring = buildWiring();
SchemaGenerator schemaGenerator = new SchemaGenerator();
return schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiring);
}
// 透過 graphQLDataFetchers,註冊兩個 dataFetcher
private RuntimeWiring buildWiring() {
return RuntimeWiring.newRuntimeWiring()
.type(newTypeWiring("Query")
.dataFetcher("bookById", graphQLDataFetchers.getBookByIdDataFetcher()))
.type(newTypeWiring("Book")
.dataFetcher("author", graphQLDataFetchers.getAuthorDataFetcher()))
.build();
}
@Bean
public GraphQL graphQL() {
return graphQL;
}
}
以下是 GraphQL 與 GraphQLSchema 的關係圖
GraphQLDataFetchers.java
com.graphqljava.tutorial.bookdetails.GraphQLDataFetchers.java
package com.graphqljava.tutorial.bookdetails;
import com.google.common.collect.ImmutableMap;
import graphql.schema.DataFetcher;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@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);
};
}
}
BookDetailsApplication.java
製作主程式 com.graphqljava.tutorial.bookdetails.BookDetailsApplication.java
package com.graphqljava.tutorial.bookdetails;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class BookDetailsApplication {
public static void main(String[] args) {
SpringApplication.run(BookDetailsApplication.class, args);
}
}
Postman
在 Postman 的 APIs 可填寫 graphql schema 定義,在進行 API 查詢時,Postman 就能根據 schema 提供語法 hint
在 Request 中,將 Data Type 改為 GraphQL,就能進行 GraphQL 查詢並得到結果
{
bookById(id: "book-1"){
id
name
pageCount
author {
firstName
lastName
}
}
}
References
Build and Test a CRUD API using GraphQL, Spring Boot and MongoDB
GraphQL, MongoDB and Java: An introduction
Spring Boot + GraphQL + MongoDB example with Spring Data & graphql-java
沒有留言:
張貼留言