Skip to content

Commit

Permalink
temp2
Browse files Browse the repository at this point in the history
  • Loading branch information
mskacelik committed Aug 28, 2024
1 parent 6302927 commit 10a1390
Show file tree
Hide file tree
Showing 16 changed files with 157 additions and 382 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
import org.jboss.as.server.deployment.annotation.CompositeIndex;
import org.jboss.as.server.deployment.module.ModuleDependency;
import org.jboss.as.server.deployment.module.ModuleSpecification;
import org.jboss.logging.Logger;
import org.jboss.modules.ModuleLoader;
import org.jboss.modules.Module;
import org.wildfly.extension.microprofile.graphql.client._private.MicroProfileGraphQLClientLogger;
Expand All @@ -44,7 +43,7 @@ public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitPro
final ModuleLoader moduleLoader = Module.getBootModuleLoader();
ModuleDependency dependency = new ModuleDependency(moduleLoader, "org.wildfly.extension.microprofile.graphql-client-smallrye", false, false, true, false);
// this is an equivalent of meta-inf="import" in jboss-deployment-structure.xml and is needed to be able to see CDI beans from the module
dependency.addImportFilter(s -> s.equals("META-INF"), true);
dependency.addImportFilter(s -> s.startsWith("META-INF"), true);
moduleSpecification.addSystemDependency(dependency);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
</resources>

<dependencies>
<module name="io.smallrye.graphql.client.api" export="true" services="import"/>
<module name="io.smallrye.graphql.client.api" export="true"/>
<module name="org.jboss.resteasy.resteasy-jaxrs"/>
<module name="jakarta.ws.rs.api"/>
<module name="org.slf4j"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

<dependencies>
<module name="io.smallrye.graphql.client.api" export="true"/>
<module name="io.smallrye.graphql.client.common" export="true" services="import"/>
<module name="io.smallrye.graphql.client.common" export="true" services="export"/>
<module name="org.slf4j"/>
<module name="org.eclipse.microprofile.config.api"/>
<module name="io.smallrye.config"/>
Expand Down
306 changes: 93 additions & 213 deletions quickstart-client/README.md
Original file line number Diff line number Diff line change
@@ -1,250 +1,130 @@
# GraphQL quickstart
# GraphQL Client quickstart

## Prerequisites
- JDK 1.8+
- Maven

## Quickstart's compatibility with server-side quickstart
This quickstart requires the server-side quickstart implementation to function properly. Instructions for deploying the feature pack on the server-side can be found in the server-side quickstart's [README](../quickstart/README.md).

## Building and deploying the quickstart

## Building and deployment

The [main README](../README.md) contains information about the layers in this feature pack. You can use the `wildfly-maven-plugin` to build and run the server with the feature pack, and deploy the quickstart war.

```
mvn wildfly:provision wildfly:dev
```

## Check the GraphQL schema
To view the schema, execute this command:
```
curl localhost:8080/quickstart/graphql/schema.graphql
```

## Experimenting with queries using GraphiQL
GraphiQL is a UI tool that can be used to execute GraphQL queries. Open GraphiQL by navigating to
`http://localhost:8080/quickstart/graphql-ui` in your browser. Queries that you should try out are listed below.
You will probably also notice that GraphiQL, based on the knowledge of the GraphQL schema, offers autocompletion!

### Queries
Enter the following query to GraphiQL and press the play button:

```
query allFilms {
allFilms {
title
director
releaseDate
episodeID
}
}
```

Note: The equivalent query can be executed via curl by
```
curl -X POST localhost:8080/quickstart/graphql -d'{"query": "{allFilms{title director releaseDate episodeID}}"}'
```

## Functionality
The quickstart-client.war application exposes various REST endpoints that demonstrate interacting with the server-side GraphQL API through the SmallRye GraphQL Client.
The endpoints utilize both typesafe and dynamic client approaches.
### Typesafe 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.
The typesafe client functions like a MicroProfile REST Client but is tailored for GraphQL endpoints. To use it, you will need domain model classes that match the GraphQL schema.

Let’s assume that our client only requires `title` and `releaseDate` making the previous call to the API
Over-fetching of unnecessary data.
Think of a client instance as a proxy. You interact with it as you would a regular Java object, and it translates your calls into GraphQL operations.

Enter the following query into GraphiQL and hit the play button:
It directly handles domain classes, translating input and output data between Java objects and their representations in the GraphQL query language.
#### Example

```
query allFilms {
allFilms {
title
releaseDate
}
}
```
Let's start by defining a domain class `Film` to represent the data we will be working with.
```java
package org.wildfly.extras.quickstart.microprofile.graphql.client;

Notice in the response we have only retrieved the required fields. Therefore, we have prevented Over-fetching.
import java.time.LocalDate;

Let's now try out a way how to retrieve one film my its id. It's implemented by this query:
public class Film {
String title;
Integer episodeID;
String director;
LocalDate releaseDate;
String desc;
}
```
@Query
@Description("Get a Films from a galaxy far far away")
public Film getFilm(@Name("filmId") int id) {
return service.getFilm(id);
}
Next, create a GraphQL client API interface `FilmClientApi` to define the GraphQL operations:
```java
@GraphQLClientApi
interface FilmClientApi {
@Query
List<Film> getAllFilms();
}
```

Enter the following into GraphiQL and make a request.

Finally, implement a REST endpoint to expose the GraphQL client's functionality:
```java
@GET
@Path("/typesafe/films")
@Produces(MediaType.APPLICATION_JSON)
public List<Film> getAllFilms() {
FilmClientApi client = TypesafeGraphQLClientBuilder.newBuilder()
.endpoint(url) // http://localhost:8080/quickstart/graphql
.build(FilmClientApi.class);
return client.getAllFilms();
}
```
query getFilm {
film(filmId: 1) {
title
director
releaseDate
episodeID
}
}
You can test the endpoint using a tool like curl:
```

The film query method requested fields can be determined as such in our previous example.
This way we can retrieve individual film information.

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.

In GraphQL it is possible to make multiple queries at once.

Enter the following into GraphiQL to retrieve two films:

```
query getFilms {
film0: film(filmId: 0) {
title
director
releaseDate
episodeID
}
film1: film(filmId: 1) {
title
director
releaseDate
episodeID
}
}
curl localhost:8080/quickstart-client/typesafe/films
```

This enabled the client to fetch the required data in a single request.

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.

Review the following source-query in our `FilmResource` class:

The typesafe client will automatically generate the corresponding GraphQL query based on the operation's return type and domain classes:
```
public List<Hero> heroes(@Source Film film) {
return service.getHeroesByFilm(film);
query allFilms {
allFilms {
title
pisodeID
director
releaseDate
desc
}
}
```
Enter the following into GraphiQL to retrieve the film and hero data.

```
query getFilmHeroes {
film(filmId: 1) {
title
director
releaseDate
episodeID
heroes {
name
height
mass
darkSide
lightSaber
The proxy will then send this query as part of the GraphQL request to the server. The server will process the request and return a GraphQL response.
The response will be deserialized into Java objects.

Other endpoints:
- **`localhost:8080/typesafe/films/{id}`:** Retrieves a specific film by its index.
- **`localhost:8080/typesafe/delete/hero/{id}`:** Deletes a hero by its index.
- **`localhost:8080/typesafe/heroes/{surname}`:** Retrieves a list of heroes with a specific surname.

### Dynamic API
Unlike the typesafe API, the dynamic client does not require a client API interface or domain classes. It operates directly with abstract representations of GraphQL documents, constructed using a domain-specific language (DSL).
Exchanged objects are treated as abstract `JsonObject`, but can be converted to concrete model objects if necessary.
#### Example
```java
@GET
@Path("/dynamic/films/{id}")
@Produces(MediaType.APPLICATION_JSON)
public Film getFilmDynamic(@PathParam("id") int id) throws ExecutionException, InterruptedException {
try (VertxDynamicGraphQLClient dynamicGraphQLClient =
(VertxDynamicGraphQLClient) new VertxDynamicGraphQLClientBuilder()
.url(url)
.build()
) {

Variable filmId = var("filmId", nonNull(ScalarType.GQL_INT));

Document query = document(
operation("film", vars(filmId),
field("film", args(arg("filmId", filmId)),
field("title"),
field("episodeID"),
field("director"),
field("releaseDate"),
field("desc")
)
)
);
Response response = dynamicGraphQLClient.executeSync(query, Collections.singletonMap("filmId", id));
return response.getObject(Film.class, "film");
}
}
}
```

The response now includes the heroes of the film.

### Mutations

Mutations are used when data is created, updated or deleted.
Review the following mutations in our `FilmResource` class:

```
@Mutation
public Hero createHero(Hero hero) {
service.addHero(hero);
return hero;
}
@Mutation
public Hero deleteHero(int id) {
return service.deleteHero(id);
}
```

Enter the following into GraphiQL 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
}
}
```

By using this mutation we have created a Hero entity in our service.

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
}
}
You can test the endpoint using a tool like curl:
```

Similar to the `createHero` mutation method we also retrieve the name and surname of the hero
we have deleted which is defined in { }.

### 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.

Review the following snippet from the FilmResource class:

```
@Query
public List<Hero> getHeroesWithSurname(@DefaultValue("Skywalker") String surname) {
return service.getHeroesBySurname(surname);
}
```

By using the `@DefaultValue` annotation we have determined that the surname value
will be Skywalker when the parameter is not provided.

Test the following queries with GraphiQL:

curl localhost:8080/quickstart-client/dynamic/films/{id}
```
query heroWithDefaultSurname {
heroesWithSurname{
name
surname
lightSaber
}
}
query heroWithSurnames {
heroesWithSurname(surname: "Vader") {
name
surname
lightSaber
}
}
```

## Conclusion
MicroProfile GraphQL enables clients to retrieve the exact data that is required preventing
Over-fetching and Under-fetching.

The GraphQL API can be expanded without breaking previous queries enabling easy API evolution.



Expand Down
Loading

0 comments on commit 10a1390

Please sign in to comment.