How to Use Recent MicroProfile and JDK Features to Scale Your Apps in the Cloud
This article outlines features of recent JDK and MicroProfile updates for reliably deploying and scaling cloud applications.
Join the DZone community and get the full member experience.Join For Free
Managing a potentially unlimited number of autonomous, loosely coupled and frequently updated microservices on top of a flexible cloud infrastructure creates new challenges for developers. This article outlines features of recent JDK and MicroProfile updates for reliably deploying and scaling cloud applications while continuously integrating network and service updates. We’ll also cover tips for enabling documentation, authentication, and managing decoupled application dependencies, resources, and configuration.
Adding Notable JDK Updates to Your Microservices
The latest JDK release has an interesting collection of new features that can help with building microservice-based applications. JDK 11 has been generally available since September 2018 and is the first Long-Term-Support version after Java 8. As always, the features and schedule of the latest JDKs are managed via the JDK Enhancement Proposal (JEP) Process. I’ve included the JEP release numbers and links as a handy reference to the full details of each new feature.
Launching Single-File Source-Code Programs and a Standard HTTP Client
The ability to launch single-file programs enables quick testing of a developer’s code locally and can also greatly simplify the instructions needed to run the code in a container-based environment. Note this sentence in the motivation section of JEP 330:
Single-file programs -- where the whole program fits in a single source file -- are common in the early stages of learning Java, and when writing small utility programs.
Of course microservices could be described as “small utility programs.” And you can create a container to load a JDK image in this context. Then you just add a COPY to load the file into the container and a CMD to get it started in your dockerfile:
FROM adoptopenjdk/openjdk11:latest COPY mymicroservice.java / mymicroservice.java CMD ["java", "mymicroservice.java"]
Now imagine how this simplifies testing – instead of doing a build/compile and running of your code in a container, you can run it in different JVM and OS versions by simply changing the FROM line in your dockerfile!
Next up, in your single-file microservice you’ll likely need to speak to other microservices, at minimum with a call and response, probably using HTTP. To accomplish this in a very simple and readable way, implement the new standard HTTP Client (JEP 321) as part of your microservice. An HttpClient object sends requests via asynchronous requests, HTTP/2, and websockets. An HttpRequest object contains a request method and headers, and a HttpResponse object returns information from the target. Here’s a great example of a simple HTTP client implementation with a comparison of the URLConection object it replaces, from Anthony Bruno.
Work Around the Removal of XML APIs When Working With MicroProfile and Spring Apps:
Unfortunately, JEP 320 removes some handy XML processing classes as part of the removal of older Java EE and CORBA Modules, specifically JAX-WS (Java API for XML-Based Web Services), and JAXB (Java Architecture for XML Binding), which are common for reading and managing XML configuration files, among other functions.
Of course, you can build and/or refactor your apps to satisfy the Java world’s current YAML fetish if that’s your thing, but if you want to keep your applications as-is and use the latest JDK, the reference implementations of JAX-WS and JAXB are available as Maven artifacts:
- com.sun.xml.ws : jaxws-ri (JAX-WS, plus SAAJ and Web Services Metadata)
- com.sun.xml.bind : jaxb-ri (JAXB)
There are also Maven artifacts for tools and APIs, check the JEP 320 doc for full details and links.
Use Flight Recorder and Distributed Tracing to Collect and Analyze Telemetry in Your Microservices
Two recent developments can greatly help you plan and manage deployments in the cloud.
Distributed Tracing can help manage and track microservice performance as well as the interactions between microservices. Distributed traces help manage and identify bottlenecks in your cloud applications, even if they’re not all running on the same cloud, with minimal overhead and open-source visualization offerings.
OpenTracing is comprised of an API specification, frameworks, and libraries that have implemented the specification and documentation for the project. OpenTracing allows developers to add instrumentation to their application code using APIs that do not lock them into any one particular product or vendor.
Flight Recorder (JEP 328) takes the analysis one step further and dials into the JVM to track performance bottlenecks and errors in the running code.
From the JEP 328 document:
Flight Recorder records events originating from applications, the JVM, and the OS. Events are stored in a single file that can be attached to bug reports and examined by support engineers, allowing after-the-fact analysis of issues in the period leading up to a problem. Tools can use an API to extract information from recording files.
So flight recorder is great in dev and test to manage the performance and reliability of the code, then distributed tracing is great to keep an eye on deployed microservices to see where bottlenecks are occurring across distributed microservices!
Using MicroProfile to Manage Your Microservices
MicroProfile has been rapidly evolving as a great way to manage microservices with a few basic components that most microservices need. It’s an Eclipse project based on Java EE 8 and has participation from major cloud vendors, IT organizations and individuals. You can get more information on the MicroProfile project, components, and participants at microprofile.io. Also, check out the new MicroProfile starter page at https://start.microprofile.io/
Manage MicroProfile Configurations at Build and Runtime
One of the great features of MicroProfile is the configuration options. MicroProfile Config enables configurations externally and at runtime, meaning that you can manage and even change some behaviors of an application based on the application environment, without any changes to the application code.
You can use a pom.xml file to specify and transport configurations, or the can be specified or overridden at runtime. System properties are evaluated first, then environment variables, then the pom.xml or project-defaults.yml files on the classpath. This enables not only flexible runtime environments, but also easy testing of different configuration options via ENV vars and system properties.
For example, Distributed Tracing is one of the key components built-in to MicroProfile applications. To include jaeger, an OpenTracing implementation and visualization tool in your applications, you just add this dependency to your pom.xml:
<dependency> <groupId>io.thorntail</groupId> <artifactId>jaeger</artifactId> </dependency>
Next up, if you want to create a custom name for the service you can add it in the project-defaults.yml files on the classpath like this:
You can also set the name as an environment variable:
If you're running a MicroProfile application with a Java -jar file, you can also specify settings via parameters via the command line:
Java -jar -jaeger-example
Some application platforms support environment variables for each deployment. Here’s an example of Red Hat OpenShift Origin with jaeger environment variables set for a deployed container in a production environment:
This enables major flexibility when building, testing, and deploying your apps in multiple cloud environments with loosely couple services.
Build Fault Tolerance and Health Checks Into Your Microservices Without Code!
Even with all the facilities that I’ve shared so far for managing code and testing, things can still go wrong in production. For those rare cases, you want to have easy ways to shut down and redeploy apps in a container environment. You also want to use similar features to help with scale by monitoring application load and adjusting the number of containers available based on user needs.
Health checks are used to check if a specific microservice is healthy before engaging it via another microservice or process. Fault tolerance is a way to manage what happens if a microservice is failing inside a larger infrastructure of multiple processes and microservices, and what automated action to take based on predetermined rules.
The good news is that, with MicroProfile, fault tolerance and health checks can be built into your application with no changes to the code whatsoever.
For example, to enable health checks in a MicroProfile app, just add this segment to the configuration section of the application’s pom.xml file:
<config> <thorntail-v2-health-check> <path>/health</path> </thorntail-v2-health-check> </config>
Once fault tolerance and health checks are enabled, they can be monitored and automated responses to predetermined events can be triggered with separate code. That code uses CDI to apply RetryPolicy, Fallback, BulkHead, and CircuitBreaker using annotations.
I hope you have enjoyed this overview of new the JDK and MicroProfile features that can make a cloud microservice developer’s life easier as much as I’ve enjoyed putting it together. We live in interesting times! It’s truly amazing to see all of the community effort that has gone into the JEP process and the Eclipse MicroProfile project. Most of the work represents amazing innovations in managing cloud-based microservices. If I’ve inspired you or you have any questions please let me know – I love hearing from readers! And please follow me on Twitter @bbenz for useful, related information.
Opinions expressed by DZone contributors are their own.