這是一篇爲想要用Java搭建GraphQL服務器的小夥伴們準備的教程。須要你有必定的Spring Boot和Java開發相關知識,雖然咱們簡要介紹了GraphQL,可是本教程的重點是用Java開發一個GraphQL服務器。java
GraphQL是一門從服務器檢索數據的查詢語言。在某些場景下能夠替換REST、SOAP和gRPC。讓咱們假設咱們想要從一個在線商城的後端獲取某一個本書的詳情。git
你使用GraphQL往服務器發送以下查詢去獲取id爲"123"的那本書的詳情:github
{
bookById(id: "book-1"){
id
name
pageCount
author {
firstName
lastName
}
}
}
複製代碼
這不是一段JSON(儘管它看起來很是像),而是一條GraphQL查詢。它基本上表示:web
響應是一段普通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 Github org中有幾個Git倉庫。其中最重要的一個是GraphQL Java 引擎,它是其餘全部東西的基礎。mvc
GraphQL Java引擎自己只關心執行查詢。它不處理任何HTTP或JSON相關主題。所以,咱們將使用GraphQL Java Spring Boot adapter,它經過Spring Boot在HTTP上暴露API。
建立GraphQL Java服務器的主要步驟以下:
咱們的示例應用程序將是一個簡單的API,用於獲取特定書籍的詳細信息。這個API並非很全面,但對於本教程來講已經足夠了。
建立Spring應用程序的最簡單方法是使用start.spring.io/上的「Spring Initializr」。
選擇:
對於咱們使用的項目元數據:
com.graphql-java.tutorial
book-details
至於dependency(依賴項),咱們只選擇Web。
點擊Generate Project
,你就可使用Spring Boot app了。全部後面提到的文件和路徑都是與這個Generate Project相關的。
咱們在build.gradle
的dependencies
部分爲咱們的項目添加了三個依賴項:
前兩個是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'
}
複製代碼
咱們正在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
以後定義。
上面顯示的用於描述模式的特定於域的語言稱爲模式定義語言或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
資源從類路徑讀取文件,而後創GraphQLSchema
和GraphQL
實例。這個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將TypeDefinitionRegistry
與RuntimeWiring
結合起來,實際生成GraphQLSchema
。
buildRuntimeWiring
使用graphQLDataFetchers
bean來註冊兩個Datafetcher
s:
下一節將解釋DataFetcher
以及如何實現GraphQLDataFetchers
bean。
總的來講,建立GraphQL
和GraphQLSchema
實例的過程是這樣的:
GraphQL Java服務器最重要的概念多是Datafetcher
:在執行查詢時,Datafetcher
獲取一個字段的數據。
當GraphQL Java執行查詢時,它爲查詢中遇到的每一個字段調用適當的Datafetcher
。DataFetcher
是一個只有一個方法的接口,帶有一個類型的參數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的強大之處:它能夠來自內存中的靜態列表、數據庫或外部服務。
咱們的第一個方法getBookByIdDataFetcher
返回一個DataFetcher
實現,該實現接受一個DataFetcherEnvironment
並返回一本書。在本例中,這意味着咱們須要從bookById
字段獲取id參數,並找到具備此特定id的圖書。
String bookId = dataFetchingEnvironment.getArgument("id");
中的"id"爲schema中bookById
查詢字段中的「id」:
type Query {
bookById(id: ID): Book
}
...
複製代碼
第二個方法getAuthorDataFetcher
返回一個Datafetcher
,用於獲取特定書籍的做者。與前面描述的book DataFetcher
相比,咱們沒有參數,可是有一個book實例。來自父字段的DataFetcher
的結果能夠經過getSource
得到。這是一個須要理解的重要概念:GraphQL中每一個字段的Datafetcher
都是以自頂向下的方式調用的,父字段的結果是子Datafetcherenvironment
的source
屬性。
而後,咱們使用先前獲取的圖書獲取authorId,並以查找特定圖書的相同方式查找特定的做者。
咱們只實現了兩個Datafetcher
。如上所述,若是不指定一個,則使用默認的PropertyDataFetcher
。在咱們的例子中,它指的是Book.id
、Book.name
、Book.pageCount
、Author.id
、Author.firstName
和Author.lastName
都有一個默認的PropertyDataFetcher
與之關聯。
PropertyDataFetcher
嘗試以多種方式查找Java對象上的屬性。以java.util.Map
爲例, 它只是按鍵查找屬性。這對咱們來講很是好,由於book和author映射的鍵與schema中指定的字段相同。例如,在咱們爲圖書類型定義的schema中,字段pageCount
和book DataFetcher
返回一個帶有鍵pageCount
的Map
。由於字段名與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
中查找正確的鍵來解決這個問題。(一樣:在咱們的示例中不須要這個,由於咱們沒有命名不匹配)
這就是構建一個可工做的GraphQL API所需的所有內容。在啓動Spring Boot應用程序以後,能夠在http://localhost:8080/graphql
上使用API。
嘗試和探索GraphQL API的最簡單方法是使用GraphQL Playground的工具。下載並運行它。
啓動以後,你將被要求輸入一個URL,輸入http://localhost:8080/graphql
。
以後,你能夠查詢咱們的示例API,您應該會獲得咱們在開始時提到的結果。它應該是這樣的:
完整的項目和完整的源代碼能夠在這裏找到:github.com/graphql-jav…
有關GraphQL Java的更多信息能夠在文檔中找到。
對於任何問題, 咱們也有spectrum chat 接受討論。
對於直接的反饋,您也能夠在咱們的GraphQL Java Twitter account賬戶上找到咱們。
翻譯:TomorJM