1. 簡介
GraphQL 是一種由 Facebook 開發的相對較新的概念。GraphQL 是一種查詢語言,用於從服務器檢索數據,作為 REST、SOAP 或 gRPC 的替代方案。
在本教程中,我們將學習如何使用 Spring Boot 設置 GraphQL 服務器,以便將其添加到現有應用程序或在新應用程序中使用。
2. 什麼是 GraphQL?
傳統的 REST API 構建圍繞管理服務器管理的資源,並通過標準 HTTP 謂詞進行操作。雖然在這一框架內有效,但當偏離該框架或客户端需要同時從多個資源獲取數據時,它們會遇到困難,通常會導致更大的響應大小,因為存在不必要的冗餘數據。
GraphQL 通過賦予客户端以單個查詢請求所需精確的數據,從而解決了這些挑戰,從而實現對子資源的導航,並允許同時執行多個查詢。這種方法類似於 RPC(遠程過程調用),強調命名查詢和 mutation,從而使 API 開發者和消費者能夠有效地控制功能和期望結果。
例如,一個博客可以執行以下查詢:
query {
recentPosts(count: 10, offset: 0) {
id
title
category
author {
id
name
thumbnail
}
}
}此查詢將:
- 請求十篇最新文章
- 對於每篇文章,請求 ID、標題和類別
- 對於每篇文章,請求作者,返回 ID、姓名和縮略圖
在傳統的 REST API 中,這需要 11 次請求,即一次用於文章和 10 次用於作者,或者需要在文章詳情中包含作者信息。
2.1 GraphQL 模式
GraphQL 服務器暴露一個模式,描述了 API。 該模式由類型定義組成。 每個類型具有一個或多個字段,每個字段接受零個或多個參數並返回一個特定類型。
圖表由這些字段相互嵌套的方式衍生而來。 請注意,圖表不必是無環圖,環路是可以接受的,但它是有向的。 客户端可以從一個字段獲取其子字段,但不能自動獲取到父字段,除非模式明確定義了這一點。
一個博客的示例 GraphQL 模式可能包含以下定義,描述一個 Post、該 Post 的作者以及獲取博客上最新 Post 的根查詢:
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!
}名稱末尾的“!”表示它是一個非空類型。任何不具備此特徵的類型都可以是響應中從服務器返回的值。GraphQL服務正確地處理了這些情況,允許我們安全地請求可空類型的子字段。
GraphQL服務還通過一組標準字段暴露了schema,允許任何客户端在查詢時提前獲取schema定義。
這使得客户端能夠自動檢測schema的變化,並允許客户端動態適應schema的工作方式。一個非常實用的例子是GraphiQL工具,它允許我們與任何GraphQL API進行交互。
3. 介紹 GraphQL Spring Boot Starter
Spring Boot GraphQL Starter 提供了一種極佳的方式,可以在極短的時間內啓動一個 GraphQL 服務器。 通過自動配置和基於註解的編程方法,我們只需要編寫服務所需的代碼
3.1. 服務設置
我們需要確保正確配置依賴項,才能使服務正常運行。
<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>由於 GraphQL 是跨運輸協議的,因此我們在配置中包含了 Web 啓動器。這通過使用 Spring MVC 在默認的 /graphql 端點上,將 GraphQL API 暴露在 HTTP 上。其他啓動器可用於其他底層實現,例如 Spring Webflux。
我們也可以在 application.properties 文件中根據需要自定義此端點。
<h2><b>4. 定義 Schema</b></h2>
<p>GraphQL Boot 啓動器的工作原理是處理 GraphQL Schema 文件以構建正確的結構,然後將特殊 Bean 連接到該結構。 <strong>Spring Boot GraphQL 啓動器會自動查找這些 schema 文件</strong>。</p>
<p>我們需要將這些 “<em>.graphqls</em>” 或 “<em>.gqls</em>” schema 文件保存到 <em>src/main/resources/graphql/** 目錄下,Spring Boot 將會自動將其拾取。 就像往常一樣,我們可以使用 <em>spring.graphql.schema.locations</em> 和 <em>spring.graphql.schema.file-extensions</em> 配置屬性自定義位置。</p>
<p>唯一的要求是必須有一個根查詢和一個或多個根 Mutation。 與 Schema 的其餘部分不同,我們不能將其拆分到多個文件中。 這是一個 GraphQL Schema 定義的限制,而不是 Java 實現的限制。</p>
5. 實現我們的 Schema
一旦我們編寫了 Schema,就需要將其在代碼中實現。這包括三種不同的代碼類型:
- 根查詢解析器 – 用於解決 Schema 中頂層查詢的值。
- 字段解析器 – 用於解決響應中嵌套的值。
- Mutation – 用於實現 Schema 中的 Mutation。
所有這些都通過在應用程序中編寫 Bean 並進行適當的標註來實現。
5.1. 根查詢解析器
根查詢需要具有專門標註的方法,以處理該查詢中的各種字段。與模式定義不同,根查詢字段並不限制只能有一個 Spring Bean。
我們需要使用 @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);
}
}上述定義了名為 recentPosts 的方法,我們將使用它來處理 GraphQL 模式中 recentPosts 字段的任何查詢。該方法必須具有帶有 @Argument 註解的參數,這些參數與模式中的相應參數相對應。
它還可以可選地接受其他與 GraphQL 相關的參數,例如 GraphQLContext、DataFetchingEnvironment 等,用於訪問底層上下文和環境。
該方法必須返回 GraphQL 模式中類型的正確返回類型,正如我們即將看到的。我們可以使用任何簡單類型,例如 String, Int, List 等,以及它們對應的 Java 類型,系統會自動進行映射。
5.2. 使用 Bean 表示類型
GraphQL 服務器中的每一個複雜類型都由一個 Java Bean 表示,無論該 Bean 是從根查詢加載的,還是從服務器結構中的任何其他位置加載的。相同的 Java 類必須始終代表相同的 GraphQL 類型,但類名並不重要。
Java Bean 內部的字段將直接映射到 GraphQL 響應中的字段,映射關係基於字段的名稱。
public class Post {
private String id;
private String title;
private String category;
private String authorId;
}Java Bean 中任何未映射到 GraphQL 模式的字段或方法都會被忽略,但不會導致問題。這對於字段解析器的工作至關重要。
例如,這裏,字段 authorId 未對應於我們先前定義的任何模式,但它將可用作下一步使用。
5.3. 複雜值解析器
有時,字段的值可能需要進行非簡單的加載操作。 這可能包括數據庫查找、複雜的計算或其他任何操作。 <em>@SchemaMapping</em> 註解將處理方法映射到具有相同名稱的模式中的字段,並將其用作該字段的數據獲取器。
@SchemaMapping
public Author author(Post post) {
return authorDao.getAuthor(post.getAuthorId());
}重要的是,如果客户端沒有請求某個字段,GraphQL 服務器將不會執行檢索該字段的工作。這意味着,如果客户端檢索到一個 Post 並且沒有請求 author 字段,那麼上述的 author() 方法將不會被執行,也不會進行 DAO 調用。
或者,我們也可以在註解中指定父類型名稱和字段名稱:
@SchemaMapping(typeName="Post", field="author")
public Author getAuthor(Post post) {
return authorDao.getAuthor(post.getAuthorId());
}此處,標註屬性用於聲明這作為模式中 author 字段的處理程序。
5.4. 可為空值
GraphQL 模式中存在一些類型可為空,而另一些類型則不能。
我們通過直接使用 null 值來處理這個問題,在 Java 代碼中。相反,我們也可以直接使用 Java 8 中新引入的 Optional 類型來處理可空類型,系統會自動處理這些值。
這非常有用,因為它意味着我們的 Java 代碼與 GraphQL 模式的定義方法更加一致。
5.5. 變異(Mutations)
到目前為止,我們所做的一切都集中在從服務器檢索數據上。 GraphQL 同樣也具備通過變異(Mutations)更新服務器上存儲的數據的能力。
從代碼的角度來看,Query 能夠改變服務器上的數據並沒有什麼問題。我們可以輕鬆編寫 Query 解決器,它們可以接受參數、保存新數據並返回這些更改。 這樣做會導致 API 客户端產生意想不到的副作用,因此被認為是不好的做法。
相反,變異(Mutations)應該被用來告知客户端這將會導致數據存儲發生改變。
類似於 Query,變異(Mutations)通過在處理方法上使用 <em @MutationMapping</em>> 註解來在控制器中定義。 變異(Mutation)字段的返回值與 Query 字段的返回值完全相同,從而允許檢索嵌套值:
@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;
}6. GraphiQL
GraphQL 也有一個配套工具,稱為 GraphiQL。這個 UI 工具可以與任何 GraphQL 服務器進行通信,並幫助您消費和開發針對 GraphQL API。它作為一個 Electron 應用存在,可以從 這裏下載。
Spring GraphQL 內置了 GraphiQL。默認情況下它已關閉,但我們可以通過在 application.yml 中添加以下內容來啓用它:
spring:
graphql:
graphiql:
enabled: true一旦完成此操作,我們就可以導航到應用程序中的 /graphiql 端點,並獲得完整的 GraphiQL 用户界面,用於與我們的 API 交互。這提供了一個非常實用的基於瀏覽器的工具,用於編寫和測試查詢,尤其是在開發和測試期間:
7. 概述
GraphQL 是一種令人興奮的新技術,它有可能徹底改變我們開發 Web API 的方式。
Spring Boot GraphQL Starter 使其易於添加到任何新的或現有的 Spring Boot 應用程序中。