Application refactoring: Best practices for cloud migrations
Learn what application refactoring is, its role in cloud migration, typical challenges, the best ways to ensure success -- and when to not do it at all.
Most applications that can run in the cloud can also operate on-premises. However, cloud-based environments work differently in many respects, which is why it often makes sense to refactor applications as part of the process of migrating them to the cloud.
This article explains why application refactoring can be a valuable step in the cloud migration process and offers practical tips and best practices for refactoring your apps to make them cloud-friendly.
What is application refactoring in cloud migration?
In the context of cloud migration, application refactoring means rewriting or redesigning the internal components of an application with the goal of taking greater advantage of the cloud.
Application refactoring typically doesn't change any application's features or the way users interact with it. Instead, refactoring modifies what's "under the hood" of the application.
Examples of changes that could take place during the refactoring process include the following:
- Breaking a monolithic application into a series of microservices. Because microservices can scale independently of each other, they can help apps take better advantage of flexible, cloud-based environments.
- Rewriting parts of the application so it can interface with specific types of cloud services, such as serverless functions, that it wasn't using when it ran on-premises.
- Removing duplicate code from an application. This can help to reduce cloud hosting costs by decreasing the amount of resources required to run the application.
- Reorganizing code to make it more readable for developers. While this might not directly impact the application's ability to run efficiently in the cloud, it does improve maintainability, which can be important for businesses that plan to keep using the app for years or decades.
- Implementing new authentication or authorization controls inside the app to help protect it from security risks. While these changes can be beneficial for any type of app, they tend to be especially important when migrating to the cloud because cloud-based apps often operate in more complex environments and face broader attack surfaces -- both of which make security protections extra critical.
Those are just some of the refactoring initiatives that can be part of a cloud migration. Ultimately, any type of modification developers make to an app to help it run faster, more cost effectively, more securely or more reliably in the cloud counts as refactoring.
How application refactoring benefits the migration process
As noted above, it's almost always possible to take an on-premises application and migrate it to the cloud without refactoring. For example, an app running on an on-premises server can typically move to a cloud-based virtual machine with no changes to the app itself. This is an example of a lift-and-shift migration, where workloads move to the cloud without undergoing substantive changes.
But just because refactoring is not strictly necessary for a successful migration, that doesn't mean it's not worth doing. On the contrary, refactoring can open the door to a variety of benefits, including the following:
- Taking advantage of unique cloud capabilities. Arguably, the biggest reason to refactor an application during migration is to help it take advantage of new types of cloud services -- such as managed databases, serverless functions and identity and access management (IAM) frameworks -- that aren't usually available on-premises. From this perspective, refactoring is a way to help an app run more nimbly and integrate more seamlessly with cloud services.
- Removing legacy components and dependencies. By a similar token, in certain cases the cloud might not offer resources or services that an application uses on-premises. For example, an app might depend on a legacy database that you can't implement in the cloud or that would be too costly or insecure to run there. Refactoring addresses this issue by enabling developers to replace outdated components or dependencies with modern, cloud-friendly alternatives.
- Reducing cloud hosting costs. Unlike in an on-premises environment, the cost of running a cloud-based application is typically a direct reflection of how many resources it consumes. For this reason, refactoring applications in ways that make them run more efficiently through reduced resource consumption can help optimize cloud hosting costs.
- Enhanced cloud security. In the cloud, applications face risks, such as misconfigured IAM rules, that typically aren't relevant with on-premises systems. Refactoring can help bolster cloud application security to mitigate these risks.
- Improving maintainability. If an application is important enough to move it to the cloud, chances are that your business will keep using it for a long time to come. Taking steps to make the app easier to maintain -- for example, by restructuring spaghetti code so it's easier for developers to understand -- will pay dividends over the long term by reducing the time and effort needed to maintain and update the app.
Challenges of refactoring for a cloud migration
Refactoring applications during cloud migration is beneficial in a number of respects, but it can also be challenging. Issues such as the following are not uncommon:
- Limited knowledge about the cloud environment. Sometimes, a team might undertake a refactoring project knowing an application is moving to the cloud but not knowing exactly which cloud services or configurations the app will use after the migration. This leads to unclear objectives and makes it difficult to know how best to improve the app based on its post-migration environment.
- The need for cloud agnosticism. Complicating the challenge of determining how an app will run after migration is that developers often face pressure to make refactored apps cloud-agnostic, meaning they can run in any cloud, using any configuration. While such flexibility might be important to a business that doesn't want to be tied to a particular cloud, it can make it difficult to optimize the app for a specific cloud environment, since targeting a specific environment could mean the app is no longer cloud-agnostic.
- Introducing new problems. Updating an application to make it cloud-friendly can enhance the app, but it can also introduce new issues -- such as additional code that leads to an overall increase in resource consumption -- that undercut the value of refactoring.
- Misalignment of migration resources and timelines. Often, businesses decide early on how many personnel resources they'll devote to cloud migration and how long they expect the migration process to take. If they fail to allocate enough developers and time to the refactoring process, they run the risk of having refactoring delay their migration into the cloud -- or ending up with a half-refactored app that they hastily push into the cloud before it's fully optimized.
Application refactoring best practices for cloud migration
The following best practices can help reduce the risk of refactoring projects going awry and maximize the value that refactoring brings to cloud migration.
1. Set specific refactoring objectives
Perhaps the single most important practice for successful refactoring is to set clear and specific goals for the refactoring process. Don't task developers simply with "improving" the application. Give them specific mandates, such as enhancing the app's authentication model or making it compatible with a particular cloud service.
When developers know exactly what they should do, their chances of successfully doing it are much higher.
2. Refactor in small steps
Trying to overhaul an application's internal codebase all at once is a recipe for failure. Not only is it a lot of work for developers to undertake, it also increases the risk that changes in one part of the app will cause compatibility issues in other parts of it as developers work on different parts of the codebase simultaneously.
A better approach is to refactor the application in small pieces or steps. For instance, you might first make changes associated with improving security, then refactor the app to enhance performance, then clean up code for maintainability purposes.
3. Test as you go
Continuously testing applications during the refactoring process helps to catch issues early on. They're typically easier to fix at this stage because the less code you've changed since a bug appears, the less code you have to rewrite to remediate the problem.
So, don't wait until you've overhauled major parts of the app's codebase to test it. Ideally, you'll run automated tests after every code change. You should also test the application as a whole once refactoring is complete, but don't let that be the first time you test it.
4. Decide on a cloud hosting model and configuration ahead of time
Developers can work more effectively when they know from the start which type of cloud environment and configuration they should target when they refactor an application. To provide this guidance, cloud architects, developers and IT engineers should collaborate early in the migration process to decide exactly how the business intends to operate the app once it's in the cloud.
5. Set flexible timelines
Even when development teams carefully plan refactoring initiatives, it's impossible to predict how long the work might take or which hurdles developers could face. For that reason, it's important to build flexibility into timelines. You can do this by reasonably overestimating how long the work will take.
You can also decide to scale back goals if they're not met in time. For example, if the refactoring plan included modifying the internal logic for a specific application function, but that work is not completed in time, you can decide to deploy the app anyway to avoid delaying the migration process.
6. Consider refactoring tools
Refactoring is always a hands-on, largely manual process, but tools can help automate parts of it. For example, linters can improve code readability or catch instances of duplicate code. Likewise, various AI-based coding tools now exist that promise to help rewrite low-quality code automatically.
7. Avoid unnecessary changes
A final best practice for successful refactoring is to refactor only where it counts. Avoid changes that won't deliver a tangible benefit, such as refactoring an app into microservices when you plan to deploy all the services on a single server; this negates much of the value of operating the app as microservices, as they are less independent of each other when they run on the same host. Just because you can do something during refactoring, that doesn't mean it's a worthwhile use of time and development resources.
Refactoring vs. finding new software
In some cases, the best approach is not to run the application in the cloud at all. Instead, you can replace it with alternative software.
For example, imagine you currently use a custom monitoring tool that your developers implemented themselves. When you move workloads to the cloud, you might find you're better served by replacing the monitoring tool with an off-the-shelf monitoring or observability suite. Even if the alternative tool costs more to operate, the costs might be outweighed by the fact that you don't need to spend any time refactoring the custom tool to meet your monitoring needs post-migration.
You could also opt to have your developers rewrite an application from scratch rather than refactor it. Doing so tends to make sense when an application has so many legacy components, or its code quality is so poor, that updating it would be more work than just replacing it.
Chris Tozzi is a freelance writer, research adviser, and professor of IT and society who has previously worked as a journalist and Linux systems administrator.