Java 后端的未來(lái)? GraphQL?
“?GraphQL 既是一種用于 API 的查詢(xún)語(yǔ)言也是一個(gè)滿(mǎn)足你數(shù)據(jù)查詢(xún)的運(yùn)行時(shí)。”

GraphQL 既是一種用于 API 的查詢(xún)語(yǔ)言也是一個(gè)滿(mǎn)足你數(shù)據(jù)查詢(xún)的運(yùn)行時(shí)。GraphQL 對(duì)你的 API 中的數(shù)據(jù)提供了一套易于理解的完整描述,使得客戶(hù)端能夠準(zhǔn)確地獲得它需要的數(shù)據(jù),而且沒(méi)有任何冗余,也讓 API 更容易地隨著時(shí)間推移而演進(jìn),還能用于構(gòu)建強(qiáng)大的開(kāi)發(fā)者工具。
GraphQL 是一個(gè)用于 API 的查詢(xún)語(yǔ)言,是一個(gè)使用基于類(lèi)型系統(tǒng)來(lái)執(zhí)行查詢(xún)的服務(wù)端運(yùn)行時(shí)(類(lèi)型系統(tǒng)由你的數(shù)據(jù)定義)。GraphQL 并沒(méi)有和任何特定數(shù)據(jù)庫(kù)或者存儲(chǔ)引擎綁定,而是依靠你現(xiàn)有的代碼和數(shù)據(jù)支撐.
以上是 GraphQL 的一些介紹和描述. 看了你可能會(huì)一臉懵逼, 沒(méi)事, 我也是(哈哈)....... 因此, 我們先看使用后理解, 開(kāi)始我們永久不衰的 Hello World.
1. 項(xiàng)目構(gòu)建
1.1 創(chuàng)建項(xiàng)目
這里以** SpringBoot 2.1.2.RELEASE** 為例, 構(gòu)建工具選用 Gradle, 快速創(chuàng)建一個(gè) Web 項(xiàng)目. 需要的依賴(lài)有
dependencies {
implementation 'com.graphql-java:graphql-java:11.0'
implementation 'com.graphql-java:graphql-java-spring-boot-starter-webmvc:1.0'
implementation 'com.google.guava:guava:26.0-jre'
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}功能實(shí)現(xiàn)一個(gè)書(shū)本及作者信息查詢(xún). 所有數(shù)據(jù)用集合模擬如下:
private static List1.2 創(chuàng)建 Schema
在 resources 下新建
schema.graphqls文件用于描述需要的 API.
type Query {
bookById(id: ID): Book
}
type Book {
id: ID
name: String
pageCount: Int
author: Author
}
type Author {
id: ID
firstName: String
lastName: String
}該語(yǔ)句定義了兩個(gè)實(shí)體: Book 和 Author, 并且定義了一個(gè)查詢(xún): bookById, 通過(guò)書(shū)本 id 查詢(xún) Book 信息, Book 中又包含 Author 信息, 因此需要級(jí)聯(lián)查詢(xún) Author.
1.3 創(chuàng)建 GraphQL 實(shí)例
創(chuàng)建一個(gè)
GraphQLProvider.java用于初始化和暴露 GraphQL 實(shí)例.
@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: 在這里構(gòu)造 Schema
}
}基于
schema.graphqls構(gòu)造GraphQL對(duì)象, 并暴露在 IOC 容器, GraphQL 適配器將會(huì)在此 GraphQL 對(duì)象之上構(gòu)建 Schema 中的 API, 且默認(rèn)的請(qǐng)求路徑為/graphql.
1.4 構(gòu)建 GraphQLSchema
上面我們構(gòu)建 GraphQL 實(shí)例還需要實(shí)現(xiàn)
buildSchema方法, 此方法將會(huì)創(chuàng)建 GraphQLSchema 對(duì)象并獲取數(shù)據(jù).
@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結(jié)合TypeDefinitionRegistry和RuntimeWiring來(lái)實(shí)際構(gòu)造GraphQLSchema. buildWiring 使用 graphQLDataFetchers 這個(gè) Bean 來(lái)實(shí)際注冊(cè)了兩個(gè)DataFetcher.
一個(gè)通過(guò)書(shū)本 ID 查詢(xún)書(shū)本的
DataFetcher一個(gè)獲取書(shū)本中作者信息的
DataFetcher
DataFetcher和如何創(chuàng)建 graphQLDataFetchers 稍后說(shuō)明. 總的來(lái)說(shuō), 創(chuàng)建GraphQL及GraphQLSchema的流程如下:

1.5 DataFetcher
DataFetcher是 GraphQL 最重要的概念之一, 用于查詢(xún)時(shí)獲取一個(gè)字段的數(shù)據(jù). 當(dāng) GraphQL 執(zhí)行查詢(xún)時(shí), 他會(huì)為每一個(gè)字段執(zhí)行合適的DataFetcher.DataFetcher是一個(gè)只有一個(gè)方法的接口:
public interface DataFetcher {
T get(DataFetchingEnvironment dataFetchingEnvironment) throws Exception;
} Schema 中的每個(gè)字段都需要一個(gè)
DataFetcher, 如果字段沒(méi)有指定DataFetcher, 那么就會(huì)使用的PropertyDataFetcher作為默認(rèn)的DataFetcher.
創(chuàng)建 1.4 節(jié)中的 graphQLDataFetchers 實(shí)現(xiàn)
GraphQLDataFetchers.java:
@Component
public class GraphQLDataFetchers {
private static List> 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> 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 book = dataFetchingEnvironment.getSource();
String authorId = book.get("authorId");
return authors
.stream()
.filter(author -> author.get("id").equals(authorId))
.findFirst()
.orElse(null);
};
}
} 這里我們創(chuàng)建了兩個(gè)
DataFetcher: 通過(guò) ID 獲取書(shū)本信息的getBookByIdDataFetcher以及獲取書(shū)本中作者信息的getAuthorDataFetcher.
至此 GraphQL 工程就搭建起來(lái)了, 基本結(jié)構(gòu)如下:

2. API 查詢(xún)
啟動(dòng) SpringBoot 應(yīng)用
BookDetailsApplication后, 我們就可以通過(guò) http://localhost:8080/graphql 進(jìn)行 API 訪問(wèn). 你可以下載GraphQL查詢(xún)工具查詢(xún)(工具下載: https://github.com/prisma/graphql-playground), 如下:

也可以通過(guò) PostMan 查詢(xún), GraphQL 可以支持 GET 和 POST 兩種請(qǐng)求查詢(xún).
2.1 Postman 通過(guò) POST GraphQL 請(qǐng)求體查詢(xún)
Postman 中請(qǐng)求體已經(jīng)支持了 GraphQL, 查詢(xún)語(yǔ)句如下:
{
bookById(id: "book-1"){
id
name
pageCount
author {
firstName
lastName
}
}
}
2.2 Postman 通過(guò)普通 POST 請(qǐng)求查詢(xún)
正常的 POST 請(qǐng)求也可以查詢(xún) GraphQL, 需要請(qǐng)求體攜帶一個(gè)
query參數(shù), 該參數(shù)即是上面的查詢(xún)語(yǔ)句, 但是由于請(qǐng)求必須是application/json, 而 JSON 對(duì)多行字符串不支持, 因此需要做好 JSON 處理

由此也可以看出, GraphQL 請(qǐng)求體實(shí)際為我們做的就是請(qǐng)求參數(shù)的格式化.
2.3 Postman 通過(guò) GET 請(qǐng)求查詢(xún)
GraphQL 也支持 GET 方式查詢(xún), 主要是因?yàn)?GraphQLServlet 實(shí)現(xiàn)了 doGet 和 doPost 兩個(gè)方法. Get 請(qǐng)求查詢(xún)時(shí)需要一個(gè) query 請(qǐng)求參數(shù), 且參數(shù)值需要 encode.

3. 應(yīng)用場(chǎng)景
至此, 我們應(yīng)該對(duì) GraphQL 有一個(gè)簡(jiǎn)單的認(rèn)識(shí)了, 那此時(shí)就有人會(huì)問(wèn)了:
這玩意到底有啥用?
靜態(tài)變動(dòng)態(tài), 前端可以自己從 API 獲取想要的數(shù)據(jù),不必依賴(lài) REST 端返回的固定數(shù)據(jù)結(jié)構(gòu)
請(qǐng)求次數(shù)及交互數(shù)據(jù)量的減少
API 網(wǎng)關(guān)層面

? ? ? ?
? ? ? ? 如果有任何相關(guān)的問(wèn)題都可以加入 QQ/微信群一起討論, 學(xué)習(xí), 進(jìn)步. 此外如果有任何對(duì)于本公眾號(hào)的意見(jiàn)和建議也歡迎大家留言積極批評(píng)指正, 最后, 愿你我都能成為更好的自己.?
? ? ? ? 我是帥帥, 一個(gè)集帥氣, 幽默與內(nèi)涵, 并且熱愛(ài)編程, 擁抱開(kāi)源, 喜歡烹飪與旅游的暖男, 我們下期再見(jiàn).?拜了個(gè)拜!
? ? ? ??老規(guī)矩別忘了哦,?點(diǎn)擊原文鏈接跳轉(zhuǎn)到我們官方的博客平臺(tái)哦.
悄悄話(huà)
————
每文一句
————
Before you talk, LISTEN
Before you react, WAIT
Before you quit, TRY?
日常求贊
————
? ? ? 你們白漂的力量就是我拖更的史詩(shī)級(jí)動(dòng)力, 點(diǎn)贊, 評(píng)論, 再看, 贊賞, 看都看到這了, 隨便點(diǎn)一個(gè)咯.
關(guān)注加好友
拉你進(jìn)大佬交流群
————————————————
