Skip to content

Latest commit

 

History

History
 
 

api

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 

SmallRye GraphQL Client

A Java code-first type-safe GraphQL Client API suggestion for [Microprofile GraphQL](microprofile/microprofile-graphql#185).

Basic Usage:

@GraphQlClientApi
interface SuperHeroesApi {
    List<SuperHero> allHeroesIn(String location); // (1)
}

class SuperHero {
    private String name;
    private List<String> superPowers;
}

class MyApplication {
    @Inject SuperHeroesApi superHeroesApi;

    List<SuperHero> allHeroes = superHeroesApi.allHeroesIn("Outer Space");
}
  1. The default request type is query. To make it a mutation, annotate it @Mutation. The parameter name is only available if you compile the source with the -parameters option. Otherwise, you’ll have to annotate all parameters with @Name.

The example above uses CDI, e.g. when you are in a MicroProfile or Jakarta EE environment. If you are in an environment without CDI support, you need to instantiate the API interface by using the builder:

SuperHeroesApi api = GraphQlClientBuilder.newBuilder().build(SuperHeroesApi.class);

The basic idea of the Java code-first approach is that you start by writing the DTOs and query/mutation methods as you need them in your client. This ensures that you don’t request fields that you don’t need; the thinking is inspired by Consumer Driven Contracts and may evolve more into that direction. If the server uses different names, use annotations to do a mapping:

Changing Names

If the server defines a different field or parameter name, annotate it with @Name.

If the server defines a different query name, annotate the method as, e.g., @Query("findHeroesCurrentlyLocatedIn").

By renaming methods, you can also define several variations of the same request but using different return types or parameters. E.g.:

public interface SuperHeroesApi {
    SuperHero findHeroByName(String name); // (1)

    @Query("findHeroByName")
    SuperHeroWithTeams findHeroWithTeamsByName(String name); // (2)
}
  1. The SuperHero class has no team affiliations (for this example).

  2. The SuperHeroWithTeams class has a private List<Team> teamAffiliations field. The Team class doesn’t contain the members to break recursion.

Configuration

If the endpoint is always the same, e.g. a public API of a cloud service, you can add the URL to your API annotation, e.g.:

@GraphQlClientApi(endpoint = "https://superheroes.org/graphql")
interface SuperHeroesApi {
}

When instantiating the API with the builder, you can set (or overwrite) the endpoint there:

SuperHeroesApi api = GraphQlClientBuilder.newBuilder()
    .endpoint("https://superheroes.org/graphql")
    .build(SuperHeroesApi.class);

Commonly you’ll need different endpoints, e.g. when you need one endpoint for your production system, but a different endpoint for your test system. Simply use MicroProfile Config to set the endpoint; similar to the MicroProfile Rest Client, the key for the endpoint is the fully qualified name of the api interface plus /mp-graphql/url, e.g.:

org.superheroes.SuperHeroesApi/mp-graphql/url=https://superheroes.org/graphql

If you want to use a different key, set the base config key on the annotation @GraphQlClientApi(configKey = "superheroes"); then use this key for the endpoint superheroes/mp-graphql/url.

When using the builder, you can override the config key there as well: GraphQlClientBuilder.newBuilder().configKey("superheroes").

Headers

To add a custom header to the http requests sent out by the GraphQL Client, annotate your method or the API interface as @Header, e.g.:

@GraphQlClientApi
interface SuperHeroesApi {
    @Header(name = "S.H.I.E.L.D.-Clearance", constant = "TOP-SECRET")
    List<SuperHero> allHeroesIn(String location);
}

The name is always fixed, but the value can also be the name of a method for dynamic values, e.g.:

@GraphQlClientApi
interface SuperHeroesApi {
    @Header(name = "S.H.I.E.L.D.-Clearance", method = "establishShieldClearance")
    List<SuperHero> allHeroesIn(String location);

    static Clearance establishShieldClearance() { // (1)
        return userIsInRole(MANAGER) ? TOP_SECRET : PUBLIC;
    }
}
  1. This value is an enum, but it can be any Object; the GraphQL client calls <code>toString</code> to convert it.

The method must be static and accessible by the interface, i.e. in the interface itself or in one of the classes it’s nested in; if it’s in a different class, prefix it with the fully qualified class name and a dot ".", e.g. @Header(name = "S.H.I.E.L.D.-Clearance", method = "org.superheroes.SecurityTools.establishShieldClearance").

In rare cases, you may want to pass the value of a header as a parameter:

@GraphQlClientApi
interface SuperHeroesApi {
    List<SuperHero> allHeroesIn(String location, @Header(name = "S.H.I.E.L.D.-Clearance") Clearance clearance);
}

A @Header parameter is not part of the GraphQL request.

@Header annotations can be defined via `@Stereotype`s.

AuthorizationHeaders

To add an Authorization header, instead of using the generic @Header annotation, you can also use the special @AuthorizationHeader annotation. It produces a BASIC Authorization header by default or a BEARER token. You can configure the credentials in MP Config with a prefix plus /mp-graphql/ and either username and password for BASIC or bearer for BEARER. The config key defaults to the fully qualified name of the GraphQlClientApi interface or its configKey.

You can use a custom prefix by setting the confPrefix. The infix /mp-graphql/ is still applied, unless you end the confPrefix with , e.g. @AuthorizationHeader(confPrefix = "org.superheroes.security.basic. will use org.superheroes.security.basic.username, while * will use plain username.

@AuthorizationHeader annotations can be defined via `@Stereotype`s.

Logging

The Client implementation logs all GraphQL requests and responses at level INFO with the interface API as the logger name. It also logs the keys of all headers added at level DEBUG; the values may be security sensitive.