An introduction to the Eclipse MicroProfile specification – part 2

This is the second part of my introduction to the Eclipse MicroProfile specification.

In the first part I give a general introduction to the Eclipse MicroProfile specification (version 2.1) and explain the MicroProfile components Config, Metrics, Fault Toleration and Health Check.

In this part I explain the components OpenAPI, Open Tracing, JWT Propagation and REST Client followed by an overview of the changes in MicroProfile version 2.2 and version 3.0.

OpenAPI

MicroProfile OpenAPI provides a unified Java API for the OpenAPI specification (previously known as Swagger). It offers various annotations, models and programming interfaces to generate documentation for the application and REST services.

A simple example for documenting the application is shown below

@ApplicationPath("")
@OpenAPIDefinition(
        info = @Info(title = "Hello MicroProfile application", 
        version = "1.0.0",
        contact = @Contact(
                name = "Gerry Noij",
                email = "gerry.noij@craftsmen.nl",
                url = "https://www.craftsmen.nl")
        ),
        servers = {
                @Server(url = "/hello-microprofile", description = "localhost")
        }
)
public class ApplicationConfig extends Application {
}

For example, with The OpenAPIDefinition, Info and Server annotations you can add title, version and contact information as well as the URL the application runs on.

And to document a service the Tag, Operation and APIResponse annotations can be used to add a tag, name, description and expected response code to the documentation of the service.

@ApplicationScoped
@Path("hello")
@Tag(name = "Hello World greet service", description = "Get a greeting with a config property")
public class HelloWorldResource {

    @Inject
    @ConfigProperty(name = "name", defaultValue = "World")
    private String name;

    @GET
    @Operation(description = "Get a greeting")
    @APIResponse(responseCode = "200", description = "Successful, returning the greeting")
    @Produces(MediaType.TEXT_PLAIN)
    public Response greet() {
        return Response.ok("Hello " + name + "!").build();
    }
}

The documentation is exposed at the /openapi  endpoint in YAML format. To make it more readable there are various tools available, OpenAPI GUI or Swagger UI to name a few. You can also test the microservices with these.

The sample code (see the resources section) adds the Swagger UI to the application.

Open Tracing

If you have a lot of services, tracing and debugging an individual request as it travels through a distributed system can be difficult. The Open Tracing component can make it easier as it provides a standard API for instrumenting microservices for distributed tracing.

Distributed tracing helps troubleshoot microservices by logging requests as they propagate through a distributed system, which makes it easier to examine these requests.

Open Tracing enables distributed tracing in microservices without adding any explicit distributed tracing code to the application. In a MicroProfile application tracing is enabled by default on all JAX-RS endpoints.

The /health , /openapi  and /metrics  endpoints are excluded from tracing.

You can customize tracing by using a Traced annotation which allows you to customize the operation name and to activate or deactivate the monitoring on any CDI bean or JAX-RS endpoint. Another way to add tracing to the application is to inject an io.opentracing.Tracer object to start tracing in custom operations.

A simple example of the Traced annotation is shown below.

@ApplicationScoped
@Path("hello")
public class HelloWorldResource {

    @Inject
    @ConfigProperty(name = "name", defaultValue = "World")
    private String name;

    @GET
    @Traced(value=true, operationName = "HelloWorldResource.greet")
    public Response greet() {
        return Response.ok("Hello " + name + "!").build();
    }
}

The output of the traces can be sent to either a log file or a Zipkin or Jaeger server, which require some vendor-specific configuration.

JWT Propagation

In a microservice architecture, services are usually stateless. Any security state associated with a client is sent to the target service on every request in order to allow services to re-create a security context for the caller, and perform both authentication and authorization checks.

This security state is contained in a JSON Web Token (JWT), a JSON-based text format for exchanging information. The information contained in a JWT token is called claims. JWT tokens can be signed to prevent tampering; when encrypted there is no clear text in the JWT.

The MicroProfile JWT Propagation component defines functionality so that the JWT token can be verified and propagated by each microservice. It explicitly does not define the creation of a JWT token.

This is left to a dedicated service in the enterprise or an identity provider (for example Keycloak).

To be able to verify a JWT token with JWT Propagation, the JWT token must be signed with an RSA-based digital signature. The public key of the Identity Provider, which creates the JWTs, can be installed on all the microservices in advance of any actual HTTP requests.

When calls come into a microservice, it will use this public key to verify the JWT and determine if the caller’s identity is valid. If any HTTP calls are made by the microservice, it should pass the JWT in those calls, propagating the caller’s identity forward to other microservices.

The verification of the JWT token is handled by the MicroProfile implementation of the vendor. When the token is verified, the application can use the MicroProfile JsonWebToken  interface or Claim  annotation to use the JWT data in the application.

For example, in the code snippet below the caller subject is obtained from the injected JsonWebToken  and returned as the result of the microservice.

@Path("/secure")
@RequestScoped
public class SecureResource {

    @Inject
    private JsonWebToken jwToken;

    @GET
    @RolesAllowed({"user", "admin"})
    public Response greet() {
        return Response.ok(jwToken.getSubject()).build();
    }
}
 

 

REST Client

In a microservice architecture, we typically talk REST to other services. While the JAX-RS specification defines a fluent API for making calls, it is difficult to make it a true type safe client. MicroProfile Rest Client API provides a type-safe approach to invoke RESTful services over HTTP in a consistent and easy-to-reuse fashion.

The Rest Client is a Java REST client based in JAX-RS 2.0.

There are two ways of using the REST Client. You can register a REST Client using the annotation RegisterRestClient on an interface representing the REST service and injecting this interface with the annotation RestClient in the application. Or you can use the fluent RestClientBuilder as shown below.

In this example we go back to the CircuitBreaker example from part 1, where there is a call to the backend, which we can implement with the RestClientBuilder. Assume the HelloWorldResource is the backend service we want to use, the interface representing this REST service looks like:

public interface HelloWorldResourceService {
    @GET
    String greet();
}

And the RestClientBuilder in the callToBackend method will look like:
public class CircuitBreakerResource {

    @Inject
    @ConfigProperty(name = "helloWorldUrl")
    private String helloWorldUrl;

    …

    private String callToBackend() {
            return RestClientBuilder.newBuilder()
                    .baseUri(new URI(helloWorldUrl))
                    .build(HelloWorldResourceService.class).greet();
}

The URL to the backend service is injected through the MicroProfile Config component.

Changes in MicroProfile 2.2 and 3.0.

So far we have covered all components of the Eclipse MicroProfile specification 2.1. Version 2.2 of the specification adds a few minor changes to the Fault Tolerance, OpenAPI, Open Tracing, and Rest Client components.

MicroProfile3.0

Version 3.0, released on June 11, is a new major release (but still depending on Jakarta/Java EE 8). It contains new major versions of the components Metrics and Health Check, which will break backwards compatibility with the versions used in MicroProfile version 2.2.

There is also a new stand-alone MicroProfile component, Reactive Stream Operators. It defines an API that uses Reactive Streams. These allow two different libraries that provide asynchronous streaming to be able to stream data to and from each other.

There are implementations of this API available from Akka Streams, Zero Dependency and SmallRye.

—————————————-

This concludes this two-part introduction to the Eclipse MicroProfile specification. In this introduction we covered the MicroProfile components Config, Metrics, Fault Tolerance, Health Check, OpenAPI, Open Tracing, JWT Propagation and REST Client and explained them with simple code examples. The sources of these examples are available on my Github.

If you would like to get in touch with me or my colleagues for further information, a workshop or a project, please know that you are most welcome to contact us.

Resources

Sample code:

https://github.com/Misano9699/hello-microprofile

Other resources:

https://microprofile.io
https://github.com/microprofile-extensions/openapi-ext

Leave a Reply

Your email address will not be published. Required fields are marked *