tech-trends

Third Step - JEE Microservices with MicroProfile-Rest-Client

// 10-06-2020

What are the benefits of using MicroProfile-Rest-Client

MicroProfile-Rest-Client allows us to use type-safe REST clients, which are defined by a JAX-RS annotated interface and wrapped into a proxy, so they can be used like an ordinary Java Bean. The type safe REST clients avoid the boilerplate code of the native JAX-RS client api which is implemented in the proxy the type safe rest client is wrapped with. MicroProfile-Rest-Client same as MicroProfile-Config is not restricted to usage in microservices only but can also be used in ordinary WAR/EAR deployments running on any application server.

How to integrate MicroProfile-Rest-Client into an application

The MicroProfile-Rest-Client API and it’s implementations are supplied via Maven Dependencies, although these dependencies can be provided in different ways, depending on the used application runtime. The following sections explain how MicroProfile-Rest-Client is supplied by different application runtimes. MicroProfile-Rest-Client depends on MicroProfile-Config to provide the configurations for the REST clients; consequently MicroProfile-Config is a necessary dependency.

Integration with Wildfly/EAP

The cited dependencies have to be added in provided scope. No runtime dependencies are necessary, because Wildfly/EAP has MicroProfile capabilities already preinstalled as JBoss modules.

<dependency>
            <groupId>org.eclipse.microprofile.config</groupId>
            <artifactId>microprofile-rest-client-api</artifactId>
            <version>${version.microprofile.rest.client}</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.eclipse.microprofile.config</groupId>
            <artifactId>microprofile-config-api</artifactId>
            <version>${version.microprofile.config}</version>
            <scope>provided</scope>
        </dependency>

Integration with Thorntail Microservice

Thorntail provides a Maven Bill-of-Materials (bom) which includes  the Thorntail fraction for MicroProfile-Rest-Client and MicroProfile-Config.

<dependencyManagement>
                <dependencies>
                    <dependency>
                        <groupId>io.thorntail</groupId>
                        <artifactId>bom</artifactId>
                        <version>${version.thorntail}</version>
                        <scope>import</scope>
                        <type>pom</type>
                    </dependency>
                </dependencies>
            </dependencyManagement>

            <dependencies>
                <dependency>
                    <groupId>io.thorntail</groupId>
                    <artifactId>microprofile-config</artifactId>
                </dependency>
                <dependency>
                    <groupId>io.thorntail</groupId>
                    <artifactId>microprofile-restclient</artifactId>
                </dependency>
            </dependencies>

Integration with Quarkus Microservice

Quarkus, like Thorntail, provides a Maven-BOM which provides all necessary Extensions supported by the specific Quarkus version.

<dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>io.quarkus</groupId>
                <artifactId>quarkus-bom</artifactId>
                <version>${quarkus.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

   <dependencies>
     <!-- MicroProfile-Config is standard way of configuring 
          Quarkus, so there is no need for a dependency -->
          	<dependency>
               <groupId>io.quarkus</groupId>
               <artifactId>quarkus-rest-client</artifactId>
           </dependency>
   </dependencies>

How to implement a rest client

The type safe REST client is basically just a Java Interface marked with JAX-RS annotations as are used when defining server side REST resources. The Java Interface is a representation of the external REST resource you want to access. The rest client can be configured via MicroProfile-Rest-Client specific annotations, or via a RestClientBuilder.

The following snippets illustrates a simple rest client interface.

// Necessary when rest client shall be injectable and is 
// configured via MicroProfile-Rest-Client specific annotations
// ####################################################
@RegisterRestClient(configKey = "restClient")
@RegisterClientHeaders(ClientHeaders.class)
@RegisterProviders({
        @RegisterProvider(Provider.class)
})
// ####################################################
@Path("/")
public interface ExternalRestResource {

    @Path("/get")
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    String get();
}

The following list describes the MicroProfile-Rest-Client specific annotations used in the code snippet.

  • @RegisterRestClient
    Makes the REST client available via CDI and configures a custom configKey prefix for MicroProfile-Config provided configurations. Otherwise the fully qualified class name would be the prefix for the configuration keys.
  • @RegisterClientHeaders
    Registers implementations of the interface ClientHeadersFactory which allows to read incoming HTTP Headers and to define additional outgoing Http Headers.
  • @RegisterProviders
    Registers JAX-RS client providers and/or extensions. For instance implementations of the interface ResponseExceptionMapper<? Extends Throwable> which map HTTP error responses to Java exceptions.

The MicroProfile-Rest-Client configuration keys need to be defined in the form of <custom_or_fqn>/mp-rest/<config_key> and have to be provided by MicroProfile-Config. I don’t like the usage of slashes in the configuration keys, because it makes it impossible to define them in XML (e.g. pom.xml) and to use them in properties files which are used by the Openshift CLI command 

oc create config <name> –from-env-file=mp-rest.properties which creates Kubernetes ConfigMaps from the key value pairs defined in the properties file. The formerly cited command enforces parameters to only use ‘.’ or ‘_’ in their names.

How to implement advanced behaviours for a rest client?

Modify HttpHeaders

MicroProfile-Rest-Client provides an  interface – ClientHeadersFactory – which allows the setting of additional HTTP Headers on the outgoing request and reads the HTTP headers of the incoming request.

The following code snippets illustrates a simple ClientHeadersFactory implementation.

public class RestClientHeaderHandler implements ClientHeadersFactory {

    @Override
    public MultivaluedMap<String, String> 
       update(MultivaluedMap<String, String> incomingHeaders,
              MultivaluedMap<String, String> clientOutgoingHeaders) {
        
        // Will be merged with outgoing headers
        return new MultivaluedHashMap<>() {{
            put("X-Test", Collections.singletonList("test header"));
        }};
    }
}

 

This implementation has to be registered in the rest client via the @RegisterClientHeaders annotation.

<dependencyManagement>
  <dependencies>
       <dependency>
           <groupId>io.quarkus</groupId>
           <artifactId>quarkus-bom</artifactId>
           <version>${quarkus.version}</version>
           <type>pom</type>
           <scope>import</scope>
       </dependency>
   </dependencies>
  </dependencyManagement>

  <dependencies>
        <!-- Does not need a dependency defined. 
             It uses Smallrye implementation of 
             the MicroProfile-Config as default 
             for configurations -->
  </dependencies>

ExceptionMapping

MicroProfile-Rest-Client provides the interface ResponseExceptionMapper by the use of which it is possible to map a HTTP error response to a Java Exception which is thrown whenever the invocation of a method fails with an HTTP error. Otherwise the REST client would only throw a WebApplicationException without determining the actual occurring error. With exception mappers any exception can be thrown and then be handled by an interceptor or by a try-catch block.

public class RestClientExceptionMapper implements 
             ResponseExceptionMapper<RuntimeException> {

    @Override
    public RuntimeException toThrowable(Response response) {
        return new RuntimeException(“Error occurred ” 
                                    + response.getStatus());
    }
}

There are two additional methods handles(status, headers) and getPriority which are implemented in the interface.The method handles provides a choice on which state the implementation shall be used and getPriority provides an ordinal which defines the order of handlers if more than one handler is applicable for an error response.

MicroProfile-Rest-Client supports standard JAX-RS client functionalities as explained further in the docs.

How to provide the REST client instance to an application

MicroProfile-Rest-Client integrates well with CDI and makes the REST clients injectable. Alternatively the REST clients can be built via the RestClientBuilder.

CDI integration

The REST client interfaces need to be annotated with @RegisterRestClient which makes instances of these interfaces injectable into CDI Beans. No additional steps are necessary to make your rest client injectable. The rest client injection point needs to be qualified with @RestClient.

@RegisterRestClient
@Path("/")
public interface ExternalRestResource {
    ...
}

@RequestScoped
public class CDIBean {
    @Inject
    @RestClient
    ExternalRestResource externalRestResource;
}

RestClientBuilder

If dynamic configurations are needed, then the REST clients can be built via the RestClientBuilder. Additionally the REST client can be exposed to CDI via a custom CDI Producer method.

RestClientBuilder.newBuilder()
          .baseUri(new URI(baseUrl))
          .readTimeout(connectTimeout, TimeUnit.SECONDS)
          .connectTimeout(connectTimeout, TimeUnit.SECONDS)
          .register(ClientTracingFeature.class)
          .build(ExternalRestResource.class);

It is fairly simple to create a type safe REST client via the builder.

Sample Application

I have implemented a simple sample application which demonstrates the usage of MicroProfile-Rest-Client in a JEE application which is deployable as a microservice with Thorntail, Quarkus and as a WAR deployment in Wildfly.

Sample

Useful Links

  • Overview of all MicroProfile Projects
  • MicroProfile-Rest-Client Github repository
  • MicroProfile Rest-Client docs
  • Thorntail Home (Be aware that the doc is not updated regularly anymore)
  • Quarkus Home
  • Wildfly Home
  • Quarkus Github repository

// Autor

Thomas