Appendix B: Application Modernization Solutions

Although the literature on cloud-native app development is predominantly about new applications, migrating existing (monolithic) applications to the cloud while enhancing their scalability, resilience, and overall performance is also an equally important part of a cloud developer's job.

In this appendix, we will briefly cover what modernization is (and why we should do it), as well as the following topics:

  • How does Anthos, the primary Google Cloud service used for app modernization, work?
  • The phases and steps involved in modernizing Java apps
  • The six main modernization strategies available to developers

Modernizing Java apps

An overwhelming amount of applications are written in Java, and due to the value these apps provide, especially for legacy businesses, you'll likely need to modernize them without losing functionality or breaking the bank.

By modernizing old Java applications into cloud-native applications, businesses get to enjoy crucial benefits such as the following:

  • Reduced time-to-market and the ability to deploy much more frequently
  • Increased availability and the ability to create a seamless experience for users by reducing maintenance and scheduled downtime
  • Improved resilience against unscheduled failures
  • Far lower costs
  • Virtually unlimited scalability without over-provisioning resources (auto-scaling)
  • Hosting data in a more secure architecture

Let's take a closer look.

What is Google Anthos?

We've talked briefly about Google Anthos as a migration platform and landing zone where we can containerize different workloads, including Java applications, but there's a lot more to Anthos.

Anthos is an application management platform where you can develop and modernize applications, no matter where the applications are. It is built on popular open source technologies, including Kubernetes and Knative, and supports app development/migration in public, private, hybrid clouds, and even on-premises infrastructure.

As a result, it has a complex framework of interrelated technologies that makes it possible to achieve a lot of different goals. But today, we're going to simplify that framework to understand three of its main functions/components that make migration possible.

Key components of Anthos

The following are the key components of Anthos:

  • Anthos clusters: Anthos clusters combine multiple Kubernetes releases for easier management and deployment of Kubernetes applications in the environment of your choice. In essence, because both main components of Kubernetes (control plane and node components) are hosted on-premises, Anthos clusters add a common orchestration layer that developers can use for easier deployment, scaling, and configuration of their Kubernetes containers.
  • Anthos Config Management: Config Management is similar to a policy management tool; its main purpose is to allow developers to manage all of their different types of clusters using config files that are stored in a Git repository.

    In other words, with config management, you're using a Configuration as Code approach to managing clusters, similar to how you would manage applications already deployed in Google Kubernetes.

  • Anthos Service Mesh: Anthos Service Mesh creates a network of all the applications running on Anthos clusters and includes important components such as load balancing, service-to-service authentication, and monitoring. Service Mesh is an important tool for security, monitoring, and managing services.

    Important Note

    There are other components to Anthos as well such as Cloud Run for Anthos, Cloud Build for Anthos, Cloud Logging, and more. Remember, Anthos does a lot of things. However, to keep our discussion focused, we're not going to explore the rest (we've already covered many of these services in previous chapters).

Now that we understand Anthos, we can continue with the next step of modernizing Java apps: planning out the process.

Preparing to modernize Java apps

Many of the steps and best practices from Appendix A still apply to old Java apps but the overall planning process has been narrowed down, so it should be easier to follow.

For instance, in most cases, developers choose the lift and modernize method. The entire process can be summarized in two phases:

  1. The first phase is the lift part of the job and refers to containerizing the applications and creating CI/CD pipelines.
  2. The second phase is the modernize part and refers to refactoring the apps to modern Java frameworks.

Some Java applications may not be suitable for the lift-and-modernize approach. In general, packaged Java apps and those running on modern Java frameworks can be containerized using Anthos. On the other hand, traditional monolithic Java apps must be run in VMs until they can be refactored into microservices, while apps with legacy backends cannot be migrated to the cloud.

With this clear, let's dive into the first phase of modernizing Java apps.

The following is a brief explanation of all the steps in phase 1.

Phase 1 – containerizing Java applications

Containers are foundational in cloud-native applications and significantly improve the development process. Each container is a package that contains everything it needs to run an application (the code, dependencies, and libraries too). This portable and lightweight package is a great alternative to VMs that is also more efficient. That's why Anthos and many other cloud services such as Google Kubernetes Engine work primarily with containers.

Step 1 – containerizing Java applications

The process of containerizing Java applications begins with building JAR files. These JAR files can then be packaged into a containerized form such as Docker files. Alternatively, Java applications can be packaged into Kubernetes resource manifests, which is another form of container.

Step 2 – deploying applications to Anthos using modern CI/CD

The next step is to modernize our applications so that they can work flawlessly in a cloud-native environment. We do this by putting them on a highly automated assembly line called CI/CD. This is an automated and reliable software delivery method that takes over repetitive deployment and testing tasks in modern applications.

Step 3 – optimizing on-premises VMs for legacy Java applications

As we mentioned earlier, not all Java apps are suitable for containerization, so some apps will have to run in VMs. There are two ways we can do this. The first option involves using Google Compute Engine (GCE) instances, while the second option involves using VMware as a Service to manage their VMs across multi and hybrid environments while still having VMware-specific features. Additionally, Google offers built-in tools to migrate apps to GCE instances, while setting up VMware as a Service is a bit more complex.

With these three things complete, the app modernization process can continue to phase 2 where we will begin refactoring and/or re-platforming the app.

Phase 2 – Refactoring and re-platforming

Containerizing is only one part of the job. To make old Java applications fully compatible with cloud-native technologies, we also want to refactor our apps to modern Java frameworks and turn them into microservices for more scalability, security, and resilience. Most importantly, Google Cloud already has the required services to do this in its ecosystem.

For instance, developers can use Spring Cloud GCP to simplify the process of moving old Java apps to Spring Boot, a modern Java framework. The Spring Boot programs support other Google Cloud services such as Pub/Sub, Spanner, Logging, Firestore, BigQuery, and more so that you can also expand your old Java apps' functionality.

You can also continue creating modern, containerized Java apps with a host of tools and services, including Cloud Code, Artifact Registry, and build tools such as Jib and Buildpacks.

Additionally, applications that are running on commercial Java platforms and cannot be refactored will need to be re-platformed to open source alternatives such as JBoss or Tomcat.

Finally, this leaves us with legacy applications that cannot be refactored or re-platformed. To migrate these monolithic applications, we'll have to break down the monolith into microservices.

There are different ways this can be done – six, to be exact. Each of these methods or modernization strategies will be discussed in the next section.

Modernization strategies (the 6 Rs of modernization)

We must examine the different modernization strategies available today to ensure we conduct a cost-efficient and successful cloud migration. Unfortunately, it is far too common for organizations to only see two options: lift and shift and rewrite:

  • The lift and shift approach is used to recreate exactly what you have on-premises in the cloud, but it fails to realize the benefits of cloud architectures such as elasticity. The problems with on-premises are simply replicated to the cloud. Hence, these projects are often deemed failures.
  • The rewrite approach is covered in the next section as the re-architect strategy. As the name suggests, this approach involves writing a cloud-native application from scratch to recreate (and enhance) the original app's functionality but in a cloud environment.

There are multiple strategies we can use to modernize our application, and while modernizing our application, we will use more than one.

In the following section, we will examine six modernization strategies, known collectively as the 6Rs.

Retire, retain, and re-architect

The retire strategy is when we examine the application and decide that the application is no longer needed, we have another application we can use that provides the functionality in this one, or we are going to replace the application with a new one (perhaps a Software as a Service offering). Simply put, we decide that the application is not worth modernizing and we either don't need it or can replace it.

The retain strategy is when we decide that, for now, we will continue to use the application in its current on-premises. There are various reasons why we may decide to do this, and we have listed a few here:

  • The application could be on a language or platform that is not available in public clouds, such as IBM i (AS/400) or a mainframe.
  • There may be licensing terms of components that prohibit their deployment in public clouds.
  • The cost of modernization may outweigh the benefits.
  • The application has a limited lifetime, so modernizing an application that will be retired shortly after modernization would be a waste of resources.

The re-architect strategy was mentioned earlier as the rewrite option. This is where we decide to start from scratch and architect a complete replacement for the application. We use cloud-native patterns and principles from the start to design a completely new cloud-native application that replicates the functionality of the original application.

This is the most common strategy put forward by consultants, systems integrators, and developers. It can be a valid strategy and the right thing to do. However, it does have drawbacks, some of which are as follows:

  • Sunk costs in the existing application need to be written off.
  • Such projects can be lengthy in duration.
  • Such projects can be expensive to execute.
  • The benefits are only realized after the project is completed.
  • There is the risk that the expected benefits won't be realized.

We will not be using the retire, retain, or re-architect strategies in this book, but it is important to understand them and that they are valid options. When choosing strategies, it is important to assess the application in detail to decide which strategies are appropriate. Some factors to include when we assess applications are as follows:

  • Business criticality
  • Application lifetime
  • Application complexity
  • Technology/platform restrictions
  • Licensing considerations
  • Regulatory considerations
  • Value of benefits

Assessing the application portfolio is not a one-time thing. The world changes over time, so we should schedule assessments regularly. Annual or half-yearly assessments are often used.

In the remaining sections of this chapter, we will examine three of the six strategies that are used throughout this book and are appropriate for modernizing our example application. These strategies are as follows:

  • Rehost: Move and improve with no changes to the application and minimal changes to the infrastructure architecture.
  • Replatform: Replace virtual machines with an as a Service offering from Google Cloud such as Google Cloud SQL and Google Kubernetes Engine.
  • Refactor: To iteratively and incrementally pull out capabilities from the monolithic legacy application and make them microservices that are packaged in containers.

Let's look at these strategies in detail.

Rehost

At first glance, we may think that rehost is the same as lift and shift. However, rehost is actually move and improve. What do we mean by that? With lift and shift, we recreate exactly what we had on-premises. This includes all the virtual machines or physical machines that provide capabilities such as load balancing, firewalls, authentication, and so on. It also means we are not taking advantage of the cloud services provided by Google Cloud to handle those capabilities, and we do not have elasticity. The entire environment is the same as on-premises and the benefits promised by the cloud have not been realized.

With the rehost strategy, we take the core virtual machines of our application and recreate them in Google Cloud using GCE, replacing the non-core virtual machines with Google Cloud capabilities. As an example, some of the types of virtual machines that can be recreated in Google Cloud are as follows:

  • Virtual machines that run application logic
  • Virtual machines that host relational data
  • Virtual machines that host caches
  • Virtual machines that host unstructured data, such as images

Some of the types of virtual machines that would be replaced with Google Cloud capabilities are as follows:

  • Virtual machines that host firewalls (network or application firewalls)
  • Virtual machines that host load balancers
  • Virtual machines that host NAT or routing capabilities
  • Virtual machines that host VPNs

Mostly, the category of virtual machines that will be replaced with Google Cloud capabilities fulfill our network capabilities.

When we use the rehost strategy, we are still using the same operating systems, maybe even the same versions. We are not changing the code base of the application. We are making minor changes to the infrastructure architecture to take advantage of the capabilities provided by Google Cloud. The essence of the strategy is to make no changes to the software architecture or code base and make minimum changes to the infrastructure architecture, gaining some of the benefits provided by cloud infrastructures without making disruptive changes to our overall architecture.

The rehost strategy is not generally used alone but as a step on the path of modernization. It moves our application into the cloud and removes the responsibility of some capabilities from the application, placing them with the Google Cloud environment. After rehost, we generally move on to re-platform, which will be covered in the next section.

Re-platform

Re-platforming is where things start to get very interesting. This is where we start looking at Platform as a Service and the serverless capabilities provided by Google Cloud. We generally start by looking at the services provided to the application and ask the question, Is there a service provided by Google Cloud that handles this? Using these services means we no longer need to maintain a virtual machine for that capability. We no longer need to apply patches and so on. We simply configure and manage the service rather than the underlying infrastructure that supports the platform.

A typical approach we would take is to start at the data layer. Instead of managing a high availability cluster of MySQL virtual machines, for instance, and all the complexities that entail, we can choose to use Google Cloud SQL. Here, we get high availability and backups out of the box. When configuring Google Cloud SQL, we decide on the following:

  • What Relational Database Management Server (RDBMS) we want to use. This could be MySQL, for instance.
  • The version of the RDBMS we want to use.
  • The region and zone of the primary instance.
  • The private and optionally public IP addresses.
  • How much CPU, RAM, and storage we need (and if the storage is SSD or HDD).
  • The schedule for our backups.
  • If we want HA, what zone we want to place the failover instance in.

Google Cloud SQL supports MySQL, PostgreSQL, and MS SQL. This means that there is usually no or very little change to the code base, unless, of course, the application depends on an RDBMS not listed, such as Oracle.

We continue with the other data services provided to our application and re-platform each in turn. For many such data services, as with MySQL, there will be no or very little change to the code base, as we are simply replacing the service with a managed version provided by Google Cloud. However, one area where we may need to make code changes is with unstructured data. This will depend on the decisions we make. If we don't want to make code changes to an application that accesses files, for instance, we can use Google Cloud Filestore, which provides us with Network Attached Storage as a Service. If our application is structured so that the code accessing the filesystem is well encapsulated, then we could decide to replace the filesystem code with code to access Google Cloud Storage and access Blobs rather than files.

Once we have finished re-platforming the services provided to our application, we can look at re-platforming the application itself. There are multiple options for this. We can re-platform our application with one of the following:

  • Google App Engine – Standard
  • Google App Engine – Flexible
  • Google Kubernetes Engine
  • Google Cloud Functions
  • Google Cloud Run

We can categorize these options as those that are container-based and those that are not. The container-based options are Google App Engine (Flexible), Google Kubernetes Engine, and Google Cloud Run. The options that don't use containers are Google App Engine (Standard) and Google Cloud Functions. So, how do we choose between the two categories? The choice comes down to our needs:

  • Do we need a microservice architecture?
  • Do we need to orchestrate between components that will be deployed independently?
  • Do we need cloud mobility (the ability to move the application from one public cloud to another)?
  • Does the option support the language and stack we are using?

Our application may use a combination of these services. For instance, Google Cloud Functions and Google Cloud Run are very good at responding to events (triggers). It is very common to separate the code for handling events such as a file upload to cloud storage from the business logic. This separation of concerns isolates the plumbing code that's specific to an environment from the core of the application.

The following is a quick reference to help when making these decisions.

Simple applications that don't need containers:

  • Google App Engine – Standard

Container-based applications that don't need orchestration:

  • Google App Engine – Flexible

Event handling:

  • Google Cloud Functions
  • Google Cloud Run

Container orchestration, allowing stateful and stateless containers:

  • Google Kubernetes Engine

Fully serverless stateless containers:

  • Google Cloud Run

Re-platforming is not something that can be done only once. We may iterate on our re-platforming in conjunction with refactoring. So, we could do the following:

  1. Re-platform the application onto Google App Engine – Standard.
  2. Refactor to separate the events.
  3. Re-platform the event handling of the application onto Google Cloud Functions.
  4. Refactor to microservices.
  5. Re-platform the microservices onto Google Kubernetes Engine.
  6. Re-platform the event handling process onto Google Cloud Run.

Next, let's take a detailed look at how refactoring works.

Refactor

When using the refactor strategy, what we are doing is iteratively and incrementally taking the existing code base and restructuring it into a new architecture. This is not rewriting the business logic but changing the structure of the application so that it fits our non-functional requirements. The first step of refactoring is to separate the presentation logic from the business logic (services). These may already be logically separate in the application but they are not physically separate and often run in the same process. The approach is to have the presentation layer as a separate deployment to the business layer. Then, you can expose the business services to the presentation layer as Representational State Transfer (REST) services that use JavaScript Object Notation (JSON) to exchange information.

Once the presentation layer and the business layer are separated, we can start to refactor the business layer into microservices. The approach that's generally taken is to use the Strangler Fig Pattern proposed by Martin Fowler. In this pattern, we place a façade between the presentation layer and the business layer to hide changes to endpoint locations from the presentation layer. This façade is often implemented using an HTTP(S) load balancer with request routing.

With this in place, we can extract a specific capability we want to refactor into a microservice. This could be, for instance, a User Profile service that provides information on the logged-in user such as their preferred language, display name, contact preferences, and so on. Once separated, our microservice can be packaged into a container and deployed onto, for instance, Google Kubernetes Engine. Then, we can update the routing on the load balancer to route the URL for our User Profile capability to the new service. The presentation layer would not be aware of this change due to our facade.

This process continues until there is nothing left of the original monolithic application, and all the capabilities are now provided by microservices.

The refactor strategy allows us to gain business benefits very quickly and incrementally build on those benefits. If we had used the re-architect strategy, the benefits would come much later, and without feedback on the incremental changes delivered by the refactor strategy, decisions made early could likely have negative consequences, which will then be magnified by the time delay. The refactor strategy allows us to gather telemetry and feedback on our journey. With that information, we can implement course corrections along the way, with a minimal negative impact on our solution.

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

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