Modernizing Java EE Apps to Cloud Native With MicroProfile, Jakarta EE, and Open Liberty
This article is about the technical possibilities that Open Liberty provides and why I’m very confident you can smoothly migrate a Java EE application to a cloud native application.
I won’t be talking about splitting monoliths into microservices, domain-driven design, or event storming. I’m a senior consultant for cloud native topics and have worked on Java EE applications with servers such as IBM’s WebSphere Application Server (WAS) and Red Hat’s JBoss Enterprise Application Platform (EAP). I advise companies to move to cloud technologies and to pick the right tools for their environment.
I recently had the task of modernizing an application. It was a Java EE 6 app running on WAS v8. The app itself is a kind of dispatcher. It has a Simple Object Access Protocol (SOAP) endpoint to receive standardized XML messages which it forwards to the appropriate service. When a second version of the XML standard was added, it was easier to split the app into multiple services.
There were two services at the front — one for each version — that parsed the message. The core service made some transformations and distributed the message to the business services (Figure 1).
Figure 1: Java EE 6 App Split Into Services for Each Version of the XML Standard
As a first step, it’s always a good idea to eliminate cumbersome traditional application servers. They’re stable, high-performance environments, but they need a lot of time to start and are hard to configure.
If you’re using traditional WAS, move to Open Liberty or WebSphere Liberty, which is built on Open Liberty. With some limitations (remote Enterprise JavaBeans (EJBs), for example) it should be possible to easily run your app on Liberty without major adjustments. In fact, some of our clients are still using WAS. As developers, we use Liberty to build our apps locally, while in other environments, we use the client’s setup.
- Blue blocks are known good features from Java EE.
- Green blocks are the ones we used in our migrated microservices. I explain why they are useful later in this article.
- White blocks are other MicroProfile features that are worth looking into but weren’t used in this app.
Figure 2: MicroProfile Features
Resiliency in Microservices With MicroProfile and Open Liberty
When you create microservices, they communicate with each other. Sometimes you have to use synchronous RESTful calls as we did from the standard services to the core service. In this situation, use resilience in your microservices. If you don’t, a broken application can break every app in the calling chain, especially when response times are long.
With Eclipse MicroProfile, you can add resilience to your infrastructure for example, by defining timeouts in your REST client and by adding annotations to your app code to use circuitbreaker architectures.
If you disagree and say that resilience is the responsibility of a service mesh, you’re right. But, you might not have a service mesh installed in your infrastructure. When you do install a service mesh, I’ve got good news for you: Open Liberty and Eclipse MicroProfile integrate well with Istio and recognize the configurations from the mesh.
If there are configurations on the app and the service mesh, the service mesh overrules the app. But, the service mesh cannot provide fallback resilience. This is because you need business logic to react to errors with solutions (fallbacks) such as caching, returning default values, and throwing errors with descriptive error messages.
Building Twelve-Factor Apps With Eclipse MicroProfile and Open Liberty
If you want to develop a cloud native application, it’s a good idea to look into the twelve-factor app paradigm, which provides guidelines on how to build a Software as a Service (SaaS) application. Emily provides a good overview.
It’s important to prepare your application for cloud platforms and their requirements. I consider "lift-and-shift" to be an anti-pattern. Lift-and-shift just means you run your application on a different virtual machine. But it’s difficult, if not impossible, to benefit from the advantages of elastic scaling, self-healing, and zero-downtime deployments. A better approach is to use the strangler pattern, in which you create a new system around the old system, gradually letting the new system grow over a few years until the old system is "strangled."
Providing Observability With MicroProfile Metrics in Open Liberty
MicroProfile provides features, such as MicroProfile Metrics, that you really should look into when developing cloud native apps. When you have multiple, maybe hundreds or thousands of apps running simultaneously, you have to monitor them. Metrics help you do this.
The applications provide monitoring systems such as Prometheus (and Grafana for the UI) with information you can analyze and add alerts to. You can probably get metrics such as RAM or CPU usage, or the number of restarts, from the cloud platform (Kubernetes does this).
Furthermore, you can create business-driven metrics such as "how many orders are placed in my shop" or "how many logins are made in this time" and so on. Be creative! You gain new possibilities with this feature. In our case, we added metrics to identify which standard version was called with which metadata. That helped us migrate the clients and business services to the new version.
Tracing Microservices With MicroProfile OpenTracing and Open Liberty
Tracing is another important topic. When you create a microservice infrastructure, you end up managing services that communicate synchronously and asynchronously. Either way, it’s important to be able to follow the path of communications, especially when diagnosing bugs.
Imagine you have to find the bug in a calling chain with 10 apps. Maybe the 10 apps were implemented by different teams. This is very hard with no further support. You can handle this by passing a trace ID to the message and sending the information to services such as Jaeger or Zipkin.
MicroProfile provides support through a MicroProfile OpenTracing implementation. Ideally, you trace the call the whole way. In our case, we trace the call from the standard service to the core service as well as from the client calling the standard service to the services called by the core service.
Checking Microservice Health With MicroProfile Health Check and Open Liberty
Last, but not least, Platform as a Service (PaaS) cloud platforms such as Kubernetes provide self-healing.
Kubernetes checks for "liveness" and, if your application is in an unstable state, reboots the app for you. Kubernetes also checks for "readiness" before adding your app to the load balancer so it can receive traffic.
However, Kubernetes defines a pod as ready to receive work when the container is started, not when the server with the app is started and ready. This is very important: imagine scaling your app so that a new instance is created and started. If Kubernetes doesn’t check the readiness of the app itself, the first requests will fail because the server and the app aren’t already started. You can help Kubernetes by giving it endpoints in your app to provide information about your app’s readiness.
Convert Your Java EE Apps to Cloud Native Apps on Open Liberty
Overall, Open Liberty is a good choice for stepwise conversion of your Java EE applications to the cloud native paradigm. You can add one feature at a time while you refactor your app to comply with the 12 factors and remove older technologies such as remote EJBs.