使用Java × Spring Boot × Apollo来实现GraphQL客户端
首先
目前,越来越多的API开始采用GraphQL和gRPC这样的新技术,从之前长期主导API设计的REST架构中转变而来。许多人可能已经知道GitHub API v4已经采用了GraphQL。
预计未来会增加采用像GraphQL这样的新设计的API,但对于能否轻松连接到传统的REST API应用程序的GraphQL数据源仍然是一个关注点。因此,这次我想在常见的Spring Boot应用程序中使用名为Apollo的GraphQL客户端来实现类似请求GraphQL API作为数据源的功能。(不涉及实现GraphQL API服务器本身)
为了使初学者更容易理解GraphQL,我们在解释术语时使用了链接或文章,并且也适用于入门级学习。
搭建服务器端
还有一些其他知名的项目,如之前提到的 GitHub API v4 和星球大战(SWAPI GraphQL)。虽然我也可以选择使用 GitHub API,但由于涉及到身份验证,可能会稍微偏离主题,所以我决定放弃这个选项。
GraphQL 寶可夢的部署
因为非常简单,所以我先前提一下服务器端的部署方法。
-
- ローカル環境で構築します
事前に Node.js & yarn の導入が必要です(こちらの導入手順は割愛します)
# GitHubからクローン
$ git clone git@github.com:lucasbento/graphql-pokemon.git
$ cd graphql-pokemon
# build & run
$ yarn
$ yarn run build
$ yarn start
GraphQL-Pokemon started on http://localhost:5000/
在GraphiQL中,可以轻松了解API规范。
-
- 作者が GraphiQL インターフェースを Web上 でも提供してくれています
https://graphql-pokemon.now.sh
現在は上記のURLはサーバーダウンしているようなので、Issue#15 で紹介されている下記URLをお試しください
https://graphql-pokemon2.vercel.app/
もしくは、localで立ち上げていればそちらでも見れます
http://localhost:5000/graphql/endpoint
推荐查看官方文档以获取有关详细的GraphQL查询信息。
查询和变更|GraphQL
客户端的建设
我們將在使用最廣泛的Java虛擬機環境(JVM)中,開發一個基於Spring Boot應用的Web應用程式,用以調用GraphQL服務器。
-
- Runtime
JVM
Language
Java 11
もちろん Kotlin や Scala などでも構いません
その場合、適宜コードは読み替えてください
Dependency Manager
Gradle
Framework
Spring Boot
JVM 上で動く Web アプリケーションを簡単に構築するために使用します
Apollo Android
後ほど詳細を説明します
项目创建
Spring Initializr を使って Spring Boot アプリケーションのひな形をつくっていきます
最下部の Java バージョンはお使いの環境のものを利用してください
Dependencies に REST API を作るための Spring Web を追加してください
阿波罗的引入
从现在开始,我们将打开这个项目并进行实现,为了调用GraphQL API,我们将引入必要的依赖包(库)。
-
- Gradle を用いて、GraphQL クライアントライブラリである apollo-android を導入します
apollo-android は文字通り Android 向けの GraphQL クライアントライブラリです
ただ、説明にもあるとおり JVM 上であれば利用することができるのでサーバーサイドでも用いることが可能です
? A strongly-typed, caching GraphQL client for Android and the JVM
build.gradle に Apollo Android 関連の依存ライブラリを追加します
% git diff
diff --git a/build.gradle b/build.gradle
index 1325946..269b7a7 100644
--- a/build.gradle
+++ b/build.gradle
@@ -2,6 +2,7 @@ plugins {
id 'org.springframework.boot' version '2.4.1'
id 'io.spring.dependency-management' version '1.0.10.RELEASE'
id 'java'
+ id 'com.apollographql.apollo' version '2.5.2'
}
group = 'com.example'
@@ -14,6 +15,9 @@ repositories {
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
+ implementation 'com.apollographql.apollo:apollo-runtime:2.5.2'
+ // 同期通信を簡単に書くために RxJava で書けるライブラリも import しています
+ implementation 'com.apollographql.apollo:apollo-rx3-support:2.5.2'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
为什么要使用 Apollo 呢?
使用Apollo的好处在于可以充分享受GraphQL的“先定义模式”的原则。
按照”strongly-typed”的定义,Apollo会使用服务器端的模式信息来自动生成API响应的各种语言的对象类。
※ Apollo除了Java版本的apollo-android之外,还支持Node.js版本(Web)和Swift版本(iOS)等主要客户端语言。
这个自动生成的方式与传统的 REST 有很大的不同之处。
以前,我们会根据服务器端的API规范文档,从头开始在客户端实现相应的定义。然而,我们必须仔细核实这个响应字段是否是必需的字段,其类型是什么,是否可以为空值等细节。而且,由于各个API的负责人不同,我们还需要分别阅读按照各自习惯编写的规范文档。
有时候可能没有API文档,并且客户端工程师可能需要阅读实际的服务器端代码(语言)。
在这种情况下,如果对语言规范不熟悉,很容易产生误导的风险。
GraphQL自身是具有类型的,且支持空值安全的。
所有可用的资源都在服务器端的模式信息中严格描述了其类型,因此使用按照该类型生成的对象,即保证了没有通过通信丢失的规范。
只要客户端工程师了解GraphQL,无论其背后是用Java编写还是用Kotlin编写,或者是用Ruby on Rails编写,他们都不需要熟悉该语言的规范。(换言之,如果使用GraphQL,则客户端/服务器工程师都需要学习GraphQL的成本。然而,对于掌握任何一种编程语言的人来说,学习GraphQL的成本并不会非常高。)
GraphQL API 的请求实现
好了,从这里开始就是正文了!
请参考Apollo官方文档进行实施,详细说明和补充内容请参阅此处。开始使用Java – Client (Android) – Apollo GraphQL文档。
创建 schama.json
将服务器端的模式信息定义文件添加到项目中。
如果没有这个文件,就无法享受自动生成的好处,因此这是必不可少的步骤。
apollo-android插件有一个方便的命令可以生成schema.json,我们将执行该命令来创建它。
刚才,我们指定了在本地的5000端口上启动的GraphQL 的端点,以创建schema.json。
# 先に出力先のフォルダを作っておく
$ mkdir -p src/main/graphql/graphqlpokemon
# schema.json作成
$ ./gradlew downloadApolloSchema \
--endpoint="http://localhost:5000/graphql/endpoint" \
--schema="src/main/graphql/graphqlpokemon/schema.json"
BUILD SUCCESSFUL in 616ms
1 actionable task: 1 executed
添加GraphQL查询
接下来,我们将把必要的查询信息写入另一个定义文件中。
我们将使用之前在 GraphiQL 中尝试的查询(关于前151只宝可梦的信息)。
query KantoPokemons {
pokemons(first: 151) {
number,
name,
types
}
}
阿波罗自动生成查询模型类。
如果能够做到上述提到的,为了享受 strongly-typed 的好处,让我们使用 Apollo 的功能来自动生成所需的模型类,并用于 GraphQL 查询!
$ ./gradlew generateApolloSources
BUILD SUCCESSFUL in 868ms
4 actionable tasks: 2 executed, 2 up-to-date
是的,只需这3个步骤(实质上是2个步骤自动生成),预先准备工作就完成了!非常简单吧。
由于使用了在 GraphiQL 中尝试的 Graph Query,只要不出现拷贝粘贴错误,基本上可以保证以确切正确的语法进行编写,这也是一个很大的优点。
仓库的实现
终于结束了,这是Spring Boot应用程序的实现。
由于着重于GraphQL客户端实现,因此只需实现数据源获取即可,我们将只实现Spring Boot的Repository。
这次我们想要实现一个功能,以返回上述查询的信息,即获取关于关都地区宝可梦的基本信息。
package com.example.graphqlclientspring;
import com.apollographql.apollo.ApolloClient;
import com.apollographql.apollo.api.Response;
import com.apollographql.apollo.rx3.Rx3Apollo;
import graphqlpokemon.KantoPokemonsQuery;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public class PokemonRepository {
public List<KantoPokemonsQuery.Pokemon> fetchKantoPokemons() {
final var apolloClient = ApolloClient.builder()
.serverUrl("http://localhost:5000/graphql/endpoint")
.build();
final var query = KantoPokemonsQuery.builder().build();
final var apolloQueryCall = apolloClient.query(query);
// ブロッキング処理を簡単に書くため、Rx3Apollo を利用
return Rx3Apollo.from(apolloQueryCall)
.map(Response::getData)
.map(KantoPokemonsQuery.Data::pokemons)
.blockingFirst();
}
}
看到上述代码,我们可以明白,只需要几行代码就能实现 Apollo 的功能。
控制器的实现
最後,我們將創建一個用於調用上述 Repository 的入口點。
這個實現並沒有什麼特別困難的地方,Apollo 生成的類不能直接進行序列化,所以我們創建了一個 POJO 類,將返回結果以 json 格式返回。這是一個簡單的實現,僅僅是將數據轉換到該類中。
package com.example.graphqlclientspring;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.stream.Collectors;
@RestController
public class PokemonController {
private final PokemonRepository pokemonRepository;
public PokemonController(PokemonRepository pokemonRepository) {
this.pokemonRepository = pokemonRepository;
}
@GetMapping("/pokemons")
public ResponseEntity<List<PokemonResponseEntity>> kantoPokemons() {
// 取得と出力用オブジェクトへの変換
final var pokemons = pokemonRepository.fetchKantoPokemons()
.stream()
.map(pokemon -> new PokemonResponseEntity(pokemon.number(), pokemon.name(), pokemon.types()))
.collect(Collectors.toList());
return ResponseEntity.ok(pokemons);
}
// json 出力用 POJO
public static class PokemonResponseEntity {
public final String number;
public final String name;
public final List<String> types;
public PokemonResponseEntity(String number, String name, List<String> types) {
this.number = number;
this.name = name;
this.types = types;
}
}
}
确认行动
那么,我们来运行已经完成的代码,进行操作确认吧。
$ ./gradlew bootRun
让我们尝试在另一个窗口中执行或在浏览器中打开它。(jq 是用于格式化 JSON 的命令)
我认为您可以获取到前151只宝可梦的信息,如下所示。
# Spring Boot アプリケーションのデフォルトポートは 8080
$ curl -s "http://localhost:8080/pokemons" | jq
[
{
"number": "001",
"name": "Bulbasaur",
"types": [
"Grass",
"Poison"
]
},
{
"number": "002",
"name": "Ivysaur",
"types": [
"Grass",
"Poison"
]
},
{
"number": "003",
"name": "Venusaur",
"types": [
"Grass",
"Poison"
]
},
{
"number": "004",
"name": "Charmander",
"types": [
"Fire"
]
},
{
"number": "005",
"name": "Charmeleon",
"types": [
"Fire"
]
},
总结
尽管变得相当冗长,但以上是使用GraphQL API数据源的客户端处理的实现。
如果对您有帮助的话,请给予赞同或评论,那会给予我很大的鼓励。
感谢您的阅读到最后。
GitHub 仓库
因为我已经提交了下面的东西,所以请随意参考。
- https://github.com/ruwatana/graphql-client-spring