Building Cloud Native Microservices? Which Programming Model Should I Use?

Why Cloud Native Microservices?

Cloud, visible or invisible, is everywhere. You might be able to guess which country I live in! In this article, I am talking about the invisible Cloud. There are three type of clouds: private, public and multi cloud! More and more companies are working towards application modernization so that they can create cloud native microservices in order to save cost and make the best use of cloud resources. When a microservice runs in the cloud, it only costs you money when it is running. Depending on the load, it can scale up or down. In this case, you just pay for what you use. There won't be idle costs. You don't need to worry about purchasing any hardware just for occasional use, Pay as You Go model.

In terms of application modernisation, there are different routes to achieve the goal. For existing monolith applications, it is time to think about how to deploy them in the Cloud. You can either containerise them or break them into microservices. For newly-written applications, the best choice is to build green-field cloud native microservices. This article focuses on how to develop the most efficient cloud native microservices that can function well in the cloud. Let's take a step back first and clarify what cloud native means.

What Is Cloud Native?

The first question that might come to your mind is: what does cloud native mean? Generally speaking, Cloud Native refers to an application that's built for the cloud. The cloud native microservices make best use of the cloud infrastructure and form a great ecosystem. They can be easily configured without being repackaged, secure, resilient, monitorable, traceable etc. Let's summarise the characteristics of cloud native microservices.

Externalise configuration

Cloud native microservices must be configurable. When changing configuration, the microservcies should not be repackaged. Build once, configure everywhere. This is also highly recommended by the 12-factor methodology.

RESTful

Cloud Native microservices must be stateless. The state should be stored in the database, not in the memory. The microservice itself should be treated as cattle, not pet. In this case, when a microservice container is not responding, the underline cloud infrastructure can replace it without losing anything.

Fault Tolerance

Cloud Native microservices should be resilient. It should function under whatever the situation is. For example, even if its downstream service is out of service.

Discoverable

Cloud Native microservices should be able to advertise itself for what function it provides or its capability so that clients can invoke the services.

Secure

Security is extremely important in cloud native microservices. In a monolith application, only a small portion of the functions was exposed while majority functions were hidden. When moving to cloud native, every single service has a unique identity. You will need to ensure that only authenticated and authorised users can invoke the relevant microservices. For instance, if you deploy a microservice of salary query, you might only want a caller with the access right of "Manager" and "Owner" access this service.

Monitorable

Once a cloud native microservice is running in the cloud, it is essential to emit some metrics so that DevOps can monitor it in order to find out how fast it responds and how much loads it is taking, etc.

Traceable

A cloud native microservice mostly needs to talk to other services, which then talks to different services. If you try to draw an invocation chain, you might end up a->b->d->f->e and so on. Once a service is not functioning, you need to figure out which one is faulty. Without the ability to be able to visualise the invocation chain, it will be very hard to identify the faulty service. In order to build the invocation chain, microservices need to be able to propagate the correlation id. With this, some tools such as ZipKin or Jaeger can build a trace span to illustrate each service status.

Ability to communicate with the Cloud

A cloud native microservice needs to be able to communicate with cloud infrastructure about its healthy status. It should be able to tell the infrastructure whether it is ready to receive the request or whether it is still alive. If it is not alive, cloud infrastructure, e.g. Kubernetes, can easily replace it. If it is not ready, e.g. its database connection is not ready, cloud infrastructure will not route requests to this service.

When you read up to this point, you might sigh. Let alone your business logic, there are so many QoS to worry about as well. If you take care of all of these, you might have to spend the majority of time worrying about the above requirements while spending less time doing something related to your business. It is too much. I hear you. Luckily, I am here to help. Read on.

How to Build Cloud Native Microservices

No need to build one from scratch. There are frameworks that can help you with that; the most popular frameworks for developing cloud native microservices are Eclipse MicroProfile and Spring framework.

Spring framework has been around for a few years; therefore, I won't talk too much about it. Eclipse MicroProfile is relatively new. It was established in mid-2016 by IBM, Red Hat, Payara, Tomitribe, LJC, and other individuals. Later on, other companies, e.g. Microsoft, Oracle, etc also joined the community. The aim of Eclipse MicroProfile is to evolve the best programming models for developing cloud native microservices. So far, it has more than eight releases, with MicroProfile 3.0 announced not long ago. It has gained a massive amount of attention lately. Most Java EE runtimes now supports MicroProfile, such as Open Liberty, Quarkus, Payara, TomEE, Helidon, KumuluzEE, etc. You can find the implementation details here.

Spring MicroProfile
REST APIs
REST Service Spring MVC JAX-RS
Dependency Injection Spring IoC & DI CDI
API Documentation Spring REST Docs MicroProfile Open API
REST Client Spring MVC Feign MicroProfile REST Client
JSON Binding/Processing Bring Your Own Library
Jackson
JSON-B
JSON-P
Handling 100s of Services
Configuration Spring Boot Config
Spring Cloud Config
MicroProfile Config
Fault Tolerance Netflix Hystrix MicroProfile Fault Tolerance
Security Spring Security
Spring Cloud Security
EE Security
MicroProfile JWT Propagation
Operation Focus
Health Checks Spring Boot Actuator MicroProfile Health Check
Metrics Spring Boot Actuator MicroProfile Metrics
Distributed Tracing Spring Cloud Sleuth MicroProfile Open Tracing

Let's discuss each category in more detail.

Develop Cloud Native Microservices

RESTful API is a well-adopted way to develop Cloud Native Microservices. Let's focus on how to use RESTful APIs and associated technologies to write your loosely-coupled microservices.

REST APIs

REST (Representational State Transfer) is an architectural style for defining communication standards between services, making it easier for systems to communicate with each other. Both MicroProfile and Spring support REST.

JAX-RS
MicroProfile uses JAX-RS from Java EE. In JAX-RS, you need to define an Application and JAX-RS resource(s). In the following example, the Application CatalogApplication and JAX-RS resources CatalogService were defined, detailed below.

ApplicationPath("/rest")
public class CatalogApplication extends Application {
}

@Path("/items")
@Produces(MediaType.APPLICATION_JSON)
public class CatalogService {..}
@GET
public List<Item> getInventory() {...}
@GET
@Path("{id}")
public Response getById(@PathParam("id") long id) {...}

In the above-mentioned example, an end point of http://${host}:${port}/rest/items will be exposed.

Refer to this Open Liberty to learn more about JAX-RS.

Spring
In Spring framework, you will need to create a SpringBootApplication and Controller. In the following example, Application and CatalogController were created accordingly.

@SpringBootApplication
public class Application {
public static void main(String[] args)
    SpringApplication.run(Application.class, args);}
}

@RestController
public class CatalogController {..}
@RequestMapping(value = "/items", method = RequestMethod.GET)
@ResponseBody
List<Item> getInventory() {..}

@RequestMapping(value = "/items/{id}", method = RequestMethod.GET)
ResponseEntity<?> getById(@PathVariable long id) {...}

In the above-mentioned example, an end point of http://${host}:${port}/rest/items will be exposed.

Dependency Injection

When designing cloud native microservices, the best practice is to create a loosely-coupled microservices. MicroProfile adopts Contexts and Dependency Injection (CDI) from Java EE while Spring uses Spring DI, IoC to achieve the same effect.

CDI
Below is how CDI can be used for dependency injection

@ApplicationPath("/rest")
public class JaxrsApplication extends Application {
@Inject
private InventoryRefreshTask refreshTask;

The above code snippet will inject an instance of InventoryRefreshTask to refreshTask.

CDI is the centre piece for both Java EE and MicroProfile. It is very important to understand CDI. Refer to this Open Liberty guide to learn some basics about CDI.

Spring DI and IoC

Spring uses Dependency Injection, Inversion of Control to achieve loosely coupling. The following code snippet illustrates how to use Spring DI via @Autowired. An instance of InventoryRefreshTask will be injected to the variable refreshTask. By the way, Spring also supports @Inject, which is equivalent to @Autowired.

@SpringBootApplication
public class Application {
@Autowired
private InventoryRefreshTask refreshTask;
...
}

Document APIs

Microservices need to advertise their capabilities so that potential clients can utilise their services. MicroProfile and Spring handle things differently when it comes to documenting APIs.

MicroProfile Open API

MicroProfile uses MicroProfile Open API to document APIs, which is based on Swagger API. In MicroProfile Open API, any JAX-RS resources are automatically opted in to have their APIs generated. It can also take open API yaml file with the file name of openapi.yaml or openapi.yml or openapi.json under META-INF folder. Below is an example of how to document API responses and Operations.

@GET
@Produces(MediaType.APPLICATION_JSON)
@APIResponse(
    responseCode = "200",
    description = "host:properties pairs stored in the inventory.",
    content = @Content(mediaType = "application/json",
        schema = @Schema(type = SchemaType.OBJECT,
        implementation = InventoryList.class)))
@Operation(summary = "List inventory contents.",
description = "Returns the stored host:properties pairs.")

public InventoryList listContents() {
return manager.list();
}

In the above-mentioned snippet, an endpoint http://${host.name}:${port}/openapi will be exposed with the following output.

openapi: 3.0.0
info:
    title: Inventory App
   description: App for storing JVM system properties of various hosts.
license:
    name: Eclipse Public License - v 1.0
    url: https://www.eclipse.org/legal/epl-v10.html
version: "1.0"
   servers: - url: http://localhost:{port} description: Simple Open Liberty.
variables:
   port:
     description: Server HTTP port.
     default: "9080"
paths:
    /inventory/systems:
   get:
        summary: List inventory contents.
       description: Returns the currently stored host:properties pairs in the inventory.
       operationId: listContents
       responses:
           200:
           description: host:properties pairs stored in the inventory.
           content:
               application/json:
                    schema:
                         $ref: '#/components/schemas/InventoryList'
... .

If you use Open Liberty, the endpoint http://${host.name}:${port.number}/openapi/ui will also be exposed, which allows the end user to directly invoke the individual endpoints.

If you are familiar with Swagger API, you will find this familiar.

Refer to this Open Liberty guide to learn more about MicroProfile Open API.

Spring Doc

Spring uses tests to document APIs and have the ability to generate API doc as part of test running. Here's how to generate a Spring doc.

1. Define the dependencies

<dependency>
<groupId>org.springframework.restdocs</groupId>
<artifactId>spring-restdocs-mockmvc</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.springframework.restdocs</groupId>
<artifactId>spring-restdocs-core</artifactId>
<scope>test</scope>
</dependency>

2. Define your Rest service

@RestController
public class CatalogController {
   @RequestMapping("/")
   public @ResponseBody String index() {
         return "Greetings from Catalog Service!";
   }
}

3. Define all necessary test classes

@RunWith(SpringRunner.class)
@SpringBootTest(classes = CatalogController.class)
@WebAppConfiguration
public class CatalogControllerTest {
@Rule public JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation("target/generated-snippets");
private MockMvc mockMvc;
@Autowired private WebApplicationContext context;
@Before public void setUp() {
    mockMvc = MockMvcBuilders.webAppContextSetup(context) .apply(documentationConfiguration(restDocumentation)) .build();
}

4. Use alwaysDo(), responseFileds(), requestPayload(), links(), fieldWithPath(), requestParameters(), pathParameters() to document

@Test
public void crudDeleteExample() throws Exception {
    this.mockMvc.perform(delete("/crud/{id}", 10)).andExpect(status().isOk())
      .andDo(document("crud-delete-example",
      pathParameters(
        parameterWithName("id").description("The id of the input to delete")
      )));
}

When running the tests, API docs will be generated.

Rest Client

Cloud native microservices are not standalone. The microservices interact with each other. One microservice invokes the second microservice, which then invokes the third microservice, and so on. Normally, it is kind of a spiderweb. For instance, in the scenario of microservice A calling microservice B, microservice A behaves as a client. How can you make connections from microservice A to microservice B? Rest client to rescue!

MicroProfile Rest Client

JAX-RS client can be used to make client server communication, detailed below.

Client client = ClientBuilder.newClient();
Response res = client.target("http://example.org/hello").
                       request("text/plain").get();

However, it is not a type safe client, hence it is prone to error. The invocation with the wrong parameters passed in results in a runtime error, which is too late.

MicroProfile Rest Client is a type safe Rest Client, which provides a much simpler way to make the client server communication. How does it work? Below are the steps.

Step 1: Register a rest client API

@Dependent
@RegisterRestClient(baseUri=http://localhost:9080/system)
@RegisterProvider(InventoryResponseExceptionMapper.class)
public interface InventoryServiceClient {
@GET
@Produces(MediaType.APPLICATION_JSON)
List<Item> getAllItems() throws UnknownUrlException,
ServiceNotReadyException;
}

Step 2: Inject the client API to the client microservice JAX-RS resource

@Inject
@RestClient
private InventoryServiceClient invClient;
final List<Item> allItems = invClient.getAllItems();

Step 3: Rebind the backend microservice

io.openliberty.guides.inventory.client.SystemClient/mp-rest/url=http://otherhost:8080/system

Using the fully qualified class name appended with /mp-rest/url as the key and the backend service endpoint as the value. When deploying this microservice in the cloud, the backend URL will be different from other environments. Normally, you will need to rebind the backend service in your client's deployment.yaml via Kubernetes ConfigMap.

Refer to this Open Liberty guide to learn more about MicroProfile Rest Client.

Spring

Spring uses a similar approach to MicroProfile Rest Client with the corresponding technologies such as FeignClient and Injection.

Step 1: Define the client

@FeignClient(name="inventory-service",
url="${inventoryService.url}")
public interface InventoryServiceClient {
@RequestMapping(method=RequestMethod.GET,
value="/micro/inventory", produces={MediaType.APPLICATION_JSON_VALUE})
      List<Item> getAllItems();
}

Step 2: Enable the client and inject the client

@EnableFeignClients
public class Application {
    @Autowired
    private InventoryServiceClient invClient;
final List<Item> allItems = invClient.getAllItems();

...
}

Payload on the wire - JSON

JSON format is the common media type on the wire. JSON-B and JSON-P are popular technologies to help with JSON media type.

JSON-P and JSON-B

MicroProfile 2.0 and onwards supports both JSON-B and JSON-P, which dramatically simplifies the serialization and deserialization of JSON objects. Below is an example of using JSON-B to serialize artists object.

public class car {
   private String make;
   private String model;
   private String reg;
   ...
}


import javax.json.bind.Jsonb;
import javax.json.bind.JsonbBuilder;
Car car = new Car("VW", "TGUAN", "HN19MDZ");
Jsonb jsonb = JsonbBuilder.create();
String json = jsonb.toJson(car);

The toJson () method returns the serialized car object.

{
  "make": "VW",
  "model": "TGUAN",
  "reg": "HN19MFZ"
}

Deserialization with JSON-B is equally simple.

Car car = Jsonb.fromJson(json, Car.class);

In order to transport JSON object on the wire, you simply define a POJO, e.g.

public class InventoryList {

private List<SystemData> systems;

public InventoryList(List<SystemData> systems) {
   this.systems = systems;
}

public List<SystemData> getSystems() {
  return systems;
}

public int getTotal() {
  return systems.size();
}
}

In the JAX-RS resource, you can directly return this type as a JSON object.
...

@GET
@Produces(MediaType.APPLICATION_JSON)
public InventoryList listContents() {
     return manager.list();
}

Refer to this article to learn more about JSON-B.

Spring

Spring can either directly use Jackson or JSON-B.

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
final ObjectMapper objMapper = new ObjectMapper();
jsonString = objMapper.writeValueAsString(car);

// or use JSON-B
import javax.json.bind.Jsonb;
import javax.json.bind.JsonbBuilder;
Jsonb jsonb = JsonbBuilder.create();
String result = jsonb.toJson(car);

Handling 100s Microservices

It is common to have 100s microservices in your Cloud infrastructure. When handling 100s services, you will need to monitor the services, configure the services, secure the services, etc.

Configure Microservices

Cloud Native microservices are configurable, so they can be updated by DevOps. Developers won't have to repackage the microservices just because of the configuration value changes. The design principle is that those configurations can be stored somewhere externally to the microservices and the configurations are made available to the microservices. This is called externalised configuration, one of the factors highlighted by 12-factor APP. Let's take a look at how MicroProfile and Spring help us configure microservices.

MicroProfile Config

MicroProfile Config enables externalising configurations by placing configuration values in config sources and then microservices can either use injection or programmatically lookup to get the corresponding configuration values.

Step 1: Specify the configuration in a config source, either as system properties, environment variables, microprofile-config.properties or custom config sources.

# Elasticsearch
elasticsearch_url=http://es-catalog-elasticsearch:9200
elasticsearch_index=micro
elasticsearch_doc_type=items

Step 2: Use either programming lookup or inject

Config config = ConfigProvider.getConfig();
private String url = config.getValue("elasticsearch_url",
String.class);

or

@Inject @ConfigProperty(name="elasticsearch_url") String url;

Refer to this Open Liberty guide to learn more about MicroProfile Config.

Let's see how to do the same thing with Spring framework.

Spring config

You can use Spring config to achieve the configuration externalisation by using the following steps.

Step 1: Define config in a config source

# Elasticsearch
elasticsearch:
  url: http://localhost:9200
  user:
  password:
  index: micro
  doc_type: items

Step 2: Inject the config properties to a bean

@Component("ElasticConfig")
@ConfigurationProperties(prefix = "elasticsearch")
public class ElasticsearchConfig {
    // Elasticsearch stuff
    private String url;
    private String user;
    ...
    public String getUrl() {
        return url;
    }
    public void setUrl(String url) {
        this.url = url;
    }
}

Step 3: Inject the config bean to other classes

@Autowired
private ElasticsearchConfig config;
String url = config.getUrl();

Fault Tolerance

Cloud Native microservices need to be fault tolerant as there are too many uncertain factors or moving parts. MicroProfile and Spring both provide a model to achieve fault tolerance.

MicroProfile Fault Tolerance

MicroProfile Fault Tolerance provides the following capabilities by using the annotations of @Timeout, @Retry, @Fallback, @Bulkhead, @CircuitBreaker:

Timeout: Define a duration for timeout
Retry: Define some criteria on when to retry
Fallback: Provide an alternative solution for a failed execution.
Bulkhead: Isolate failures in part of the system while the rest of the system can still function.
CircuitBreaker: Offer a way of fail fast by automatically failing execution to prevent the system from overloading and indefinite wait or timeout by the clients.

The following code snippet describes the invocation of getInventory() times out after 2s. If the operation fails, the maximum 2 retries will be performed within the overall duration of 2s. For 20 consecutive invocations, if half failures occurr, the circuit will be trapped open. If failure still occurs after the retry, fallback operation fallbackInventory methold will be invoked.

@Timeout(value = 2, unit = ChronoUnit.SECONDS)
@Retry(maxRetries = 2, maxDuration = 2000)
@CircuitBreaker
@Fallback(fallbackMethod = "fallbackInventory")
@GET
public List<Item> getInventory() {
    return items;
}
public List<Item> fallbackInventory() {
    //Returns a default fallback
    return fallbackitemslist;
}

Refer to this interactive Open Liberty guide to learn more about MicroProfile Fault Tolerance.

Spring Fault Tolerance

Spring uses Hysterix to achieve fault tolerance, detailed below.

@Service
public class AppService {

   @HystrixCommand(fallbackMethod = "fallback")
    public List<Item> getInventory() {
          return items;
    }

    public List<Item> fallback() {
        //Returns a default fallback
        return fallbackitemslist;
    }
}
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker
@SpringBootApplication
@RestController
@EnableCircuitBreaker
public class Application {
...
}

Secure Microservices

Cloud native microservices should be secured as they are exposed in the open, prone to be attacked. MicroProfile uses MicroProfile JWT together with Java EE Security while Spring uses Spring security.

MicroProfile JWT

MicroProfile JWT builds on the top of JWT with a few claims added to JWT to identify user id and user rules. The following code snippet demonstrates that the endpoint /orders can only be accessed by someone with the role of "admin".

@DeclareRoles({"Admin", "User"})
@RequestScoped
@Path("/orders")
public class OrderService {
@Inject    private JsonWebToken jwt;
     @GET
     @RolesAllowed({ "admin" })
     @Produces(MediaType.APPLICATION_JSON)
     public InventoryList listContents() {
        return manager.list();
     }
     ...
}

Refer to this Open Liberty Guide to learn how to use MicroProfile JWT.

Spring Security

You can secure a Spring microservice by configuring Spring Security. If Spring Security is on the classpath, then Spring Boot automatically secures all HTTP endpoints using basic authentication.

First, you need to specify the dependency on spring-boot-starter-security.
Second, in your microservice, specify the following annotation EnableWebSecurity or EnableResourceServer to secure microservices. See example below.

@Configuration
@EnableWebSecurity
@EnableResourceServer
public class OAuth2ResourceServerConfig extends
ResourceServerConfigurerAdapter {
    @Autowired
    Private JWTConfig securityConfig;
    .........
    .........
}

Microservices Performance

After you have deployed your microservices in the cloud, it is DevOps's responsibility to monitor microservices' performance. If something goes wrong, DevOps needs some monitoring data to identify the bottleneck or spot any warning from metrics data. The intelligent cloud native microservices should be able to communicate with the cloud infrastructure about its health status on whether it is ready to receive traffic or serve requests etc. Let's see what the programming models have to offer regarding this aspect.

Health Check

A Cloud native microservice should be able to communicate with the cloud infrastructure about its health status. Both MicroProfile and Spring provide this capability. Kubernetes, the most popular microservice orchestrator, can check the container's (running instance of microservices) readiness or liveness status. Liveness means the microservice is not live. A pod restart needs to be performed, such as running out of memory. Readiness means the microservice is not ready for server request, such as unresponsive DB connection, etc.

MicroProfile Health

MicroProfile Health 2.0 and onwards provides both Readiness and Liveness endpoints. Microservices can provide implementations of HealthCheck with the annotation of @Readiness to config readiness check procedures. The aggregation of all the beans implementations of HealthCheck and the annotation @Readiness configures the endpoint of /ready.

@Readiness
public class HealthEndpoint implements HealthCheck {
    @Override
    public HealthCheckResponse call() {...}
}

Similarly, microservices can provide implementations of HealthCheck with the annotation of @Liveness to config liveness check procedures. The aggregation of all the beans implementations of HealthCheck with the annotation of @Liveness configures the endpoint of /live.

@Liveness
public class HealthEndpoint implements HealthCheck {
    @Override
    public HealthCheckResponse call() {...}
}

Kubernetes can query the /health/live or /health/ready endpoints in its liveness or readiness probe correspondingly, as per the code snippet below.

livenessProbe:
exec:
command:
- curl
- -f
- http://localhost:8080/health/live
initialDelaySeconds: 120
periodSeconds: 10

readinessProbe:
exec:
command:
- curl
- -f
- http://localhost:8080/health/ready
initialDelaySeconds: 120
periodSeconds: 10

Refer to this Open Liberty Guide to learn how to use MicroProfile Health.

Spring

Spring Boot uses Actuator to provide the application's health status. SpringBoot Actuator exposes /health endpoint to indicate the running application's health status, such as Databbase connection, lack of disk space, etc. Applications provide this info via the implementations of HealthIndicator. This health information is collected from all the beans implementing the HealthIndicator interface configured in the application context. Below is an example of custom health implementation.

@Component
public class HealthCheck implements HealthIndicator {

    @Override
    public Health health() {
        int errorCode = check(); // perform some specific health check
        if (errorCode != 0) {
            return Health.down()
              .withDetail("Error Code", errorCode).build();
        }
        return Health.up().build();
    }

    public int check() {
        // Our logic to check health
        return 0;
    }
}

Metrics

For a running cloud native microservice, it is useful to know how much traffic it is serving, what's the throughput, any sign it might stop working soon. Metrics can help with this.

MicroProfile Metrics

MicroProfile Metrics provides an endpoint /metrics to expose all the metrics info including underline runtime. The endpoint of /metrics displays some base metrics. As an example, Open Liberty provides the following metrics type out of box. The details for each type were omitted in this article.

# TYPE base:classloader_total_loaded_class_count counter
# TYPE base:gc_global_count counter
# TYPE base:cpu_system_load_average gauge
# TYPE base:thread_count counter
# TYPE base:classloader_current_loaded_class_count counter
# TYPE base:gc_scavenge_time_seconds gauge
# TYPE base:jvm_uptime_seconds gauge
# TYPE base:memory_committed_heap_bytes gauge
# TYPE base:thread_max_count counter
# TYPE base:cpu_available_processors gauge
# TYPE base:thread_daemon_count counter
# TYPE base:gc_scavenge_count counter
# TYPE base:classloader_total_unloaded_class_count counter
# TYPE base:memory_max_heap_bytes gauge
# TYPE base:cpu_process_cpu_load_percent gauge
# TYPE base:memory_used_heap_bytes gauge
# TYPE base:gc_global_time_seconds gauge
...

You can add application specific metrics to gather more metrics. Below is an example of how to collect the response time and invocation number etc for the associated endpoint.

@Timed(name = "Inventory.timer", absolute = true, displayName="Inventory Timer",   description = "Time taken by the Inventory",   reusable=true)
@Counted(name="Inventory", displayName="Inventory Call count", description="Number of times the Inventory call happened.", monotonic=true, reusable=true)
@Metered(name="InventoryMeter", displayName="Inventory Call Frequency", description="Rate of the calls made to Inventory",             reusable=true)

 // Get all rows from database
public List<Item> findAll(){ }

Refer to this Open Liberty Guide to learn how to use MicroProfile Metrics.

Spring Actuator

Spring also provides metrics via Spring Actuator. Spring Actuator exposes an endpoint /metrics to display application metrics. In the following code snippet, /metrics displays the number of valid list and invalid list counts. Below is an example of a custom Metrics implementation.

@Service
public class LoginServiceImpl {

    private final CounterService counterService;
    public List<Item> findAll (CounterService counterService) {
        this.counterService = counterService;
        if(list.size()>1)
	counterService.increment("counter.list.valid ");
         else
	counterService.increment("counter.list.invalid");
}

Distributed Tracing

In a microservice architecture, it is common that one microservice invokes another microservice and so on. For DevOps, it is important to view the invocation chain. When there is a problem, the faulty service should be pinned down right away. In order to support this, we need a way to create a chain of invocations. Luckily, this is where distributed tracing comes into play. The implementation detail for the distributed tracing is to propagate the correlation id down the invocation chain, so that Zipkin or Jaeger can use this common correlation id to form a chain. Both MicroProfile and Spring have distributed tracing support.

MicroProfile Open Tracing

MicroProfile Open Tracing defines behaviours and an API for accessing an OpenTracing compliant Tracer object within your JAX-RS application. The behaviours specify how incoming and outgoing requests will have OpenTracing Spans automatically created.

When a request is sent from a traced client, a new Span is created and its SpanContext is injected in the outbound request for propagation downstream. The new Span will be a child of the active Span, if an active Span exists. The new Span will be finished when the outbound request is completed. All JAX-RS and Rest Client invocations automatically have the correlation id propagated.

You can specify non-JAX-RS operations to have correlation id propagated via @Traced, detailed below.

Custom Trace Implementation

import org.eclipse.microprofile.opentracing.Traced;
import io.opentracing.ActiveSpan;
import io.opentracing.Tracer;
@Traced(value = true, operationName ="getCatalog.list")
public List<Item> getInventory() {
      try (ActiveSpan childSpan = tracer.buildSpan("Grabbing
messages from Messaging System").startActive()) {...}
}

Visit this Open Liberty guide to learn more about MicroProfile Open Tracing.

Spring Tracing

Spring uses Spring Cloud Sleuth for distributed tracing support. If the Spring cloud sleuth is configured in the class path, the trace information will be generated automatically.

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>

By now, you should have some ideas of the capabilities of MicroProfile and Spring, let's get started with creating your cloud-native microservices.

Getting started

Both MicroProfile and Spring have a starter page.

MicroProfile starter

MicroProfile starter (https://start.microprofile.io/ ) provides a great way for you to create a microservice using MicroProfile and you can choose your favourite runtime, such as Open Liberty, Thorntail, Payara, TomEE, KumuluzEE, Helidon, etc.

You can also use a command line to create a microservice using MicroProfile. Refer to Karm's blog on how to use the command line tool. We are currently creating a VS Code extension plug-in to allow you to directly create microservices from your IDE. We plan to create extensions for other IDE such as Intellij, Eclipse Che, etc. Stay tuned!

Spring Starter

Spring has a starter page (https://start.spring.io/) to help you create a Spring Boot application.

Differences

MicroProfile and Spring are comparable from a functional perspective. However, they do have differences, which are summarised below.

Spring MicroProfile
APIs Open Source
Driven by Pivotal
Spring way of things
Open Source
Driven by Community
Open standards
Behaviours in accordance specifications
Lines of Code More code
Do what you want/need with code (and configuration)
Less code
Customize server configuration
Libraries/Dependencies Find, mix and match what you like
Manage your own dependencies
Runtime provides what is needed per specifications
Application Packaging Fat JARs Thin/Skinny JARs
Note: Liberty has optimized support for Spring Boot apps in containers

Summary

Both Spring and Eclipse MicroProfile provide facilities for developers to build next-generation cloud native microservices with the following observations.

They share similarities
There are differences, too (and sometimes, significant ones)

Spring has been around for quite a few years and has gained a lot of popularity. Eclipse MicroProfile and Java EE (now Jakarta EE) are evolving rapidly (and gaining momentum) as community-driven and standards-based efforts for developing microservices and cloud native applications in enterprise Java.

It is great that developers can now choose what they prefer. Companies should provide developers with platforms that enable innovation and flexibility and are enterprise and production ready. Open Liberty (https://openliberty.io/), a fast, small and lightweight runtime, supports both Eclipse MicroProfile/Java EE and Spring.

Acknowledgements

This article was heavily influenced by the experiment of converting IBM BlueCompute microservices migrating from Spring to Eclipse MicroProfile. The series of blogs describing the migration can be found here. Many thanks to my colleague YK Chang for his input into this article.

References

  1. Migrating Bluecompte app from Spring to MicroProfile blogs
  2. MicroProfile sites
  3. MicroProfile implementation runtimes
  4. MicroProfile starter page
  5. MicroProfile blogs
  6. Open Liberty site
  7. MicroProfile and Istio ecosystem
  8. Cloud Native Starter with MicroProfile and Istio

About the Author

Emily Jiang


Emily Jiang