SmallRye GraphQL
本指南演示 Quarkus 应用如何使用 SmallRye GraphQL,它是 MicroProfile GraphQL 规范的一种实现。
GraphQL 规范网站这样介绍它:
GraphQL 是面向 API 的查询语言,也是基于现有数据执行这些查询的运行时。它为 API 数据提供完整、易懂的描述,使客户端准确获取所需内容,并便于 API 随时间演进,同时提供强大的开发工具。
GraphQL 最初由 Facebook 于 2012 年开发,自 2015 年起成为开放标准。
GraphQL 是 REST API 的另一种选择。它可以从以下方面帮助客户端:
- 避免过量获取与获取不足
-
典型 REST API 的响应结构由服务器决定。即使不需要所有字段,客户端也可能不得不接收全部数据,造成过量获取。反过来,客户端也可能需要根据第一次响应继续执行多个请求(例如 HATEOAS 场景),才能获取全部所需数据,形成获取不足。
- API 演进
-
GraphQL 按客户端请求返回数据,因此为已有 API 增加字段和能力,通常不会破坏已有客户端。
前置条件
完成本指南需要:
-
约 15 分钟。
-
一个 IDE。
-
JDK 17 或以上版本,并正确配置
JAVA_HOME。 -
Apache Maven 3.9.16
-
如需使用,可安装 Quarkus CLI。
-
如需构建原生可执行文件,可安装并配置 Mandrel 或 GraalVM;也可以使用 Docker 执行原生容器构建。
架构
我们将构建一个简单应用,在 /graphql 暴露 GraphQL API。
本例受到原文所链接的一个常见 GraphQL API 示例启发。
完整示例
建议按照后面的步骤逐步创建应用,也可以直接查看完整示例。
克隆仓库:git clone https://github.com/quarkusio/quarkus-quickstarts.git,或下载原文链接中的归档。
完整代码位于 microprofile-graphql-quickstart 目录。
创建 Maven 项目
首先创建项目,执行以下命令:
quarkus create app org.acme:microprofile-graphql-quickstart \
--extension='quarkus-smallrye-graphql' \
--no-code
cd microprofile-graphql-quickstart
创建 Gradle 项目时,添加 --gradle 或 --gradle-kotlin-dsl。
CLI 的安装与使用详见 Quarkus CLI 指南。
mvn io.quarkus.platform:quarkus-maven-plugin:3.40.1:create \
-DprojectGroupId=org.acme \
-DprojectArtifactId=microprofile-graphql-quickstart \
-Dextensions='quarkus-smallrye-graphql' \
-DnoCode
cd microprofile-graphql-quickstart
使用 Maven 命令创建 Gradle 项目时,添加 -DbuildTool=gradle 或 -DbuildTool=gradle-kotlin-dsl。
Windows 用户请注意:
-
在 cmd 中,不使用反斜杠续行,应把命令放在同一行。
-
在 PowerShell 中,用双引号包住
-D参数,例如"-DprojectArtifactId=microprofile-graphql-quickstart"。
该命令生成项目并引入 smallrye-graphql 扩展。
对于已有 Quarkus 项目,在项目根目录执行以下命令添加扩展:
quarkus extension add quarkus-smallrye-graphql
./mvnw quarkus:add-extension -Dextensions='quarkus-smallrye-graphql'
./gradlew addExtension --extensions='quarkus-smallrye-graphql'
这会在构建文件中加入:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-graphql</artifactId>
</dependency>
implementation("io.quarkus:quarkus-smallrye-graphql")
准备应用:GraphQL API
下面开始创建 GraphQL API。
首先创建表示遥远星系中电影及角色的实体:
package org.acme.microprofile.graphql;
public class Film {
public String title;
public Integer episodeID;
public String director;
public LocalDate releaseDate;
}
public class Hero {
public String name;
public String surname;
public Double height;
public Integer mass;
public Boolean darkSide;
public LightSaber lightSaber;
public List<Integer> episodeIds = new ArrayList<>();
}
enum LightSaber {
RED, BLUE, GREEN
}
| 为便于阅读,这里使用公共字段;私有字段配合公共 getter/setter 同样适用。 |
这些类描述 GraphQL schema,即客户端能够访问的对象、字段和关系。
接着创建一个充当仓库的 CDI bean:
@ApplicationScoped
public class GalaxyService {
private List<Hero> heroes = new ArrayList<>();
private List<Film> films = new ArrayList<>();
public GalaxyService() {
Film aNewHope = new Film();
aNewHope.title = "A New Hope";
aNewHope.releaseDate = LocalDate.of(1977, Month.MAY, 25);
aNewHope.episodeID = 4;
aNewHope.director = "George Lucas";
Film theEmpireStrikesBack = new Film();
theEmpireStrikesBack.title = "The Empire Strikes Back";
theEmpireStrikesBack.releaseDate = LocalDate.of(1980, Month.MAY, 21);
theEmpireStrikesBack.episodeID = 5;
theEmpireStrikesBack.director = "George Lucas";
Film returnOfTheJedi = new Film();
returnOfTheJedi.title = "Return Of The Jedi";
returnOfTheJedi.releaseDate = LocalDate.of(1983, Month.MAY, 25);
returnOfTheJedi.episodeID = 6;
returnOfTheJedi.director = "George Lucas";
films.add(aNewHope);
films.add(theEmpireStrikesBack);
films.add(returnOfTheJedi);
Hero luke = new Hero();
luke.name = "Luke";
luke.surname = "Skywalker";
luke.height = 1.7;
luke.mass = 73;
luke.lightSaber = LightSaber.GREEN;
luke.darkSide = false;
luke.episodeIds.addAll(Arrays.asList(4, 5, 6));
Hero leia = new Hero();
leia.name = "Leia";
leia.surname = "Organa";
leia.height = 1.5;
leia.mass = 51;
leia.darkSide = false;
leia.episodeIds.addAll(Arrays.asList(4, 5, 6));
Hero vader = new Hero();
vader.name = "Darth";
vader.surname = "Vader";
vader.height = 1.9;
vader.mass = 89;
vader.darkSide = true;
vader.lightSaber = LightSaber.RED;
vader.episodeIds.addAll(Arrays.asList(4, 5, 6));
heroes.add(luke);
heroes.add(leia);
heroes.add(vader);
}
public List<Film> getAllFilms() {
return films;
}
public Film getFilm(int id) {
return films.get(id);
}
public List<Hero> getHeroesByFilm(Film film) {
return heroes.stream()
.filter(hero -> hero.episodeIds.contains(film.episodeID))
.collect(Collectors.toList());
}
public void addHero(Hero hero) {
heroes.add(hero);
}
public Hero deleteHero(int id) {
return heroes.remove(id);
}
public List<Hero> getHeroesBySurname(String surname) {
return heroes.stream()
.filter(hero -> hero.surname.equals(surname))
.collect(Collectors.toList());
}
}
现在创建第一个 GraphQL API。
编辑 org.acme.microprofile.graphql.FilmResource:
@GraphQLApi (1)
public class FilmResource {
@Inject
GalaxyService service;
@Query("allFilms") (2)
@Description("Get all Films from a galaxy far far away") (3)
public List<Film> getAllFilms() {
return service.getAllFilms();
}
}
| 1 | @GraphQLApi 表示该 CDI bean 是 GraphQL 端点。 |
| 2 | @Query 使该方法可以用 allFilms 名称查询。 |
| 3 | 为查询方法提供文档说明。 |
@Query 的值可省略,省略时采用方法名推导查询名称。 |
第一个可查询 API 已定义,后面将继续扩展它。
启动
以开发模式启动 Quarkus:
quarkus dev
./mvnw quarkus:dev
./gradlew --console=plain quarkusDev
内省
可以通过以下请求取得完整 GraphQL schema:
curl http://localhost:8080/graphql/schema.graphql
服务器会返回 API 的完整 schema。
GraphQL UI
| 实验性功能,不属于 MicroProfile 规范。 |
GraphQL UI 便于直接与 API 交互。
smallrye-graphql 扩展附带 GraphiQL,默认在开发和测试模式启用。若希望生产模式也包含它,可将 quarkus.smallrye-graphql.ui.always-include 设为 true。
访问 http://localhost:8080/q/graphql-ui/ 打开 GraphQL UI。

为 UI 增减安全控制的方法,见“Web 端点授权”指南。
查询 GraphQL API
打开开发模式部署的 GraphQL UI。
输入以下查询并点击执行按钮:
query allFilms {
allFilms {
title
director
releaseDate
episodeID
}
}
该查询包含 Film 的全部字段,因此响应也返回全部字段。GraphQL 允许客户端自行选择需要的字段。
假设客户端只需要 title 和 releaseDate,前一个请求就获取了不必要的数据。
改为执行以下查询:
query allFilms {
allFilms {
title
releaseDate
}
}
响应只包含所需字段,避免了过量获取。
继续向 FilmResource 添加:
@Query
@Description("Get a Films from a galaxy far far away")
public Film getFilm(@Name("filmId") int id) {
return service.getFilm(id);
}
这里省略了 @Query 的值,因此查询名由方法名去掉 get 前缀得到。 |
该查询按 ID 获取电影。参数上的 @Name 将参数名改为 filmId;省略时默认是 id。
在 UI 中执行:
query getFilm {
film(filmId: 1) {
title
director
releaseDate
episodeID
}
}
与前面的例子一样,可以自行选择 film 查询返回的字段,获取单部电影。
如果客户端同时需要 ID 为 0 和 1 的电影,按单资源端点设计的 REST API 通常需要两次调用。
GraphQL 可以在一次请求中执行多个查询。
执行以下查询获取两部电影:
query getFilms {
film0: film(filmId: 0) {
title
director
releaseDate
episodeID
}
film1: film(filmId: 1) {
title
director
releaseDate
episodeID
}
}
客户端由此在一个请求中获得所需数据。
扩展 API
目前 API 只能查询电影,现在增加获取电影中英雄角色的能力。
在 FilmResource 中添加:
public List<Hero> heroes(@Source Film film) { (1)
return service.getHeroesByFilm(film);
}
| 1 | 允许返回 Film 的查询同时获取 List<Hero> 数据。 |
这个方法改变了 schema,但旧查询仍可继续工作,因为这里只是新增获取电影角色数据的能力。
执行以下查询获取电影及角色:
query getFilmHeroes {
film(filmId: 1) {
title
director
releaseDate
episodeID
heroes {
name
height
mass
darkSide
lightSaber
}
}
}
响应现在包含电影中的英雄角色。
批量处理
当返回类似 getAllFilms 的集合时,可以使用批量形式,更高效地获取相关角色:
public List<List<Hero>> heroes(@Source List<Film> films) { (1)
// Here fetch all hero lists
}
| 1 | 批量接收电影列表,以便一并查询相关角色。 |
非阻塞
返回 Uni 或为方法添加 @NonBlocking,可以使查询采用响应式/非阻塞执行:
@Query
@Description("Get a Films from a galaxy far far away")
public Uni<Film> getFilm(int filmId) {
// ...
}
也可以使用 @NonBlocking:
@Query
@Description("Get a Films from a galaxy far far away")
@NonBlocking
public Film getFilm(int filmId) {
// ...
}
使用 Uni 或 @NonBlocking 时,请求在事件循环线程上执行,而不是工作线程。
一个请求可以混合阻塞和非阻塞操作:
@Query
@Description("Get a Films from a galaxy far far away")
@NonBlocking
public Film getFilm(int filmId) {
// ...
}
public List<Hero> heroes(@Source Film film) {
return service.getHeroesByFilm(film);
}
上例在事件循环上获取电影,然后切换到工作线程获取角色。
RunOnVirtualThread
给方法添加 @RunOnVirtualThread,即可在虚拟线程中执行查询:
@Query
@Description("Get a Films from a galaxy far far away")
@RunOnVirtualThread
public Film getFilm(int filmId) {
// ...
}
请同时遵循虚拟线程的通用使用指南。
事务
当存在 JTA 事务管理器(如 Hibernate ORM 或 quarkus-narayana-jta)时,扩展会自动为阻塞式 GraphQL 操作添加事务行为,等同于标注 @Transactional。这方便了需要读写数据库的解析器。
仅当以下条件全部满足时,才添加隐式事务:
-
存在
TRANSACTIONS能力,即类路径中有 JTA 扩展。 -
不存在 Hibernate Reactive;后者由响应式会话自行管理事务。
-
方法位于
@GraphQLApi类中,且是 GraphQL 操作:@Query、@Mutation、@Subscription、@Name、@Resolver或@Source方法。 -
方法返回阻塞类型。
Uni、Multi、CompletionStage、CompletableFuture、Publisher等响应式返回类型不会被自动事务化。 -
方法没有
@NonBlocking。 -
方法尚未显式标注
@Transactional;显式配置总是优先。
若某个操作不需要此行为,例如调用的库自行管理 JDBC 事务,可以标注:
@Query("aiGenerationQuota")
@Transactional(Transactional.TxType.NOT_SUPPORTED)
public AiQuota getRemainingQuota() {
// ...
}
也可以使用 REQUIRES_NEW 等其他 TxType 控制事务参与方式,因为显式注解优先于隐式事务。
抽象类型
当前 schema 只有 Hero 和 Film 两个具体类型。下面增加其他类型,并引入便于客户端使用的抽象。
接口
先为英雄增加盟友。
创建表示 Ally 的实体:
public class Ally {
public String name;
public String surname;
public Hero partner;
}
更新 GalaxyService,加入盟友:
private List<Ally> allies = new ArrayList();
public GalaxyService() {
// ...
Ally jarjar = new Ally();
jarjar.name = "Jar Jar";
jarjar.surname = "Binks";
allies.add(jarjar);
}
public List<Ally> getAllAllies() {
return allies;
}
更新 FilmResource,允许查询全部盟友:
@Query
public List<Ally> allies() {
return service.getAllAllies();
}
在 UI 中执行:
query getAllies {
allies {
name
surname
}
}
Ally 与 Hero 有一些共同字段,因此可以为所有角色建立共同抽象。
创建描述共同角色特征的 Java 接口:
public interface Character {
(1)
String getName();
String getSurname();
}
| 1 | 接口中的 getter 定义它包含的 GraphQL 字段。 |
更新 Hero 和 Ally,实现该接口:
public class Hero implements Character {
// ...
(1)
public String getName() {
return name;
}
(1)
public String getSurname() {
return surname;
}
}
public class Ally implements Character {
// ...
(1)
public String getName() {
return name;
}
(1)
public String getSurname() {
return surname;
}
}
| 1 | 接口无法定义实例字段,因此实现类需要实现 getter。 |
schema 随之改变,新增 Ally 类型和 Character 接口:
(1)
interface Character {
name: String
surname: String
}
(2)
type Ally implements Character {
name: String
surname: String
partner: Hero
}
(3)
type Hero implements Character {
name: String
surname: String
# ...
}
| 1 | Character 接口的 getter 被定义为 GraphQL 字段。 |
| 2 | 新增 Ally 类型,它实现 Character。 |
| 3 | Hero 也更新为实现 Character。 |
更新 GalaxyService,返回所有角色:
public List<Character> getAllCharacters() {
List<Character> characters = new ArrayList<>();
characters.addAll(heroes);
characters.addAll(allies);
return characters;
}
现在客户端可以查询全部角色,而不仅是英雄。
向 FilmResource 添加:
@Query
@Description("Get all characters from a galaxy far far away")
public List<Character> characters() {
return service.getAllCharacters();
}
在 UI 中执行:
query getCharacters {
characters {
name
surname
}
}
联合类型
| 实验性功能,不属于 MicroProfile 规范。 |
目前 API 只能直接查询某个实体或实体列表。现在希望支持跨实体搜索。Hero 和 Ally 共有 Character 抽象,但它不包含 Film。
创建表示搜索结果可能类型的新抽象:
package org.acme.microprofile.graphql;
import io.smallrye.graphql.api.Union;
@Union (1)
public interface SearchResult {
}
| 1 | @Union 表明 Java 接口代表 GraphQL 联合类型,而不是 GraphQL 接口。 |
| 表示联合类型的 Java 接口不必为空,但其中的 getter 不会直接改变 GraphQL schema。 |
让实体实现 SearchResult:
public class Film implements SearchResult {
// ...
}
public interface Character extends SearchResult {
// ...
}
public class Hero implements Character {
// ...
}
public class Ally implements Character {
// ...
}
为 GalaxyService 添加搜索:
public List<SearchResult> search(String query) {
List<SearchResult> results = new ArrayList<>();
List<Film> matchingFilms = films.stream()
.filter(film -> film.title.contains(query)
|| film.director.contains(query))
.collect(Collectors.toList());
results.addAll(matchingFilms);
List<Character> matchingCharacters = getAllCharacters().stream()
.filter(character -> character.getName().contains(query)
|| character.getSurname().contains(query))
.collect(Collectors.toList());
results.addAll(matchingCharacters);
return results;
}
向 FilmResource 添加:
@Query
@Description("Search for heroes or films")
public List<SearchResult> search(String query) {
return service.search(query);
}
在 UI 中执行:
query searchTheGalaxy {
search(query: "a") {
... on Film {
title
director
}
... on Character {
name
surname
}
}
}
| SearchResult 联合包含实现 Character 的成员,因此可以在查询中使用 Character 接口。 |
变更操作(Mutation)
创建、更新或删除数据时使用 Mutation。
下面为 API 增加添加和删除英雄的能力。
向 FilmResource 添加:
@Mutation
public Hero createHero(Hero hero) {
service.addHero(hero);
return hero;
}
@Mutation
public Hero deleteHero(int id) {
return service.deleteHero(id);
}
执行以下 Mutation 插入英雄:
mutation addHero {
createHero(hero: {
name: "Han",
surname: "Solo"
height: 1.85
mass: 80
darkSide: false
episodeIds: [4, 5, 6]
}
)
{
name
surname
}
}
它会在服务中创建 Hero 实体。
响应包含新角色的 name 和 surname,因为 Mutation 的花括号选择集请求了这两个字段。也可以请求客户端需要的服务端生成字段。
再尝试删除条目:
mutation DeleteHero {
deleteHero(id :3){
name
surname
}
}
与创建操作类似,花括号中的选择集让响应返回被删除角色的 name 和 surname。
订阅
订阅允许持续接收查询相关事件,底层使用 WebSocket。详情见 GraphQL over WebSocket 协议规范。
例如,在创建新英雄时接收通知:
BroadcastProcessor<Hero> processor = BroadcastProcessor.create(); (1)
@Mutation
public Hero createHero(Hero hero) {
service.addHero(hero);
processor.onNext(hero); (2)
return hero;
}
@Subscription
public Multi<Hero> heroCreated(){
return processor; (3)
}
| 1 | 用于广播新英雄的 Multi 处理器。 |
| 2 | 添加新英雄时,同时广播它。 |
| 3 | 将该流暴露到 schema,并在运行时通过 WebSocket 提供。 |
客户端连接 /graphql 的 WebSocket 后,就能接收新英雄创建事件:
subscription ListenForNewHeroes {
heroCreated {
name
surname
}
}
按字段查询
还可以按单个字段查询,例如按姓氏查找英雄。
向 FilmResource 添加:
@Query
public List<Hero> getHeroesWithSurname(@DefaultValue("Skywalker") String surname) {
return service.getHeroesBySurname(surname);
}
@DefaultValue 指定未提供 surname 参数时使用 Skywalker。
在 UI 中尝试以下查询:
query heroWithDefaultSurname {
heroesWithSurname{
name
surname
lightSaber
}
}
query heroWithSurnames {
heroesWithSurname(surname: "Vader") {
name
surname
lightSaber
}
}
上下文
通过 SmallRye 专有的实验性功能,可以在代码任意位置获取 GraphQL 请求信息:
@Inject Context context;
在 GraphQLApi 类中,也可以通过方法参数获取:
@Query
@Description("Get a Films from a galaxy far far away")
public Film getFilm(Context context, int filmId) {
// ...
}
上下文对象提供:
-
原始请求(Query/Mutation)。
-
参数。
-
路径。
-
选中的字段。
-
变量。
这些信息有助于优化对下游数据存储的查询。
更多信息见 JavaDoc。
GraphQL-Java
上下文还提供访问底层 graphql-java 的接口:
DataFetchingEnvironment dfe = context.unwrap(DataFetchingEnvironment.class);
schema 生成期间也可以访问底层 graphql-java,直接添加功能:
public GraphQLSchema.Builder addMyOwnEnum(@Observes GraphQLSchema.Builder builder) {
// Here add your own features directly, example adding an Enum
GraphQLEnumType myOwnEnum = GraphQLEnumType.newEnum()
.name("SomeEnum")
.description("Adding some enum type")
.value("value1")
.value("value2").build();
return builder.additionalType(myOwnEnum);
}
通过观察者,可向 schema builder 添加自定义内容。
要使用观察者,需在 application.properties 中设置 quarkus.smallrye-graphql.events.enabled=true。 |
类型适配
适配到标量
另一项 SmallRye 专有实验功能,可以将已有标量对应的 Java 类型映射成其他标量类型,也可以将原本会生成 GraphQL Type 或 Input 的复杂对象映射到已有标量。
将已有标量映射为另一种类型
public class Movie {
@AdaptToScalar(Scalar.Int.class)
Long idLongThatShouldChangeToInt;
// ....
}
上例把 Java Long 映射为 Int 标量,而非默认 BigInteger。
将复杂对象映射为标量
public class Person {
@AdaptToScalar(Scalar.String.class)
Phone phone;
// ....
}
这样会映射为 String 标量,而不是创建 GraphQL Type 或 Input。
为支持这种转换,Phone 需要接收 String(或 Int、Date 等对应类型)的构造函数、相应 setter,或者 fromString、fromInt、fromDate 等静态方法。
例如:
public class Phone {
private String number;
// Getters and setters....
public static Phone fromString(String number) {
Phone phone = new Phone();
phone.setNumber(number);
return phone;
}
}
@AdaptToScalar 的详情见 JavaDoc。
通过适配器转换
更复杂的情况可以提供 Adapter,在其中自行完成映射。
AdaptWith 的详情见 JavaDoc。
例如:
public class Profile {
// Map this to an email address
@AdaptWith(AddressAdapter.class)
public Address address;
// other getters/setters...
}
public class AddressAdapter implements Adapter<EmailAddress, Address> {
@Override
public Address from(EmailAddress email) {
Address a = new Address();
a.addressType = AddressType.email;
a.addLine(email.getValue());
return a;
}
@Override
public EmailAddress to(Address address) {
if (address != null && address.addressType != null && address.addressType.equals(AddressType.email)) {
return new EmailAddress(address.lines.get(0));
}
return null;
}
}
也支持 @JsonbTypeAdapter。 |
Map 的内置支持
Map 的键和值可能在运行时变化,因此难以描述在 schema 中,GraphQL 默认不支持 Map。Quarkus 利用上述适配机制,将 Map 映射为带有可选 key 参数的 Entry<Key,Value>,使你可以返回整个 Map,也可以按键查询。
例如:
@Query
public Map<ISO6391, Language> language() {
return languageService.getLanguages();
}
public enum ISO6391 {
af,
en,
de,
fr
}
public class Language {
private ISO6391 iso6391;
private String nativeName;
private String enName;
private String please;
private String thankyou;
// Getters & Setters
}
| 键和值均可为枚举、标量或复杂对象。 |
查询整个 Map 的所有字段:
{
language{
key
value {
enName
iso6391
nativeName
please
thankyou
}
}
}
响应示例:
{
"data": {
"language": [
{
"key": "fr",
"value": {
"enName": "french",
"iso6391": "fr",
"nativeName": "français",
"please": "s'il te plaît",
"thankyou": "merci"
}
},
{
"key": "af",
"value": {
"enName": "afrikaans",
"iso6391": "af",
"nativeName": "afrikaans",
"please": "asseblief",
"thankyou": "dankie"
}
},
{
"key": "de",
"value": {
"enName": "german",
"iso6391": "de",
"nativeName": "deutsch",
"please": "bitte",
"thankyou": "danke dir"
}
},
{
"key": "en",
"value": {
"enName": "english",
"iso6391": "en",
"nativeName": "english",
"please": "please",
"thankyou": "thank you"
}
}
]
}
}
也可以按键查询:
{
language (key:af){
value {
please
thankyou
}
}
}
此时只返回对应的值:
{
"data": {
"language": [
{
"value": {
"please": "asseblief",
"thankyou": "dankie"
}
}
]
}
}
| 可以用自己的实现覆盖默认 Map 适配器。 |
错误码
使用 SmallRye 专有的 @ErrorCode,可以向 GraphQL 错误响应添加错误码:
@ErrorCode("some-business-error-code")
public class SomeBusinessException extends RuntimeException {
// ...
}
发生 SomeBusinessException 时,错误输出包含该错误码:
{
"errors": [
{
"message": "Unexpected failure in the system. Jarvis is working to fix it.",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"annotatedCustomBusinessException"
],
"extensions": {
"exception": "io.smallrye.graphql.test.apps.error.api.ErrorApi$AnnotatedCustomBusinessException",
"classification": "DataFetchingException",
"code": "some-business-error-code" (1)
}
}
],
"data": {
...
}
}
| 1 | 错误码。 |
JavaScript 客户端
将 quarkus.smallrye-graphql.js-client.enabled 设为 true 后,扩展在构建时生成 JavaScript 客户端库和类型化代理模块,作为 Web 资源。配合 Web Dependency Locator,可通过标准 import map 约定导入。
启用客户端
在 application.properties 中添加:
quarkus.smallrye-graphql.js-client.enabled=true
这会生成两个资源:
| 资源 | 用途 |
|---|---|
|
|
可复用的 GraphQLClient 类,提供 query()、mutate() 和 subscribe()。 |
|
|
类型化代理,为各个查询、变更和订阅导出函数。 |
使用类型化代理
生成的代理导出 Queries、Mutations、Subscriptions 对象,以及底层 client 实例:
import { client, Queries, Mutations } from '@quarkus/graphql-api';
// Call a query
const data = await Queries.allBooks();
// Call a query with variables
const result = await Queries.book({ id: 42 });
// Call a mutation
const created = await Mutations.createBook({ title: 'Quarkus in Action' });
每个函数接收与 GraphQL 操作参数对应的 variables 对象。包含选择集的 GraphQL 查询字符串在构建时自动生成。
订阅
订阅操作返回 Subscription 对象:
import { Subscriptions } from '@quarkus/graphql-api';
const sub = Subscriptions.onBookCreated()
.onData(data => console.log('New book:', data))
.onError(err => console.error('Error:', err))
.onComplete(() => console.log('Stream completed'));
// Cancel later
await sub.cancel();
订阅使用 graphql-transport-ws WebSocket 子协议。
直接使用客户端库
需要更多控制时,可直接导入 GraphQLClient:
import { GraphQLClient } from '@quarkus/graphql';
const client = new GraphQLClient({ endpoint: '/graphql' });
// Custom query
const data = await client.query(`
query {
allBooks {
title
author { name }
}
}
`);
结语
SmallRye GraphQL 让客户端准确获取所需数据,减少过量获取与获取不足。
API 可以通过新增能力演进,同时保持已有查询兼容。
配置参考
带标记的配置项在构建时固定;其他配置可以在运行时覆盖。
|
配置项 |
类型 |
默认值 |
|---|---|---|
|
提供查询的根路径,默认 graphql;默认相对于 ${quarkus.http.root-path} 解析。 环境变量: 更多信息 |
string |
|
|
启用 Apollo Federation。未明确设置时,如果检测到 GraphQL Federation 注解,就自动启用。 环境变量: 更多信息 |
boolean |
|
|
启用 Federation 批量解析,默认关闭。 环境变量: 更多信息 |
boolean |
|
|
启用指标,默认 false;启用时需要指标扩展。 环境变量: 更多信息 |
boolean |
|
|
启用追踪。添加追踪扩展后默认启用。 环境变量: 更多信息 |
boolean |
|
|
启用事件,让应用可以接收启动和执行期间的事件。 环境变量: 更多信息 |
boolean |
|
|
启用非阻塞支持,默认 true。 环境变量: 更多信息 |
boolean |
|
|
类型命名策略,可选 default、merge-inner-class、full。 环境变量: 更多信息 |
string |
|
|
将数据获取器异常写入日志;开发和测试模式默认 true,生产模式默认 false。 环境变量: 更多信息 |
boolean |
|
|
通过 HTTP 提供 schema。 环境变量: 更多信息 |
boolean |
|
|
服务端支持的 GraphQL WebSocket 子协议,可选 graphql-ws、graphql-transport-ws,默认两者都启用。 环境变量: 更多信息 |
字符串列表 |
|
|
为所有 GraphQL 操作生成 JavaScript 客户端代理。启用后,会生成静态客户端库、类型化代理及裸模块导入所需的 import map。 环境变量: 更多信息 |
boolean |
|
|
额外注册的标量,来自 graphql-java-extended-scalars 库。 环境变量: 更多信息 |
可选 uuid(java.util.UUID)、object(java.lang.Object)、json(jakarta.json.JsonObject)。 |
|
|
客户端初始化负载中保存 Authorization 信息的键名,其值按 Authorization 请求头处理。优先使用请求头,但 JavaScript 等环境无法直接为 WebSocket 添加任意头。另一选择是 Bearer Token Authentication,它可在连接打开前注入头,有些情况下更合适。默认未定义,即不从初始化负载读取认证信息。 环境变量: 更多信息 |
string |
|
|
是否启用 GraphQL UI。默认在 UI 已包含时启用,参见 always-include。 环境变量: 更多信息 |
boolean |
|
|
指定 schema 字段可见性。使用逗号分隔的 GraphQLType.GraphQLField 模式确定需要排除的字段;特殊值 no-introspection 禁用内省字段。详情见 graphql-java 文档。 环境变量: 更多信息 |
string |
|
|
从响应 data 中排除 null 字段,但因解析失败产生的错误字段除外。默认关闭。 环境变量: 更多信息 |
boolean |
|
|
需要解包的异常类名。 环境变量: 更多信息 |
字符串列表 |
|
|
应公开消息的运行时异常类名列表。默认隐藏运行时异常消息,返回通用 Server Error。 环境变量: 更多信息 |
字符串列表 |
|
|
应隐藏消息的受检异常类名列表。默认公开受检异常消息。 环境变量: 更多信息 |
字符串列表 |
|
|
隐藏异常消息时使用的默认文本,默认为 Server Error。 环境变量: 更多信息 |
string |
|
|
错误响应包含的扩展字段列表,默认不包含。有效值包括 exception、classification、code、description、validationErrorType、queryPath。 环境变量: 更多信息 |
字符串列表 |
|
|
允许通过 HTTP GET 查询。 环境变量: 更多信息 |
boolean |
|
|
允许 POST 请求通过查询参数提供或覆盖值。 环境变量: 更多信息 |
boolean |
|
|
在 schema 中包含标量定义。 环境变量: 更多信息 |
boolean |
|
|
在 schema 中包含 schema 自身的定义。 环境变量: 更多信息 |
boolean |
|
|
在 schema 中包含指令。 环境变量: 更多信息 |
boolean |
|
|
在 schema 中包含内省类型。 环境变量: 更多信息 |
boolean |
|
|
向标准输出记录请求负载,并可选择包含变量。 环境变量: 更多信息 |
|
|
|
是否将被忽略的字符捕获为 AST 节点,默认 false。 环境变量: 更多信息 |
boolean |
|
|
是否将 graphql.language.Comment 捕获为 AST 节点。 环境变量: 更多信息 |
boolean |
|
|
是否将 graphql.language.SourceLocation 捕获为 AST 节点,默认 true。 环境变量: 更多信息 |
boolean |
|
|
解析器接受的原始 token 数上限,超过后抛出异常,默认 15000。 环境变量: 更多信息 |
int |
|
|
解析器接受的原始空白 token 数上限,超过后抛出异常,默认 200000。 环境变量: 更多信息 |
int |
|
|
查询的数据字段总数超过限制时中止,默认不限制。 环境变量: 更多信息 |
int |
|
|
查询总深度超过限制时中止。 环境变量: 更多信息 |
int |
|
|
自 3.26 起弃用,改用 quarkus.smallrye-graphql.ui.enabled。 是否启用 GraphQL UI,默认在已包含 UI 时启用,参见 always-include。 环境变量: 更多信息 |
boolean |
|
|
SmallRye GraphQL UI 配置 |
类型 |
默认值 |
|
GraphQL UI 路径。不允许使用 /,否则会阻止应用提供其他内容。默认相对于 ${quarkus.http.non-application-root-path} 解析。 环境变量: 更多信息 |
string |
|
|
始终包含 UI。默认只在开发和测试中包含;设为 true 后生产环境也会包含。 环境变量: 更多信息 |
boolean |
|











暂无评论内容