You are on page 1of 8

BROUGHT TO YOU IN PARTNERSHIP WITH

Developing
Microservices With CONTENTS

öö MicroProfile Platform

Eclipse MicroProfile
ö ö Conclusion

WRITTEN BY KEN FINNIGAN


PRINCIPAL SOFTWARE ENGINEER AT RED HAT MIDDLEWARE

MicroProfile™ was formed in June 2016 as a collaboration between


Red Hat, IBM, Tomitribe, Payara, and the London Java Community to
bring microservices to Enterprise Java. By the end of 2016, it became
an Eclipse Foundation project where it officially became Eclipse Mi-
croProfile™, and is now part of the Eclipse Foundation's Cloud Native
Java portfolio.

Since its inception, Eclipse MicroProfile has focused on rapid inno-


vation with a consistent and regular release cadence. The pace of
innovation can be seen in the eight platform releases since 2016 and Jakarta EE Developer Surevy results
will grow with a regular cadence of three releases a year. Rapid inno-
vation enables MicroProfile to move quickly to meet the needs of the
community, instead of waiting years for a specification to be released.

In three short years, MicroProfile has grown to include eight individual


specifications that are part of the platform releases, and three addi-
tional specifications that are currently standalone. Each specification
is able to evolve independently at its own pace, while combining into
a cohesive platform for microservice development.

In the short time MicroProfile has existed, we've already seen great
adoption within the community, which continues to grow. This year's
Jakarta EE Developer survey showed a huge increase in adoption since
last year:

1
Discover how YOU can build
Cloud Native Microservices
using Eclipse MicroProfile

Get the free white paper now!

DOWNLOAD
http://bit.ly/TechWPDzone19
DEVELOPING MICROSERVICES WITH ECLIPSE MICROPROFILE

MicroProfile Platform be created when needed. You can also choose to inject a configura-
What exactly is the MicroProfile release? It combines specifications tion into an Optional type, which handles the problem of having no
which the community considers necessary for developing microser- configuration value in a more elegant manner in cases where there is
vices with Enterprise Java. The diagram below lists the specifications no default value defined.
that are part of the platform, along with the versions for the most
HEALTH
recent 3.0 platform release in June 2019:
With the rise of Kubernetes and containers, it has become important
for microservices to define what it means for them to be functioning
and able to accept requests. In the Kubernetes world, this function-
ality is referred to as liveness and readiness, where liveness indicates
whether the runtime has failed and needs to be restarted, and read-
iness tells the container whether requests can start flowing into the
microservice for processing.

With MicroProfile Health 2.0 released in June, as part of the platform


3.0 release, the specification was altered to have separate means of
identifying a liveness vs. a readiness check. This change allows devel-
In this RefCard, we will briefly cover each of the above specifications
opers to have greater flexibility about where a particular check might
and how they assist in developing microservices for Enterprise Java.
fit in in order to answer questions about being live and ready.

CONFIG
Defining checks doesn't do much for us if there isn't a way to see the
Configuring a microservice, or application, is a foundational ability
results. The MicroProfile Health specification defines three endpoints
that we, as developers, need for our code. Whether it's the simple
that provide the results of our checks, each with an overall "status" in-
ability to define specific ports and database configurations for differ-
dicating the combined "up" or "down" status from each check. There
ent environments, or we're actually configuring how a microservice
is an endpoint for each check type, /health/liveness and /health/
behaves, we need a means by which we can retrieve configuration
readiness , and there is /healt h, which combines the liveness and
values for use within a microservice.
readiness checks.
The MicroProfile Config specification offers that ability, but also
Whether we want a liveness or readiness check, both must utilize the
enables us to define different types of ConfigSource . A Config-
HealthCheck interface to create the check. We use either @Liveness or @
Source specifically handles retrieving configuration values from a
Readiness to indicate the type of check we need. A liveness check that
particular location, or type of storage. By default, an implementation
always returns "up," not a real production check, can be written as:
of MicroProfile Config must include a ConfigSource for retrieving
configuration from system properties and environment variables. @ApplicationScoped

However, there are many other ConfigSource implementations that @Liveness


public class LivenessCheck implements HealthCheck {
are available for ZooKeeper, directories, Kubernetes ConfigMaps, etc.
@Override
public HealthCheckResponse call() {
Retrieving a configuration value only requires a single annotation to
return HealthCheckResponse.named("liveness").
indicate the name of the key, and whether you want to have a default
up().build();
value set if one is not found under the key: }
}
@Inject
@ConfigProperty(name = "app.mykey", defaultValue="defa
Note that we've given it a name, "liveness," as each check we create
ult")
needs a unique name by which it can be identified. If we wanted the
private String myValue;
check to be "down," it would call down() instead of up() . It's also
A value for the above configuration property can then be set in micro- possible to create multiple checks in a single class by using CDI Pro-
profile-config.properties , as shown below: ducers to create the checks. As an example, the above check could be
rewritten as a method:
app.mykey=myValue

@Produces
An implementation of MicroProfile Config also provides built-in con- @ApplicationScoped
verters for all primitive and array types, but a custom converter can @Liveness

Code continued on next Page

3 BROUGHT TO YOU IN PARTNERSHIP WITH


DEVELOPING MICROSERVICES WITH ECLIPSE MICROPROFILE

HealthCheck livenessCheck() { • @Gauge - Samples the value of the object, such as the instance
return HealthCheckResponse.named("liveness").up(). returned from a method.
build();
• @Metered - Tracks the frequency of invocations of what is
}
annotated.
A health check can also include a set of key/value pair data that • @Timed - Timer that tracks many statistics about invocations,

can be returned in the JSON response to provide additional con- such as minimum and maximum execution, average, standard
textual information about why the check failed or any pertinent deviation, etc.
information for operations. We can see an example of that in the The annotations can be added to a class, except for @Gauge , applied
readiness check below: to all methods or to specific methods of a class. It's also possible to

@ApplicationScoped inject or produce metrics by using @Metric , making it possible to read


@Readiness the value of a @ConcurrentGauge to reduce the number of calls being
public class ReadinessCheck implements HealthCheck { executed concurrently, or for doing any other custom processing
Override
based on available metrics data.
public HealthCheckResponse call() {
return HealthCheckResponse.named("readiness")
Let's take a look at a short example of how we can define our own
.withData("key", "value")
metrics:
.down().build();
}
@Timed
}
@Counted(name = "counted")
public String greeting() {
With all the above checks defined, /health would return the follow-
return "Hello World";
ing JSON: }

{
Above is a very simple method we've annotated with @Timed and @
"status": "DOWN",
Counted . There are attributes available to customize various parts of
"checks": [
{ the metric, but, in this case, we've gone with the defaults. The only at-
"name": "liveness", tribute we change is the name of one of the metrics, otherwise we get
"status": "UP" a clash because the generated metric name uses the method name.
},
{ With MicroProfile Metrics present within a microservice, the /metrics
"name": "readiness",
endpoint returns all the current metrics that are capturing data from
"status": "DOWN",
the microservice. The returned data will either be in JSON or Open-
"data": {
"key": "value" Metrics text format, depending on any HTTP Accept header that may
} have been passed. Unless JSON is specifically requested, /metrics
} will typically return OpenMetrics text formatted metrics.
]
} We've already talked about microservice metrics that we can add
to our code, but there are also many base metrics that a runtime
There is no limit as to what you could do within a health check, but
is required to provide as well. Base metrics include memory usage,
good practice is to not execute calls or tasks that can take a long time
threads, available CPUs, etc. A full list can be found here: github.com/
to complete unless it's absolutely necessary. Having checks that take
eclipse/microprofile-metrics/blob/2.0.1/spec/src/main/asciidoc/re-
a while to complete, especially for liveness checks, can result in con-
quired-metrics.adoc
tainer environments reaching a timeout and restarting your microser-
vice because it thinks the container has crashed. In addition, it's possible for a runtime to define a set of vendor met-
rics that can provide telemetry on the runtime that can be used within
METRICS
MicroProfile Metrics offers the following annotations for capturing operations. Depending on the runtime, it may even make sense to tie

metrics in a microservice: a health check to Metrics in order to determine its health.

• @Counted - Counts the number of invocations of what is annotated.


As mentioned previously, /metrics returns all the metrics being

• @ConcurrentGauge - Measures the current number of concur-


captured in the microservice. How do we split that out if the list is very

rent, or parallel, invocations. long? Each of the different types of metrics available, base, vendor, or

4 BROUGHT TO YOU IN PARTNERSHIP WITH


DEVELOPING MICROSERVICES WITH ECLIPSE MICROPROFILE

application, can be accessed by a direct endpoint to retrieve only that content = @Content(mediaType = "application/json",

subset of metrics. The endpoints are /metrics/base , /metrics/ven- schema = @Schema(implementation = User.
class))),
dor , and /metrics/application .
@APIResponse(responseCode = "400", description = "User
not found")
OPENTRACING
public Response getUserByName(
MicroProfile OpenTracing doesn't have much of an API, only @Traced ,
@Parameter(description = "The name that needs to
but it does a lot behind the scenes for our microservice without us be fetched. Use user1
needing to worry about it. For instance, when used with the Micro- for testing. ", required = true) @PathParam("username")
Profile REST Client, it will notice if you have an incoming span on a String
username)
request and create a child span to propagate in outbound REST Client
{...}
calls, setting the necessary HTTP headers for you. This enables a trac-
ing implementation to properly show child calls, without developers The above code generates an OpenAPI document segment that
needing to add any code for adding headers or propagating spans. looks like:

By default, when MicroProfile OpenTracing is part of a microservice, /user/{username}:

every JAX-RS resource method is automatically traced for us without get:


summary: Get user by user name
needing to specify it. There may be some instances in which you want
operationId: getUserByName
to prevent a method from being traced, or you'd like to alter the name
parameters:
of the method that is displayed in the traces. For this, we can add @ - name: username
Traced onto a class or method to customize the behavior we desire. in: path
description: 'The name that needs to be
OPENAPI fetched. Use user1 for testing.'
MicroProfile OpenAPI was originally based on the Swagger project, required: true
schema:
and subsequently OpenAPI, to provide a way to document REST
type: string
endpoints within a microservice. It can generate an OpenAPI v3 docu-
responses:
ment from annotations on REST endpoints, a pre-generated OpenAPI default:
document, or a combination of the two. The last option is particularly description: The user
beneficial when you don't want to define server URLs and other items content:
application/json:
that can change between environments into the annotations directly.
schema:
$ref: '#/components/
The generated, or provided, OpenAPI document is accessible at the
schemas/User'
/openapi endpoint of the microservice. By default, the OpenAPI
400:
document is returned in YAML format, but you could also retrieve it in description: User not found
JSON by passing the necessary Accept header.
REST CLIENT
Though a basic OpenAPI document can be generated from the JAX-RS
If you've used the JAX-RS Client previously in your Enterprise Java
annotations on resource methods, it's likely necessary to customize
projects, you might be wondering what the MicroProfile REST Client
and complement that information with additional details. MicroPro-
is and why it's needed. It takes the concepts defined by the JAX-RS
file OpenAPI provides a series of annotations to add this additional
Client and makes it usable through type safe means in your code.
information, such as @Callback , @APIResponse , @RequestBody , etc.
The full list of annotations can be found in the MicroProfile OpenAPI So, what does that actually mean? It allows a developer to define an
specification: github.com/eclipse/microprofile-open-api/blob/2.0/ interface that represents an external service that we need to commu-
spec/src/main/asciidoc/microprofile-openapi-spec.adoc#quick-over- nicate with, just as if we were developing that service ourselves with
view-of-annotations JAX-RS annotations. Below is an example of an interface that defines
the API of an external service:
Let's look at a short example, with the following JAX-RS endpoint and
@RegisterRestClient
OpenAPI annotations:
@Path("/")
@GET public interface NameService {
@Path("/{username}") @GET
@Operation(summary = "Get user by user name") @Path("name")
@APIResponse(description = "The user", @Produces(MediaType.APPLICATION_JSON)

Code continued on next column Code continued on next Page

5 BROUGHT TO YOU IN PARTNERSHIP WITH


DEVELOPING MICROSERVICES WITH ECLIPSE MICROPROFILE

Name name(); Response sentPUTviaPOST(MyEntity entity);


}
@POST
Other than the presence of @RegisterRestClient , and the fact that @ClientHeaderParam(name = "X-Request-ID", value =
it's an interface instead of a class, all the JAX-RS annotations can be "{generateRequestId}")

used to define a service we want to expose instead of one we want to Response postWithRequestId(MyEntity entity);

call. We could also have defined the interface without @RegisterRes-


@GET
tClient , but including it means we've registered our need for there to
@ClientHeaderParam(name = "CustomHeader",
be a CDI bean created that represents this service.
value = "{some.pkg.MyHeaderGenerator.
generateCustomHeader}",
In defining the above interface, and specifying we want a CDI bean,
required = false)
we can then inject an instance of that service and use it: Response getWithoutCustomHeader();
default String generateRequestId() {
@Inject
return UUID.randomUUID().toString();
@RestClient
}
NameService nameService;
}
...
public class MyHeaderGenerator {
nameService.name()
public static String generateCustomHeader(String

When we call nameService.name() , the runtime performs the headerName) {


if ("CustomHeader".equals(headerName)) {
necessary HTTP calls for us, hiding the complexity from our business
throw UnsupportedOperationException();
methods while giving us type safety in what we're calling.
}
return "SomeValue";
That's great, but how do I tell the runtime where this service lives?
}
There are two ways we can do that; the easiest is to add it to the
}
interface with @RegisterRestClient(baseUri="http://local-
host:8200") . There might be times when that is ok, but most of the FAULT TOLERANCE
time we don't want hard-coded URIs. The second way to configure it MicroProfile Fault Tolerance offers the following annotations:
is to define a configuration property in microprofile-config.prop-
• @Timeout - Indicates the maximum amount of time a method
erties such as my.package.NameService/mp-rest/url=http://
should execute, and if the method takes longer to fail the
localhost:8200 .
execution.
It's also possible to utilize annotations from MicroProfile Fault Toler-
• @Fallback - Indicates a fallback method or class to be used
ance on the interface, saving us from having to create wrapper meth-
if the execution fails exceptionally after other Fault Tolerance
ods to call the interface, just to be able to add the annotations onto it.
processing has completed.

Another great feature is the ability to propagate headers from incom- • @Retry - Ability to retry an operation a number of times, with
ing to outgoing requests, which is especially useful when passing delays, to account for brief network errors.
JWTs or other security headers. All we need to do is add @Register-
• @CircuitBreaker - Prevents repeated failures by utilizing a
ClientHeaders onto the interface and then set values on the org.
fail-fast approach to allow a downstream service to recover.
eclipse.microprofile.rest.client.propagateHeaders config
• @Builkhead - Defines the maximum number of concurrent
property to indicate which headers we want propagated.
requests that can be processed by a method, and the depth of
If we need custom headers to be set on specific methods only, we can the waiting queue.
use @ClientHeaderParam to set specific values or call methods to
• @Asynchronous - Forces execution of a client request to occur
generate a value for the header. Here are a couple of examples:
on a separate thread.

@Path("/somePath")
Many of the above annotations can be combined to provide enhanced
public interface MyClient {
@POST
behavior, such as @Retry and @Fallback to retry an operation several
times before triggering a fallback response if all retries failed. The
@ClientHeaderParam(name = "X-Http-Method-Override", below example shows what it would look like, where nameService is a
value = "PUT") REST client we're using to call an external service:
Code continued on next column

6 BROUGHT TO YOU IN PARTNERSHIP WITH


DEVELOPING MICROSERVICES WITH ECLIPSE MICROPROFILE

@Retry(maxRetries = 2, maxDuration = 1000, delay = the JAX-RS security context, we can directly inject the token itself, or
200) specific claims within the token, into our microservice:
@Fallback(fallbackMethod = "greetingFailure")
public Greeting greeting() { @Inject
return new Greeting("Hello " + nameService.name(). @Claim("roles")
getName()); private JsonArray jsonRoles;
}
private Greeting greetingFailure() { @Inject
return new Greeting("Hello from Fallback"); @Claim(standard = Claims.rawToken)
} private String rawToken;

For @Retry , we've indicated we only want to make two attempts, with @Inject
private JsonWebToken jwt;
a delay of 200 milliseconds between each retry, but we want to fail
the execution if the total time of all retries reaches 1000 milliseconds.
REACTIVE MESSAGING
With @Fallback , we've chosen to use a method instead of a class,
Developing reactive microservices, and by extension reactive systems,
where the most important piece to note is that the return type of our
has gained a lot of traction over the last few years, which is why
fallback needs to match that of the method, in this case, Greeting .
MicroProfile Reactive Messaging will be a key piece for MicroProfile in

There are many different ways that the annotations from MicroProfile supporting the reactive world. Although it only recently had its first

Fault Tolerance can be combined to provide the specific handling you release, the specification was under discussion and development for

might require in your microservice. Unfortunately, it's not possible to a year before the release.

cover all of them in this Refcard.


Reactive Messaging provides a way to connect event-driven microser-

JWT AUTHENTICATION
vices together. The focus of the specification defines a development
Sure, we can create our microservices without any security, but that's model for producing, consuming, and processing messages. We do
not a realistic design choice for most environments. That's where that by utilizing a combination of @Incoming and @Outgoing on a
MicroProfile JWT Authentication comes into play. JWT Authentication method. If we only produce events, then use @Outgoing ; if we only
provides a way for security tokens to be passed into and between mi- consume events, then use @Incoming ; if we process events from one
croservices for securing resources with authentication and authoriza- channel to another, we use both. Below is an example of receiving
tion. Early on, it was decided it made sense to converge on using JWTs events from "orders" and adding a new event into a "queue":
due to these tokens being an industry standard, but the specification
@Incoming("orders")
also allows existing Enterprise Java security annotations to be used in @Outgoing("queue")
conjunction with JWT. public CompletionStage<String> prepare(String message)
{
For instance, all the JSR 250 security annotations, such as @Role- Order order = jsonb.fromJson(message, Order.class);
sAllowed and @PermitAll , can be used to secure JAX-RS endpoints return makeIt(order)

in a microservice. The MicroProfile JWT Authentication specification .thenApply(beverage -> PreparationState.


ready(order, beverage));
defines the JWT so the claims can easily be retrieved from it, irre-
}
spective of what created the token or how many different runtimes it
might pass through first. What makes using it even easier is that you From our microservice's perspective, we don't particularly care where
can pass the JAX-RS SecurityContext into your JAX-RS endpoint to or what type of channels we might be consuming from and producing
retrieve the user principal from the token. Here's an example: to, we just need a way to identify which channels we're dealing with.
This approach allows the developer to not worry about being tied to
@GET
a particular type of channel during initial development, but how does
@Path("name")
@Produces(MediaType.APPLICATION_JSON)
that work?
public Name name(@Context SecurityContext context) {
The specification defines extension points called "connectors" that
Principal caller = context.getUserPrincipal();
return caller != null ? new Name(caller.getName())
bridge the communication to a specific transport and the channel
: new Name(name); definitions. There's no limit to the different types of connectors that
} could be created. As an example, the SmallRye implementation of Re-
active Messaging currently offers connectors for AMQP, Camel, Kafka,
In addition to the JWT token being automatically associated with
Cloud Events, MQTT, HTTP, and the Eclipse Vert.x Event Bus.

7 BROUGHT TO YOU IN PARTNERSHIP WITH


DEVELOPING MICROSERVICES WITH ECLIPSE MICROPROFILE

The connectors are easily switched by changing a Maven dependency • To learn more, visit the Eclipse MicroProfile website which
and a configuration property to indicate which connector should be includes a blog, FAQs, and other useful information.
used for a specific channel. This approach offers developers greater
• The Eclipse MicroProfile project page offers more technically
flexibility in switching between different connectors for different
oriented information on the project, its governance, specifica-
environments.
tions, and links to code repositories.

Conclusion • To join in the discussion, check out the MicroProfile discus-


We've taken a high-level look through most of the currently released sion group.
Eclipse MicroProfile specifications, but there are many more feature
'Eclipse MicroProfile' is a trademark of the Eclipse Foundation, Inc.
details that there simply wasn't the space to cover appropriately.
Additional information can be found in the specification documents
themselves or in the many examples that are available on the Internet.

Eclipse MicroProfile has a variety of specifications for solving different


use cases when developing Enterprise Java-based microservices,
enabling the developer to focus on creating business logic and not the
complexities of distributed systems.

There are several ways to learn more about Eclipse MicroProfile and get
involved in driving the evolution of Enterprise Java-based microservices:

Written by Ken Finnigan, Principal Software Engineer at Red Hat Middleware


Ken is a Principal Software Engineer for Red Hat Middleware and has been a consultant and software engineer
for 20 years with enterprises throughout the world. Ken and Bob McWhirter lead the WildFly Swarm project,
which aims to bring together the worlds of microservices and Java EE. Ken has previously served as the project
lead for LiveOak, along with other JBoss projects. Ken is currently writing “Java Microservices in Action” and has
previously written two books, including “JBoss Weld CDI for Java Platform.”

Devada, Inc.
600 Park Offices Drive
Suite 150
Research Triangle Park, NC

888.678.0399 919.678.0300
DZone communities deliver over 6 million pages each month
to more than 3.3 million software developers, architects, Copyright © 2019 Devada, Inc. All rights reserved. No part of this
and decision makers. DZone offers something for everyone, publication may be reproduced, stored in a retrieval system, or
including news, tutorials, cheat sheets, research guides, fea- transmitted, in any form or by means electronic, mechanical,
ture articles, source code, and more. "DZone is a developer’s photocopying, or otherwise, without prior written permission of
dream," says PC Magazine. the publisher.

8 BROUGHT TO YOU IN PARTNERSHIP WITH

You might also like