SmallRye GraphQL
本指南演示了您的 Quarkus 应用程序如何使用 SmallRye GraphQL,即 MicroProfile GraphQL规范的实现。
This guide demonstrates how your Quarkus application can use SmallRye GraphQL, an implementation of the MicroProfile GraphQL specification.
正如 GraphQL规范网站所述:
As the GraphQL specification website states: GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools.
*GraphQL*最初由 *Facebook*开发于 2012 年,自 2015 年以来一直是一个开放标准。
GraphQL was originally developed by Facebook in 2012 and has been an open standard since 2015.
GraphQL 并不是 REST API 规范的替代品,而仅仅是一种替代方案。与 REST 不同,GraphQL API 能够通过以下方式使客户端受益:
GraphQL is not a replacement for REST API specification but merely an
alternative. Unlike REST, GraphQL API’s have the ability to benefit the client by:
Preventing Over-fetching and Under-fetching:: REST APIs are server-driven fixed data responses that cannot be determined by
the client. Although the client does not require all the fields the client
must retrieve all the data hence Over-fetching
. A client may also require
multiple REST API calls according to the first call (HATEOAS) to retrieve
all the data that is required thereby Under-fetching
.
- API Evolution
-
Since GraphQL API’s returns data that are requested by the client adding additional fields and capabilities to existing API will not create breaking changes to existing clients.
Prerequisites
Unresolved directive in smallrye-graphql.adoc - include::{includes}/prerequisites.adoc[]
Architecture
在本指南中,我们构建了一个简单的 GraphQL 应用程序,该应用程序在 `/graphql`上公开一个 GraphQL API。
In this guide, we build a simple GraphQL application that exposes a GraphQL API
at /graphql
.
此示例受一个流行的 GraphQL API 启发。
This example was inspired by a popular GraphQL API.
Solution
我们建议您遵循接下来的部分中的说明,按部就班地创建应用程序。然而,您可以直接跳到完成的示例。
We recommend that you follow the instructions in the next sections and create the application step by step. However, you can go right to the completed example.
克隆 Git 存储库: git clone {quickstarts-clone-url}
,或下载 {quickstarts-archive-url}[存档]。
Clone the Git repository: git clone {quickstarts-clone-url}
, or download an {quickstarts-archive-url}[archive].
解决方案位于 microprofile-graphql-quickstart
directory中。
The solution is located in the microprofile-graphql-quickstart
directory.
Creating the Maven Project
首先,我们需要一个新项目。使用以下命令创建一个新项目:
First, we need a new project. Create a new project with the following command:
Unresolved directive in smallrye-graphql.adoc - include::{includes}/devtools/create-app.adoc[]
此命令生成一个项目,导入 `smallrye-graphql`扩展。
This command generates a project, importing the smallrye-graphql
extension.
如果您已经配置了 Quarkus 项目,则可以通过在项目基本目录中运行以下命令将 `smallrye-graphql`扩展添加到您的项目:
If you already have your Quarkus project configured, you can add the smallrye-graphql
extension
to your project by running the following command in your project base directory:
Unresolved directive in smallrye-graphql.adoc - include::{includes}/devtools/extension-add.adoc[]
这会将以下内容添加到构建文件中:
This will add the following to your build file:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-graphql</artifactId>
</dependency>
implementation("io.quarkus:quarkus-smallrye-graphql")
Preparing an Application: GraphQL API
在本节中,我们将开始创建 GraphQL API。
In this section we will start creating the GraphQL API.
首先,创建以下代表遥远星系中一部电影的实体:
First, create the following entities representing a film from a galaxy far, far away:
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
}
为了便于阅读,我们使用具有公共字段的类,但具有公共获取器和设置器的私有字段的类也可以正常工作。 |
For readability we use classes with public fields, but classes with private fields with public getters and setters will also work. |
我们刚刚创建的类描述了 GraphQL 模式,这是客户端可以访问的一组可能的数据(对象、字段、关系)。
The classes we have just created describe the GraphQL schema which is a set of possible data (objects, fields, relationships) that a client can access.
让我们继续使用一个示例 CDI bean,它可以充当存储库:
Let’s continue with an example CDI bean, that would work as a repository:
@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。
Now, let’s create our first GraphQL API.
将 org.acme.microprofile.graphql.FilmResource
类编辑为以下内容:
Edit the org.acme.microprofile.graphql.FilmResource
class as following:
@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 annotation indicates that the CDI bean will be a GraphQL endpoint |
2 | @Query annotation defines that this method will be queryable with the name allFilms |
3 | Documentation of the queryable method |
当 |
The value of the |
通过这种方式,我们创建了第一个可查询 API,我们稍后将对其进行扩展。
This way we have created our first queryable API which we will later expand.
Launch
在开发模式下启动 Quarkus 应用程序:
Launch the quarkus application in dev mode:
Unresolved directive in smallrye-graphql.adoc - include::{includes}/devtools/dev.adoc[]
Introspect
可通过调用以下内容来检索 GraphQL API 的完整架构:
The full schema of the GraphQL API can be retrieved by calling the following:
curl http://localhost:8080/graphql/schema.graphql
服务器将返回 GraphQL API 的完整架构。
The server will return the complete schema of the GraphQL API.
GraphQL UI
实验性 - 未包含在 MicroProfile 规范中 |
Experimental - not included in the MicroProfile specification |
GraphQL UI 是一个出色的工具,允许与 GraphQL API 方便地进行交互。
GraphQL UI is a great tool permitting easy interaction with your GraphQL APIs.
Quarkus smallrye-graphql
扩展附带 GraphiQL,并在 dev
和 test
模式中默认启用它,但也可以通过将 quarkus.smallrye-graphql.ui.always-include
配置属性设置为 true
来对 production
模式显式进行配置。
The Quarkus smallrye-graphql
extension ships with GraphiQL and enables it by default in dev
and test
modes,
but it can also be explicitly configured for production
mode as well, by setting the quarkus.smallrye-graphql.ui.always-include
configuration property to true
.
可从 [role="bare"][role="bare"]http://localhost:8080/q/graphql-ui/ 访问 GraphQL UI。
The GraphQL UI can be accessed from [role="bare"]http://localhost:8080/q/graphql-ui/ .
请参阅 Authorization of Web Endpoints 指南,了解如何为 GraphQL UI 添加/删除安全性。
Have a look at the Authorization of Web Endpoints Guide on how to add/remove security for the GraphQL UI.
Query the GraphQL API
现在访问在 dev
模式中已部署的 GraphQL UI 页面。
Now visit the GraphQL UI page that has been deployed in dev
mode.
将以下查询输入 GraphQL UI 然后按 play
按钮:
Enter the following query to the GraphQL UI and press the play
button:
query allFilms {
allFilms {
title
director
releaseDate
episodeID
}
}
由于我们的查询包含 Film
类中的所有字段,因此我们将检索我们响应中的所有字段。由于 GraphQL API 响应是由客户端确定的,因此客户端可以选择它所需的字段。
Since our query contains all the fields in the Film
class
we will retrieve all the fields in our response. Since GraphQL API
responses are client determined, the client can choose which fields
it will require.
让我们假设我们的客户端仅需要 title
和 releaseDate
,这样就使得之前对不需要数据的 API Over-fetching
的调用。
Let’s assume that our client only requires title
and releaseDate
making the previous call to the API Over-fetching
of unnecessary
data.
将以下查询输入 GraphQL UI 然后按 play
按钮:
Enter the following query into the GraphQL UI and hit the play
button:
query allFilms {
allFilms {
title
releaseDate
}
}
在响应中需要注意,我们只检索必需字段。因此,我们已防止 Over-fetching
.
Notice in the response we have only retrieved the required fields.
Therefore, we have prevented Over-fetching
.
让我们继续向 FilmResource
类中添加以下内容,以扩展我们的 GraphQL API。
Let’s continue to expand our GraphQL API by adding the following to the
FilmResource
class.
@Query
@Description("Get a Films from a galaxy far far away")
public Film getFilm(@Name("filmId") int id) {
return service.getFilm(id);
}
需要注意的是,我们已排除了 @Query
注释中的值。因此,查询名称隐式设定为方法名称,排除 get
.
Notice how we have excluded the value in the @Query
annotation.
Therefore, the name of the query is implicitly set as the method name
excluding the get
.
此查询将允许客户端按 ID 检索电影,并且参数上的 @Name
注释将参数名称更改为 filmId
,而非省略 @Name
注释时的默认名称 id
.
This query will allow the client to retrieve the film by id, and the @Name
annotation on the parameter
changes the parameter name to filmId
rather than the default id
that it would be if you omit the @Name
annotation.
在 GraphQL UI
中输入以下内容并发出请求。
Enter the following into the GraphQL UI
and make a request.
query getFilm {
film(filmId: 1) {
title
director
releaseDate
episodeID
}
}
如之前示例所示,可以确定 film
查询方法请求的字段。通过这种方式,我们可检索单独的电影信息。
The film
query method requested fields can be determined
as such in our previous example. This way we can retrieve individual
film information.
然而,假设我们的客户端同时需要具有影片 ID 0
和 1
的两部电影。在 REST API 中,客户端需要向 API 发出两个调用。因此,客户端将为 Under-fetching
.
However, say our client requires both films with filmId 0
and 1
.
In a REST API the client would have to make two calls to the API.
Therefore, the client would be Under-fetching
.
在 GraphQL 中,一次执行多个查询是可能的。
In GraphQL, it is possible to make multiple queries at once.
在 GraphQL UI
中输入以下内容来检索两部电影:
Enter the following into the GraphQL UI
to retrieve two films:
query getFilms {
film0: film(filmId: 0) {
title
director
releaseDate
episodeID
}
film1: film(filmId: 1) {
title
director
releaseDate
episodeID
}
}
这使客户端能够在单个请求中获取所需数据。
This enabled the client to fetch the required data in a single request.
Expanding the API
到目前为止,我们已创建了一个 GraphQL API 来检索电影数据。我们现在希望使客户端能够检索 Film
的 Hero
数据。
Until now, we have created a GraphQL API to retrieve film data.
We now want to enable the clients to retrieve the Hero
data of the Film
.
将以下内容添加到我们的 FilmResource
类:
Add the following to our FilmResource
class:
public List<Hero> heroes(@Source Film film) { (1)
return service.getHeroesByFilm(film);
}
1 | Enable List<Hero> data to be added to queries that respond with Film |
通过添加此方法,我们已有效地改变了 GraphQL API 的架构。尽管架构发生了变化,但以前的查询仍然有效。因为我们仅扩展了 API 以能够检索 Film
的 Hero
数据。
By adding this method we have effectively changed the schema of the GraphQL API.
Although the schema has changed the previous queries will still work.
Since we only expanded the API to be able to retrieve the Hero
data of the Film
.
在 GraphQL UI
中输入以下内容来检索电影和英雄数据。
Enter the following into the GraphQL UI
to retrieve the film and hero data.
query getFilmHeroes {
film(filmId: 1) {
title
director
releaseDate
episodeID
heroes {
name
height
mass
darkSide
lightSaber
}
}
}
现在,响应中包括了电影的英雄。
The response now includes the heroes of the film.
Batching
当您公开 getAllFilms
一般的 Collection
返回值时,您可以使用上述批处理方式,以更有效地获取英雄:
When you are exposing a Collection
return like our getAllFilms
, you might want to use the batch form of the above, to more efficiently fetch
the heroes:
public List<List<Hero>> heroes(@Source List<Film> films) { (1)
// Here fetch all hero lists
}
1 | Here receive the films as a batch, allowing you to fetch the corresponding heroes. |
Non blocking
可以通过将 Uni
用作返回类型来让查询变为响应式,或向方法添加 @NonBlocking
:
Queries can be made reactive by using Uni
as a return type, or adding @NonBlocking
to the method:
@Query
@Description("Get a Films from a galaxy far far away")
public Uni<Film> getFilm(int filmId) {
// ...
}
或可以使用 @NonBlocking
:
Or you can use @NonBlocking
:
@Query
@Description("Get a Films from a galaxy far far away")
@NonBlocking
public Film getFilm(int filmId) {
// ...
}
使用 `Uni`或 `@NonBlocking`表示请求将在事件循环线程上执行,而不是在工作线程上。
Using Uni
or @NonBlocking
means that the request will be executed on Event-loop threads rather than Worker threads.
可以一次将阻塞和非阻塞混合在同一个请求中,
You can mix Blocking and Non-blocking in one request,
@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);
}
上述内容会在事件循环线程上获取电影,但是会切换到工作人员线程来获取英雄。
Above will fetch the film on the event-loop threads, but switch to the worker thread to fetch the heroes.
Abstract Types
当前模式很简单,只有两个具体类型 Hero`和 `Film
。现在我们想要使用附加类型来扩展我们的 API,并且添加一些抽象,以便让客户端可以轻松与之交互。
The current schema is simple with only two concrete types, Hero
and Film
.
Now we want to expand our API with additional types and add some abstractions
that make interacting with them easy for clients.
Interfaces
让我们给我们的英雄配一些盟友。
Let’s give our heroes some allies.
首先,创建一个新实体来表示我们的 Ally
。
First, create a new entity to represent our Ally
.
public class Ally {
public String name;
public String surname;
public Hero partner;
}
更新 `GalaxyService`让其有一些盟友。
Update the GalaxyService
to have some allies.
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`以允许客户端查询所有盟友:
Let’s also update FilmResource
to allow clients to query for all allies:
@Query
public List<Ally> allies() {
return service.getAllAllies();
}
在 GraphQL UI
中输入以下内容并发出请求。
Enter the following into the GraphQL UI
and make a request.
query getAllies {
allies {
name
surname
}
}
请注意,`Ally`有一些与 `Hero`相同的字段。为了更好地使客户端查询变得容易,我们来为任何角色创建一个抽象。
Notice that Ally
has a some of the same fields as a Hero
.
To better make queries easier for clients, let’s create an abstraction for any character.
创建一个新的 Java 接口来定义我们的共同人物特征。
Create a new Java interface that defines our common character traits.
public interface Character {
(1)
String getName();
String getSurname();
}
1 | Getters defined in an interface will define the GraphQL fields that it contains |
现在,更新我们的 `Hero`和 `Ally`实体来实现此接口。
Now, update our Hero
and Ally
entities to implement this interface.
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 | Because interfaces can’t define fields, we have to implement the getters |
通过添加一个接口并更新现有实体来实现它,我们有效地更改了模式。更新的模式现在将包括新的 `Ally`类型和 `Character`接口。
By adding an interface and updating existing entities to implement it, we have effectively changed the schema.
The updated schema will now include the new Ally
type and Character
interface.
(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 | The Character interface was defined with the getters as fields |
2 | The Ally type was added and it implements Character |
3 | The Hero type was updated to implement Character |
更新我们的 `GalaxyService`来提供所有角色。
Update our GalaxyService
to provide all characters.
public List<Character> getAllCharacters() {
List<Character> characters = new ArrayList<>();
characters.addAll(heroes);
characters.addAll(allies);
return characters;
}
现在,我们可以允许客户端查询所有角色,而不仅仅是英雄。
Now we can allow clients to query for all characters, not just heroes.
将以下内容添加到我们的 FilmResource
类:
Add the following to our FilmResource
class:
@Query
@Description("Get all characters from a galaxy far far away")
public List<Character> characters() {
return service.getAllCharacters();
}
在 GraphQL UI
中输入以下内容并发出请求。
Enter the following into the GraphQL UI
and make a request.
query getCharacters {
characters {
name
surname
}
}
Unions
实验性 - 未包含在 MicroProfile 规范中 |
Experimental - not included in the MicroProfile specification |
到目前为止,我们的 API 仅允许我们直接查询实体或实体列表。现在,我们希望允许客户端搜索我们所有的实体。虽然 Hero
和 Ally
具有 Character
的一个共享抽象类型,但没有包含 Film
的抽象。
So far, our API has only allowed us to query directly for an entity or list of entities.
Now we want to allow clients to search all of our entities.
While Hero
and Ally
have a shared abstract type of Character
, there is no abstraction that also includes Film
.
首先,创建此新抽象类型,表示搜索结果的可能返回类型。
First, create this new abstract type representing the possible return types for a search result.
package org.acme.microprofile.graphql;
import io.smallrye.graphql.api.Union;
@Union (1)
public interface SearchResult {
}
1 | @Union is required to indicate this Java interface represents a GraphQL union, not a GraphQL interface |
表示 GraphQL 联合的 Java 接口不必为空,但任何定义的 getter 都不会显式更改 GraphQL 模式。 |
The Java interface representing the GraphQL union does not have to be empty, but any getters defined will not explicitly change the GraphQL schema. |
更新我们的实体以实现 SearchResult
:
Update our entities to implement SearchResult
:
public class Film implements SearchResult {
// ...
}
public interface Character implements SearchResult {
// ...
}
public class Hero implements Character {
// ...
}
public class Ally implements Character {
// ...
}
更新 `GalaxyService`以提供搜索功能:
Update GalaxyService
to provide search:
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
类:
Add the following to our FilmResource
class:
@Query
@Description("Search for heroes or films")
public List<SearchResult> search(String query) {
return service.search(query);
}
在 GraphQL UI
中输入以下内容并发出请求。
Enter the following into the GraphQL UI
and make a request.
query searchTheGalaxy {
search(query: "a") {
... on Film {
title
director
}
... on Character {
name
surname
}
}
}
我们能够使用 |
We are able to use the |
Mutations
当创建、更新或删除数据时,我们将使用 Mutations。
Mutations are used when data is created, updated or deleted.
让我们现在在我们的 GraphQL API 中添加添加和删除英雄的能力。
Let’s now add the ability to add and delete heroes to our GraphQL API.
将以下内容添加到我们的 FilmResource
类:
Add the following to our FilmResource
class:
@Mutation
public Hero createHero(Hero hero) {
service.addHero(hero);
return hero;
}
@Mutation
public Hero deleteHero(int id) {
return service.deleteHero(id);
}
在 GraphQL UI
中输入以下内容以插入 Hero
:
Enter the following into the GraphQL UI
to insert a Hero
:
mutation addHero {
createHero(hero: {
name: "Han",
surname: "Solo"
height: 1.85
mass: 80
darkSide: false
episodeIds: [4, 5, 6]
}
)
{
name
surname
}
}
通过使用此 Mutation,我们在服务中创建了一个 Hero
实体。
By using this mutation we have created a Hero
entity in our service.
请注意,我们在响应中如何检索创建的英雄的 name
和 surname
。这是因为我们选择在 Mutation 查询中的 { }
中的响应中检索这些字段。这可能是一个服务器端生成字段,客户端可能会要求。
Notice how in the response we have retrieved the name
and surname
of the created Hero. This is because we selected to retrieve
these fields in the response within the { }
in the mutation query.
This can easily be a server side generated field that the client may require.
我们现在尝试删除一个条目:
Let’s now try deleting an entry:
mutation DeleteHero {
deleteHero(id :3){
name
surname
}
}
类似于 createHero
Mutation 方法,我们还检索我们已删除的英雄的 name
和 surname
,这些英雄在 { }
中进行定义。
Similar to the createHero
mutation method we also retrieve the name
and
surname
of the hero we have deleted which is defined in { }
.
Subscriptions
订阅允许您订阅查询。它允许您接收事件并正在使用 Web 套接字。请参阅 GraphQL over WebSocket Protocol 规范以了解更多详细信息。
Subscriptions allow you to subscribe to a query. It allows you to receive events and is using web sockets. See the GraphQL over WebSocket Protocol spec for more details.
示例:我们想要知道何时创建新英雄:
Example: We want to know when new Heroes are being created:
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 | The Multi processor that will broadcast any new `Hero`es |
2 | When adding a new Hero , also broadcast it |
3 | Make the stream available in the schema and as a WebSocket during runtime |
现在,任何连接到 /graphql
WebSocket 连接的客户端都将接收关于创建新英雄的事件:
Any client that now connect to the /graphql
WebSocket connection will receive events on new Heroes being created:
subscription ListenForNewHeroes {
heroCreated {
name
surname
}
}
Creating Queries by fields
还可以对各个字段进行查询。例如,我们创建一种通过姓氏查询英雄的方法。
Queries can also be done on individual fields. For example, let’s create a method to query heroes by their last name.
将以下内容添加到我们的 FilmResource
类:
Add the following to our FilmResource
class:
@Query
public List<Hero> getHeroesWithSurname(@DefaultValue("Skywalker") String surname) {
return service.getHeroesBySurname(surname);
}
通过使用注释 @DefaultValue
,我们确定当未提供参数时,surname 的值将为 Skywalker
。
By using the @DefaultValue
annotation we have determined that the surname value
will be Skywalker
when the parameter is not provided.
使用 GraphQL UI
测试以下查询:
Test the following queries with the GraphQL UI
:
query heroWithDefaultSurname {
heroesWithSurname{
name
surname
lightSaber
}
}
query heroWithSurnames {
heroesWithSurname(surname: "Vader") {
name
surname
lightSaber
}
}
Context
您可以使用该实验性 SmallRye 特定功能,在代码中的任何位置获取有关 GraphQL 请求的信息:
You can get information about the GraphQL request anywhere in your code, using this experimental, SmallRye specific feature:
@Inject
Context context;
或者,如果您位于 GraphQLApi
类中,则可以用作方法中的参数,例如:
or as a parameter in your method if you are in the GraphQLApi
class, for instance:
@Query
@Description("Get a Films from a galaxy far far away")
public Film getFilm(Context context, int filmId) {
// ...
}
上下文对象允许您获取以下信息:
The context object allows you to get:
-
the original request (Query/Mutation)
-
the arguments
-
the path
-
the selected fields
-
any variables
这使您可以优化到数据存储区的下游查询。
This allows you to optimize the downstream queries to the datastore.
请参阅 JavaDoc 了解更多详细信息。
See the JavaDoc for more details.
GraphQL-Java
该上下文对象还允许您通过使用泄漏抽象,访问底层 graphql-java 特征:
This context object also allows you to fall down to the underlying graphql-java features by using the leaky abstraction:
DataFetchingEnvironment dfe = context.unwrap(DataFetchingEnvironment.class);
在生成 Schema 期间,您还可以访问底层 graphql-java
以直接添加自己的特征:
You can also get access to the underlying graphql-java
during schema generation, to add your own features directly:
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);
}
通过使用 @Observer
,您可以向 Schema 构建器添加任何内容。
By using the @Observer
you can add anything to the Schema builder.
为了让观察者正常工作,您需要启用事件。在 |
For the Observer to work, you need to enable events. In |
Adapting
Adapt to Scalar
另一项 SmallRye 特定的实验性功能,允许您将现有标量(由实现映射到特定 Java 类型),映射到另一种类型,或映射复杂对象,这通常会在 GraphQL 中创建 Type
或 Input
,到现有的标量。
Another SmallRye specific experimental feature, allows you to map an existing scalar (that is mapped by the implementation to a certain Java type) to another type,
or to map complex object, that would typically create a Type
or Input
in GraphQL, to an existing scalar.
Adapting an existing Scalar to another type:
public class Movie {
@AdaptToScalar(Scalar.Int.class)
Long idLongThatShouldChangeToInt;
// ....
}
以上将调整 Long
java 类型为 Int
标量类型,而不是 default BigInteger
。
Above will adapt the Long
java type to an Int
Scalar type, rather than the default BigInteger
.
Adapting a complex object to a Scalar type:
public class Person {
@AdaptToScalar(Scalar.String.class)
Phone phone;
// ....
}
这将映射到字符串标量,而不是在 GraphQL 中创建 Type
或 Input
。
This will, rather than creating a Type
or Input
in GraphQL, map to a String scalar.
为了能够执行上述操作, Phone
对象需要有一个使用 String 的构造器(或 Int
/ Date
/ 等),或为 String(或 Int
/ Date
/ 等)设置一个设置器方法,或有一个 fromString
(或 fromInt
/ fromDate
- 取决于标量类型)静态方法。
To be able to do the above, the Phone
object needs to have a constructor that takes a String (or Int
/ Date
/ etc.),
or have a setter method for the String (or Int
/ Date
/ etc.),
or have a fromString
(or fromInt
/ fromDate
- depending on the Scalar type) static method.
例如:
For example:
public class Phone {
private String number;
// Getters and setters....
public static Phone fromString(String number) {
Phone phone = new Phone();
phone.setNumber(number);
return phone;
}
}
请参阅 JavaDoc 中的 @AdaptToScalar
特征的更多信息。
See more about the @AdaptToScalar
feature in the JavaDoc.
Adapt with
对于更复杂的情况,另一个选项是提供适配器。之后,您便可以在适配器中自行进行映射。
Another option for more complex cases is to provide an Adapter. You can then do the mapping yourself in the adapter.
请参阅 AdaptWith
中有关 JavaDoc 功能的更多信息。
See more about the AdaptWith
feature in the JavaDoc.
例如:
For example:
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;
}
}
我们还支持 |
|
Built-in support for Maps
默认情况下,由于映射很难在架构中建模(因为键和值在运行时可能是动态的),GraphQL 不支持默认映射。使用上述调整,已为 Quarkus 添加 Map
支持,并将其映射到具有可选键参数的 Entry<Key,Value>
。这使您可以返回映射,也可以按键查询映射。
By default, due to the fact that maps are hard to model in a schema (as the keys and values can be dynamic at runtime) GraphQL does not support maps by default.
Using the above adaption, Map
support is added for Quarkus and are mapped to an Entry<Key,Value>
with an optional key parameter.
This allows you to return a map, and optionally query it by key.
示例:
Example:
@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
}
键和值对象可以是枚举、标量或复杂对象。 |
The key and value object can be any of Enum, Scalar or Complex object |
现在,您可以使用所有字段来查询整个映射:
You can now query the whole map with all the fields:
{
language{
key
value {
enName
iso6391
nativeName
please
thankyou
}
}
}
例如,这将返回如下结果:
This will return a result like this for example:
{
"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"
}
}
]
}
}
您还可以按键查询
You can also query by key
{
language (key:af){
value {
please
thankyou
}
}
}
这将只返回映射中的该值:
That will return only that value in the map:
{
"data": {
"language": [
{
"value": {
"please": "asseblief",
"thankyou": "dankie"
}
}
]
}
}
可以用我们自己的实现来覆盖默认映射适配器。 |
The default map adapter can to overridden with our own implementation. |
Error code
您可以在 GraphQL 响应中使用(SmallRye 特有的) @ErrorCode
在错误输出上添加错误代码:
You can add an error code on the error output in the GraphQL response by using the (SmallRye specific) @ErrorCode
:
@ErrorCode("some-business-error-code")
public class SomeBusinessException extends RuntimeException {
// ...
}
当出现 SomeBusinessException
时,错误输出将包含错误代码:
When SomeBusinessException
occurs, the error output will contain the Error code:
{
"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 | The error code |
Additional Notes
如果您正在使用 smallrye-graphql
扩展,并且存在且启用了 micrometer
指标扩展,则可能会遇到 java.lang.NoClassDefFoundError
,因为某些版本的 smallrye-graphql
扩展对 Microprofile Metrics API 有运行时要求。添加以下 MicroProfile Metrics API 依赖项以解决此问题:
If you are using the smallrye-graphql
extension and the micrometer
metrics extension is present and metrics are
enabled, you may encounter a java.lang.NoClassDefFoundError
as some versions of the smallrye-graphql
extension
have runtime requirements on the Microprofile Metrics API. Add the following MicroProfile Metrics API dependency
to resolve the issue:
<dependency>
<groupId>org.eclipse.microprofile.metrics</groupId>
<artifactId>microprofile-metrics-api</artifactId>
</dependency>
implementation("org.eclipse.microprofile.metrics:microprofile-metrics-api")
Conclusion
SmallRye GraphQL 使客户端能够检索所需的确切数据,从而避免 Over-fetching
和 Under-fetching
。
SmallRye GraphQL enables clients to retrieve the exact data that is
required preventing Over-fetching
and Under-fetching
.
可以对 GraphQL API 进行扩展,而不会中断以前的查询,从而可以轻松实现 API evolution
。
The GraphQL API can be expanded without breaking previous queries enabling easy
API evolution
.