Chapter 3. Eclipse MicroProfile for Microservices

The next Java microservice framework we’ll look at is MicroProfile.

Java Enterprise Edition (EE) (now Jakarta EE) has been the workhorse of enterprise Java applications for more than 15 years. Many enterprises have heavily invested in open source and proprietary Java EE technologies, and this has affected everything from how they hire software talent to training, tooling, and management. Java EE has always been very capable at helping developers build tiered applications by offering functionality like servlets/JavaServer Pages (JSPs), transactions, component models, messaging, and persistence.

Despite its popularity, recently the feeling began to grow that Java EE’s pace of innovation was too slow for a world that demanded cloud-native applications, microservices, and containers. This feeling prompted various Java User Groups, Java Champions, vendors, and corporations to join forces and release the MicroProfile specification.

MicroProfile 1.0, announced during JavaOne 2016, was composed of the CDI, JSON-P, and JAX-RS specifications from Java EE. These base APIs allow experienced Java EE developers to utilize their existing skill sets for this fast-paced, innovative, and open source specification.

As a specification, there are several MicroProfile implementations: these include Thorntail from Red Hat, Payara Micro from Payara, TomEE from Apache, and OpenLiberty from IBM, just to name a few.

Because Java EE had a strong influence on MicroProfile, it is worth mentioning that in 2017, Oracle donated Java EE to the Eclipse Foundation under the Jakarta EE brand. Although Jakarta EE and MicroProfile share the same origin, their purpose remains very different. While Jakarta EE is a continuation of Java EE and focuses on enterprise applications, MicroProfile instead focuses on Enterprise microservices applications.

The MicroProfile 2.1 specification defines the base programming model using the Jakarta EE CDI, JSON-P, JAX-RS, and JSON-B APIs, and adds Open Tracing, Open API, Rest Client, Config, Fault Tolerance, Metrics, JWT Propagation, and Health Check APIs (see Figure 3-1). Development remains active, with groups working on Reactive Streams, Reactive Messaging, GraphQL, long running actions, and service mesh features.

Figure 3-1 MicroProfile 2.1
Figure 3-1. MicroProfile 2.1 APIs

Thorntail

Thorntail is the MicroProfile implementation from Red Hat. It is a complete teardown of the WildFly application server into bite-sized, reusable components that can be assembled and formed into a microservice application. Assembling these components is as simple as including a dependency in your Java Maven (or Gradle) build file; Thorntail takes care of the rest.

Thorntail evaluates your pom.xml (or Gradle) file and determines what dependencies your microservice actually uses (e.g., CDI, OpenTracing, and Metrics), and then builds an uber-JAR (just like Spring Boot and Dropwizard) that includes the minimal APIs and implementations necessary to run your service.

Besides the MicroProfile API, Thorntail provides many other functionalities. Some components provide only access to other APIs, such as JPA or JMS; other components provide higher-level capabilities, such as integration with Infinispan.

Getting Started

You can start a new Thorntail project by using the Thorntail Generator web console to bootstrap it (similar to Spring Initializr for Spring Boot). Simply open the page and fill in the fields with the following values, as shown in Figure 3-2:

  • Group ID: com.redhat.examples

  • Artifact ID: hello-microprofile

  • Dependencies: MicroProfile Config, JAX-RS

Figure 3-2 Thorntail generator
Figure 3-2. Thorntail Generator

Now click the blue Generate Project button. This will cause a file called hello-microprofile.zip to be downloaded. Save the file and extract it.

Navigate to the hello-microprofile directory, and try running the following command:

$ mvn thorntail:run

Make sure that you have stopped the backend service that you started in the previous chapter.

If everything boots up without any errors, you should see some logging similar to this:

2018-12-14 15:23:54,119 INFO  [org.jboss.as.server] (main)
    WFLYSRV0010: Deployed "demo.war" (runtime-name : "demo.war")
2018-12-14 15:23:54,129 INFO  [org.wildfly.swarm] (main)
    THORN99999: Thorntail is Ready

Congrats! You have just gotten your first MicroProfile application up and running. If you navigate to http://localhost:8080/hello in your browser, you should see the output shown in Figure 3-3.

Figure 3-3 Hello from Thorntail
Figure 3-3. Hello from Thorntail

Hello World

Just like with the Spring Boot framework in the preceding chapter, we want to add some basic “Hello World” functionality and then incrementally add more functionality on top of it.

We want to expose an HTTP/REST endpoint at /api/hello that will return “Hello MicroProfile from X,” where X is the IP address where the service is running. To do this, navigate to src/main/java/com/examples/hellomicroprofile/rest. This location should have been created for you if you followed the preceding steps. Then create a new Java class called HelloRestController, as shown in Example 3-1. We’ll add a method named hello() that returns a string along with the IP address of where the service is running. You’ll see in Chapter 6, in the sections on load balancing and service discovery, how the host IPs can be used to demonstrate proper failover, load balancing, etc.

Example 3-1. src/main/java/com/examples/hellomicroprofile/rest/HelloRestController.java
public class HelloRestController {

    public String hello() {
        String hostname = null;
        try {
            hostname = InetAddress.getLocalHost()
                                  .getHostAddress();
        } catch (UnknownHostException e) {
            hostname = "unknown";
        }
        return "Hello MicroProfile from " + hostname;
    }

}

Add the HTTP Endpoints

You might have noticed that the POJO class HelloRestController in the MicroProfile project has exactly the same implementation as the HelloRestController class in the Spring Boot project. The only exception will be the HTTP endpoint annotations, which we add in Example 3-2:

@Path

Maps specific parts of the HTTP URI path to classes and methods.

@GET

Specifies that any HTTP GET method will invoke the annotated method. The method won’t be called if the path is the same but the HTTP method is anything other than GET.

@Produces

Specifies the MIME type of the response.

Again, note that import statements are omitted in the following example.

Example 3-2. src/main/java/com/examples/hellospringboot/HelloRestController.java
@Path("/api")
public class HelloRestController {

    @GET
    @Produces("text/plain")
    @Path("/hello")
    public String hello() {
        String hostname = null;
        try {
            hostname = InetAddress.getLocalHost()
                    .getHostAddress();
        } catch (UnknownHostException e) {
            hostname = "unknown";
        }
        return "Hello MicroProfile from " + hostname;
    }

}

In this code, all we’ve done is add the aforementioned annotations. For example, @Path("/api") at the class level says “map any method-level HTTP endpoints under this root URI path.” When we add @Path("/hello") and @GET, we are telling MicroProfile to expose an HTTP GET endpoint at /hello (which will really be /api/hello). The annotation @Produces("text/plain") maps requests with a media type of Accept: text/plain to this method.

If you build the application and run thorntail:run again, you should be able to reach the HTTP endpoint:

$ mvn clean thorntail:run

Now if you point your browser to http://localhost:8080/api/hello, you should see a response similar to the one shown in Figure 3-4.

Figure 3-4 Hello from MicroProfile
Figure 3-4. Successful hello

Now, the same way as we did for Spring Boot, we will see how to inject external properties into our app using MicroProfile’s Config API.

Externalize Configuration

Like Spring Boot, MicroProfile defines a mechanism to externalize configuration. It uses different ConfigSources to consume configuration from system properties, environment variables, or even from a META-INF/microprofile-config.properties file. Each of these sources has a different priority, so configuration values can be overwritten.

To see how simple it is to consume external configuration, let’s add a new property to our src/main/resources/META-INF/microprofile-config.properties file (remember to create the folder structure and the file if they don’t exist):

helloapp.saying=Guten Tag aus

Now, as shown in Example 3-3, let’s add the @Inject and @ConfigProperty("helloapp.saying") annotations and our new saying field to the HelloRestController class. Note that, unlike with Spring Boot, we don’t need setters or getters.

Example 3-3. src/main/java/com/examples/hellomicroprofile/HelloRestController.java
@Path("/api")
public class HelloRestController {

    @Inject
    @ConfigProperty(name="helloapp.saying")
    private String saying;

    @GET
    @Produces("text/plain")
    @Path("/hello")
    public String hello() {
        String hostname = null;
        try {
            hostname = InetAddress.getLocalHost()
                                  .getHostAddress();
        } catch (UnknownHostException e) {
            hostname = "unknown";
        }
        return saying + " " + hostname;
    }

}

Because we’ve started using the CDI API in our examples, we’ll also need to add the beans.xml file, with the contents shown in Example 3-4.

Example 3-4. src/main/resources/META-INF/beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans
        xmlns="http://xmlns.jcp.org/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
        bean-discovery-mode="all">
</beans>

This file will instruct the CDI API to process all the injection points marked with the @Inject annotation.

Let’s stop our application from running (if we haven’t) and restart it:

$ mvn clean thorntail:run

Now if we navigate to http://localhost:8080/api/hello we should see the German version of the saying, as shown in Figure 3-5.

Figure 3-5 Successful German
Figure 3-5. Successful German hello

Expose Application Metrics and Information

Another similarity with Spring Boot is the ability of MicroProfile applications to expose endpoints that can be used to monitor our applications. To enable this, we need to add the MicroProfile Metrics dependency to our pom.xml file.

Open up the pom.xml file for the hello-microprofile microservice and add the following Maven dependency within the <dependencies>…</dependencies> section:

<dependency>
    <groupId>io.thorntail</groupId>
    <artifactId>microprofile-metrics</artifactId>
</dependency>

Adding this dependency will cause the application to expose a lot of information that will be very handy for debugging and general insight.

Now stop your microservice and restart it by running:

$ mvn clean thorntail:run

Try hitting the URL http://localhost:8080/metrics and examine what gets returned. You should see something like the result in Figure 3-6.

Figure 3-6 MicroProfile metrics
Figure 3-6. MicroProfile metrics

Easy, right?

Running Outside of Maven

Remember how easy it was to run Spring Boot outside Maven? Luckily, with MicroProfile it just takes the exact same few steps to get a microservice ready for shipment and production.

Just like Spring Boot, MicroProfile prefers atomic, executable JARs with all dependencies packed into a flat classpath. This means the JAR that we create as part of a call to mvn clean package is executable and contains all we need to run our microservice in a Java environment! To test this out, go to the root of the hello-microprofile microservice project and run the following commands:

$ mvn clean package
$ java -jar target/demo-thorntail.jar

That’s it! Exactly the same approach used by Spring Boot.

Calling Another Service

In a microservices environment, each service is responsible for providing its functionality or service to other collaborators. If we wish to extend the hello_microprofile microservice, we will need to create a service that we can call using JAX-RS client functionality. Just like we did for the Spring Boot microservice, we’ll leverage the backend service from the source code that accompanies this report. The interaction will look similar to Figure 3-7.

Figure 3-7 Calling another service
Figure 3-7. Calling another service

If you look at the source code for this report, you’ll see a Maven module called backend which contains a very simple HTTP servlet that can be invoked with a GET request and query parameters. The code for this backend is very simple, and it does not use any of the microservice frameworks (Spring Boot, MicroProfile, etc.).

To start up the backend service on port 8080, navigate to the backend directory and run the following:

$ mvn clean wildfly:run

Remember that the hello-microprofile service should be stopped before running the backend service.

This service is exposed at /api/backend and takes a query parameter called greeting. For example, when we call this service with the path /api/backend?greeting=Hello, like this (you can also visit this URL with your browser):

$ curl -X GET http://localhost:8080/api/backend?greeting=Hello

We get back a JSON object like this:

{
  "greeting" : "Hello from cluster Backend",
  "time" : 1459189860895,
  "ip" : "172.20.10.3"
}

Next, we’ll create a new HTTP endpoint, /api/greeting, in our hello-microprofile microservice and use the JAX-RS Client API to call this backend.

First, we create a new class in src/main/java/com/examples/hellomicroprofile called GreeterRestController and fill it in similarly to how we filled in the HelloRestController class (see Example 3-5).

Example 3-5. src/main/java/com/example/hellomicroprofile/rest/GreeterRestController.java
@Path("/api")
public class GreeterRestController {

    @Inject
    @ConfigProperty(name="greeting.saying", defaultValue = "Hello")
    private String saying;

    @Inject
    @ConfigProperty(name = "greeting.backendServiceHost",
        defaultValue = "localhost")
    private String backendServiceHost;

    @Inject
    @ConfigProperty(name = "greeting.backendServicePort",
        defaultValue = "8080")
    private int backendServicePort;


    @GET
    @Produces("text/plain")
    @Path("greeting")
    public String greeting() {

        String backendServiceUrl = String.format("http://%s:%d",
                backendServiceHost,backendServicePort);

        return "Sending to: " + backendServiceUrl
    }

}

We’ve created a simple JAX-RS resource here that exposes an /api/greeting endpoint that just returns the value of the backendServiceUrl field. Also note that we’re injecting the backend host and port as environment variables that have default values if no environment variables are set. Again, we’re using MicroProfile Config’s @ConfigProperty annotation to accomplish this.

Let’s also add the BackendDTO class, shown in Example 3-6, which is used to encapsulate responses from the backend.

Example 3-6. src/main/java/com/examples/hellomicroprofile/BackendDTO.java
public class BackendDTO {

    private String greeting;
    private long time;
    private String ip;

    public String getGreeting() {
        return greeting;
    }

    public void setGreeting(String greeting) {
        this.greeting = greeting;
    }

    public long getTime() {
        return time;
    }

    public void setTime(long time) {
        this.time = time;
    }

    public String getIp() {
        return ip;
    }

    public void setIp(String ip) {
        this.ip = ip;
    }
}

Next, let’s add our JAX-RS client implementation to communicate with the backend service. It should look like Example 3-7.

Example 3-7. src/main/java/com/example/hellospringboot/GreeterRestController.java
@Path("/api")
public class GreeterRestController {

    @Inject
    @ConfigProperty(name="greeting.saying", defaultValue = "Hello")
    private String saying;

    @Inject
    @ConfigProperty(name = "greeting.backendServiceHost",
        defaultValue = "localhost")
    private String backendServiceHost;

    @Inject
    @ConfigProperty(name = "greeting.backendServicePort",
        defaultValue = "8080")
    private int backendServicePort;

    @GET
    @Produces("text/plain")
    @Path("greeting")
    public String greeting() {
        String backendServiceUrl = String.format("http://%s:%d",
                backendServiceHost,backendServicePort);

        System.out.println("Sending to: " + backendServiceUrl);

        Client client = ClientBuilder.newClient();
        BackendDTO backendDTO = client.target(backendServiceUrl)
                .path("api")
                .path("backend")
                .queryParam("greeting", saying)
                .request(MediaType.APPLICATION_JSON_TYPE)
                .get(BackendDTO.class);

        return backendDTO.getGreeting()
                + " at host: " + backendDTO.getIp();
    }

}

Now let’s build the microservice and verify that we can call this new greeting endpoint and that it properly calls the backend. We’ll configure this service to run on a different port than the default (8080) so that it doesn’t collide with the backend service, which is already running on that port:

$ mvn thorntail:run 
  -Dswarm.network.socket-binding-groups.standard-sockets
  .port-offset=100

In Chapter 6, we’ll see how running these microservices in their own Linux containers removes the restriction of port swizzling at runtime. With all that done, you can point your browser to http://localhost:8180/api/greeting to see if our microservice properly calls the backend and displays what we’re expecting, as shown in Figure 3-8.

Figure 3-8 Successful backend
Figure 3-8. Successful backend

Where to Look Next

In this chapter, you learned about the MicroProfile specification and its Thorntail implementation. You also learned how to expose REST endpoints, configuration, and metrics and make calls to external services. This was meant as a quick introduction to Thorntail and is by no means a comprehensive guide. Check out the following links for more information:

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.164.24