Best Practices for Development Teams Migrating Apps to the Cloud
Enterprise applications typically fall into one of three categories. At one extreme are the majestic legacy monolithic applications running on servers in data centers owned by the organization. On the other hand, modern microservices-based apps are built from the ground up to run natively in the cloud. In between are migrated apps that you have lifted and shifted into containers and deployed on the cloud. We can refer to these apps as migrated, not modernized.
Almost all organizations agree that cloud-based applications are essential for scaling to meet digital demand today. Those who have successfully undergone the transformation journey enjoy the benefits of modernization. But while app modernization, which implies refactoring and rearchitecting services for the cloud, offers value, it also comes with obstacles and risks.
Modernization requires a mastery of new tools and changes to familiar workflows. It also calls for a mindset change and a willingness to assess and confront risk. Companies ready to do this can mitigate their risks by using a platform approach to take control of their modernization efforts.
Migration is simply moving an application from on-premises infrastructure to a modern cloud setup. This approach maintains the functionality and familiarity of the existing application while reducing data center, compute, and storage costs. It is a simple solution and offers some limited benefits. Modernization is a much more complex and expensive process but provides more value.
Building a Strategic Cloud Migration and Modernization Plan
Table of Contents
Migration (Rehosting) Vs Modernization (Refactoring)
There are many options to modernize legacy applications. The two methods discussed here are Rehosting or migration (a.k.a. lift-and-shift) and Refactoring or modernization (or rewriting). It would be helpful to take a brief look at both these approaches:
Rehosting: involves moving applications to the cloud as they are, hence the alternative name “lift-and-shift.” In this approach, you move the application to a cloud environment and only make the minimum changes necessary to get it to run. A vital concern is to verify, after migration, that the app can access all the data it was able to earlier.
Lift-and-shift has several advantages. You don’t make extensive code or architecture changes, thus avoiding expensive development and testing. It is possible to migrate core components quickly and easily. There is no effect on security and compliance.
However, this approach has some disadvantages too. App migration does not take the maximum advantage of cloud features, leading to latency and performance issues. Known problems that continue to exist could blow up at any time.
Refactoring: In this approach, applications are modified and re-architected to take advantage of the cloud. This approach is complex because significant code changes are involved. You must thoroughly test the changes to ensure that you have not caused any regressions. While this technique is more expensive and complicated, it is also a lot more beneficial.
Refactoring gives you many advantages. When you replace a monolith with microservices, you can respond faster to customer requests. Using cloud infrastructure for scale and elasticity is efficient and saves you money. You can upgrade to a modern DevOps process.
There are a few disadvantages. If you are genuinely cloud-native and use many cloud features, you may get locked into a particular vendor. The refactoring process takes time and requires special technical skills.
Best Practice #1 – Think Strategic: Modernize While you Migrate
Modernization is a must for most companies to stay competitive – to achieve scalability and agility and deliver products per customer expectations. Owners of legacy applications have a powerful incentive to modernize. However, modernization takes time and carries its fair share of risk. Therefore, as mentioned in the previous section, a practical alternative approach is to first migrate to the cloud. You can do this with lift-and-shift in which you containerize the legacy applications and deploy them to the cloud. This process does not involve any source code changes.
So, if we consider modernization a strategic initiative, migration is more akin to a tactical approach. Let’s take an in-depth look at the differences between strategy and tactics and how we can use them to build an effective business strategy.
Hundreds of years ago, the ancient Chinese general Sun Tzu wrote the classic military treatise, The Art of War, which continues to influence military, political, and business thinking. Sun Tzu first used the terms “strategy” and “tactics” in a military context.
In a business endeavor, start by defining an end goal. A strategy is an action plan that you will take to achieve the goal. Tactics are the exact steps that you will take to meet the goal. Think of strategy as architecture or high-level design. Tactics are much more low-level or detailed.
According to Sun Tzu, it takes forever to implement a strategy when you have no tactics. And having tactics without an overarching guiding strategy is a precursor to defeat. In other words, we need both strategy and tactics for success. Now let us see how this applies to modernization and migration.
Modernization has several end goals – to deliver new features faster, shorten release cycles, increase scalability and elasticity, and increase customer satisfaction. Of this, migration helps to improve security and simplify the release process by enabling DevOps. It does not directly achieve the other goals but starts you on the path towards completing them.
All modernization projects deliver the same benefits. But we have often said that modernization is not a one-size-fits-all solution. When you finalize a strategy for modernization, you are customizing it to your situation. Note also that modernization is not a once-and-done process. It is a continuous activity. Migration is the first step in the process.
Migrating enterprise applications to the cloud is the beginning of your cloud journey. It is a catalyst or starting point and hence a valuable tactic as part of your overall modernization strategy. But migration projects that are not governed by and connected to a modernization strategy will lead to defeat, disappointment, and despair. And conversely, a modernization strategy needs tactics like migration to keep moving along.
You may be wondering why, with so many limitations, companies still opt for migration. There are many good reasons:
- You may want to urgently get out of a data center using a small team to migrate. Your on-prem data center costs will immediately decrease.
- You are okay getting some benefits from migration now instead of getting all the benefits of modernization later.
- You want to learn more about the cloud before plunging into full-scale modernization.
- This option may have the minimum downtime because the monolith will still be available.
- Your migrated app runs on better-performing hardware, thereby giving you an immediate performance boost.
- You can scale on-demand, though not very efficiently.
- The cloud provider supplies enhanced security features.
These are some benefits you get with not much effort.
Best Practice #2 – Avoid Future Pain: Benchmark App Complexity, Technical Debt, Risk Early
As part of deciding the modernization strategy, one should start with an assessment of the current IT landscape. This analysis will help you to pinpoint the current pain points, what needs to improve, by how much, and how to measure the improvement. This knowledge guides you towards a modernization approach in terms of which legacy applications to modernize and in what order.
Further, the discovery process serves to build a business case for modernization by understanding your current state and helps determine what problems are holding the business back and whether migration will solve these problems.
An assessment will give you a thorough understanding of your applications and IT landscape. You will use this information to understand your business problems and come up with an outcome-oriented roadmap for modernization. You should perform the assessment across several dimensions like application portfolio, infrastructure, performance, and costs. It will help identify what apps to eliminate, consolidate, modernize, replace, or remove from the modernization process.
You can build an execution roadmap based on the information gathered in the assessment phase. The roadmap prioritizes initiatives like app modernization, cloud migration, containerization, and DevOps by examining the findings of the assessment phase. It also gives insights into costs and timelines. A good place to start is with Gartner’s PACE-layered Application Strategy.
Before starting application migration and modernization projects, another critical activity is to measure and benchmark application metrics related to complexity, technical debt, and risk.
Some quantitative parameters we can benchmark include:
- Percentage of CPU that the app is utilizing
- Percentage of memory being used
- Average load on servers (requests per second)
- Uptime (availability)
- Page load times
- Transactions throughput
- End-to-end response time (for important transactions)
- Database response times
- Operational cost
- Time to make changes (from the receipt of a change request to the availability of the feature in production)
- The average time between releases
- MTTR (time to fix critical issues)
Qualitative parameters that you could baseline include:
Automated migration tools: Does the cloud provider supply automated tools? If yes, they may make your migration process smoother.
Order of migration: If you have several apps for migration, you may not want to migrate all of them at once. Figure out the order in which you want to proceed.
App lifecycle: Of these apps, which ones are you planning to keep for the long run, and which ones are you planning to phase out soon?
Regulatory compliances: You and the cloud provider are jointly responsible for industry compliances. Make sure that you continue to meet all compliance requirements post-migration.
Scope creep: Be clear on what tasks you will perform during migration and ensure that you stick to them.
Finally, here are some technical and business outcomes that you may want to benchmark and improve:
Support Cost: Reduction in the cost of support, as many operations transition to the cloud provider.
Quality: Fewer defects escaping to production code, as test coverage and automated testing increase.
Availability, scalability, and elasticity: Zero, or reduced, downtime. Automated provisioning of new compute instances.
Self-healing: automated failure recovery.
Agility: release velocity increases, with improved CI/CD pipelines and automation.
Compatibility: Not locked into one cloud provider.
Flexibility: Increased configurations.
TCO optimization: Optimize legacy apps to get more out of existing investments. Refactor those applications or services that have the highest technical debt first..
So, at the end of the discovery phase, you have a good idea of:
- the priority by which different applications must be modernized
- what your critical parameters are before migration/modernization
- post-migration/modernization targets for these parameters
It may be easier to use a tool to do this. Here is an example of a tool that performs a migration assessment.
Database Migration to the Cloud
When migrating a monolith, moving the database over to the cloud comes with its own challenges. Remember that in the data center, you had one application and one copy of the database. Now you may have to maintain several copies of the database at different locations. Some of your clients may have strict requirements about hosting their data only in some geographic regions, like inside the EU.
Now you need to:
- Think about how the migration might affect your data.
- Prepare the database for migration.
- Move the database over from the data center to the cloud without downtime or data loss. If you have a massive database, you may need to move the data (and the services that use them) in sprints.
- Secure the database in the cloud. You may have to apply special safeguards if you store sensitive personal or financial or similar data.
- Replicate the data.
- Validate the data access functionality after migration.
After moving the database to the cloud, you need to monitor usage to measure transaction throughputs and any pattern of surges in usage.
If done properly, migrating databases to the cloud results in superior performance, cost savings, predictability, availability, and observability.
Avoiding Future Pain
Most migration and modernization projects end in disappointment. Executing app modernization and migration projects is hard. A McKinsey survey reports that out of all modernization projects, 75% end up over budget and 38% behind schedule. This results in failed projects and costly overruns, incorrect prioritization of apps to modernize, and no justifiable business case for modernization.
These problems crop up because many companies have no outcomes to measure success against. Hence it is critical to measure everything. Here are a couple of suggestions on how to avoid post-migration pain.
- Baseline your application complexity using a data-driven approach before migrating. This helps you build data-driven business cases for modernization.
- Use tools and automation to measure app complexity based on code modularity and dependency entanglements. Analyze the risk of changes impacting stability based on the depth and length of the dependency chains, and then aggregate these to assess the overall technical debt level.
This approach has several benefits. It helps you benchmark technical debt, migration risk, and complexity against your suite of legacy apps. It enables you to identify frameworks that could pose security and licensing risks in the future.
Related: 11-step guide to App Modernization
Best Practice #3 – Why Migrate? Set Clear Scalability, Cost, Velocity Goals
We have seen that migration is tactical and hence fixes only some issues. So, if you are expecting migration to resolve all your problems, you may be setting yourself up for disappointment. Make sure that you are not migrating for the wrong reasons.
Here are some expectations that migration will not meet, or meet only to some extent:
Improve the scalability of your monolithic application
Migration will only shift your infrastructure to the cloud, but it will not improve scalability. It may even get worse as you have to scale your IaaS to get any scale benefits.
Improve engineering velocity
Your ability to release new features more often and increase the rate of innovation in your application will not change by simply migrating your monolith to the cloud – it’s still a monolith carrying the same technical debt.
Lower operating costs immediately
Shutting down some or all of your data center will reduce costs on the infrastructure side and allow for some DevOps management benefits if you containerize your monolith. But those cost gains are likely to be offset when you try to add capacity. This is because your monolith has no elasticity – it scales as a single entity and has no horizontal scalability. You will save money but only in the long term.
If you are trying to lower technical debt with migration, you can reduce it to an extent by adopting the following patterns and practices:
Test Coverage Automation: Improve automated test coverage after migration. Make TDD (Test Driven Development) the norm.
CI/CD pipelines: Create CI/CD pipelines with automated test execution after the migration. That would let developers know immediately about issues in the code.
Microservices: Consider converting part of your legacy app to microservices. Microservices have several advantages.
Detecting security vulnerabilities: Cloud providers offer a variety of tools to help make you regulation-compliant and secure. Use them.
Static code analysis: Add static code analyzers to the CI/CD pipelines. Create a baseline and ensure that, at a minimum, the code quality does not degrade.
Note that the migration process is limited, so you may not be able to implement all these best practices. Thus, you can only reduce a small amount of technical debt.
There are some more practices, which if followed, increase your chances of success as you migrate to the cloud:
A solid business case: Ensure migration delivers tangible benefits. To do this, list your goals and see if they are achievable. Consider how you will measure results and decide if the migration has met your desired goals.
Bucketize your workloads and map them: Ensure that the cloud provider can provide the support needed for your workloads to have the desired outcomes.
Define a cloud operating model: A cloud operating model specifies how the cloud will transform your current workflows, internal processes, and operations. It should define how your organization will function after you have completed the migration.
Estimate timeline and budgets: You should use cloud-specific estimation tools to get a more accurate timeline and budget estimate before starting the project.
Prioritize the order to migrate apps: Conduct a strategic portfolio analysis to determine what applications or workloads to move first.
Test the migrated app: Pay attention to the testing of your cloud app. Have a disaster recovery plan in place.Security is critical: The customer and the cloud service provider share the cloud security responsibility. Create complete and robust security policies for success post-migration.
Best Practice #4 – Iterate for Best Results: Big Bang Migrations Often Disappoint
Sometimes, you can rehost your application in a jiffy. Big-bang migrations to the cloud can result in quick, feel-good wins, but unless they are part of a comprehensive modernization strategy, the legacy problems that plagued you earlier will persist in the cloud. Migration will not help you.
Instead, for best results, consider taking the following refactoring steps after rehosting.
Tip 1: Selectively refactor your app
You may have hundreds or thousands of legacy apps in your portfolio. For example, Air-France-KLM has about 2000 apps it is trying to modernize. Not all of them will be business-critical to the same extent.
Some applications should be extracted as a microservice because:
- of their business value. They may be a significant part of your intellectual property or a key business driver related to customer experience.
- they have so much utility that they should exist as a critical shared core service to be used by other applications.
- they are carrying tremendous technical debt, and refactoring them will unlock resources, lower costs, and increase development velocity.
- all of these reasons, or some combination of the above.
AI-based assessment solutions can identify the technical debt different apps are carrying, the complexity of converting those apps to services, and the specific classes contributing most to the technical debt of that app. When combined with the business value this app provides, the analysis will help development teams decide which services to refactor selectively versus trying to re-architect the entire app.
Tip 2: Refactor iteratively:
Similar to selectively refactoring, development teams should consider an iterative refactoring strategy that chips away at their monolith one critical service at a time. As a wise person said, modernization is a journey, not a sprint. We are talking not only about technical changes but also about people, processes, and culture change.
This process is described in the Strangler Fig design pattern. You incrementally migrate parts of the legacy apps by rewriting them as microservices. The old code is not used but remains. Gradually, the new features replace the old ones. You then remove the old code.
In the end, some parts of the monolith may remain. You may retain them or retire them altogether. Replace the other parts with new, refactored code.
Note that both the old and new code may use the same services and data.
The industry is moving at a rapid pace. You cannot say you are done even after executing a migration project because requirements and technologies would have evolved. So, you have to keep iterating.
Best Practice #5 – Disappointed with Lift and Shift? It’s Not Too Late to Modernize
While you can carry out migration quickly and relatively inexpensively, you get much fewer benefits when compared to modernization. We touched on this topic in the earlier section on Migration Vs Modernization. Now let’s examine the subject in more detail.
- Monoliths are not easily scalable – you cannot simply scale up a few microservices. You need to scale up the whole, massive application. The total lack of horizontal scalability drives up costs. This expense frustrates execs who assume they have a cloud application that should be cheaper to operate, more performant, and more responsive to their business needs–particularly as those needs change periodically, competitively, and directionally.
- Monoliths are complicated to change because they are brittle, fragile, and have long release cycles and longer maintenance windows. Explaining to management why releases are taking longer and maintenance windows even exist anymore – and the rising costs of those to boot–is painful. You are now in the cloud, but you still have the strongly coupled classes and the technical debt that bog you down.
- Monoliths are built as one application from a single codebase and are written in a single language that may not be the first choice for many developers. Modern applications, written as microservices, allow developers to use different languages, databases, and tools for each service.
- Everything in the cloud moves at lightning speed. There are all kinds of convenient resources and services that are useful but can also cost you money. If you are not careful, you could spend much more than you planned. And it is not possible to monitor your usage manually. You need to automate several activities and get SRE (System Reliability Engineering) skills to keep your expenses from getting out of hand.
- You are no more agile than you were earlier. You were under the impression that the cloud would help you be more agile in delivering customers’ requests. But the cloud requires several parts to work to achieve this – automation, elasticity, and scalability, uptime – which the migration has not improved.
However, there are some steps you can take to get more bang for your migration dollars:
- Keep a close eye on the cloud machines that you spin up. Devs sometimes spin up instances for testing purposes and then forget to turn them off. Ensure that all machines are turned off in the evenings and weekends (unless they need to be continuously on).
- Use the correct machine sizes. With cloud experience, you will realize what the optimum (performance and cost) sizes are for your production, test, DevOps, and database machines. Keep tweaking for best results.
- Judiciously use Reserved and Spot instances for maximum savings.
- Software licenses that you purchased for using on-premises can be ported to the cloud and make you eligible for discounts.
- Consider moving away from on-prem monitoring tools and using the cloud provider’s monitoring tools. They will work better.
- Continuously modernize. We will cover this in a later section.
The iterations we discussed in the previous section are not the first or last steps in the modernization journey. Instead, they should be a reminder that modernization is never completely done. There will always be new technologies to be mastered and integrated and more improvements that you can make. Your organization will get better with each iteration.
The most obvious candidate for immediate refactoring is your monolith in the cloud. You have already migrated – lifted and shifted – that monolith to the cloud and can now benchmark the current costs and benefits of modernizing to take full advantage of being in the cloud.
Best Practice #6 – Eliminate Dead Code or face the Revenge of the Zombies
The term “Dead Code” generally refers to source code that is not executed or is executed but the results are never used. Also known as Zombie Code (hence the title of this section), it is code that is redundant. Neither are its results used nor does it affect the rest of the program. Unreachable Code, code that will never run because of the program structure, also comes under Dead Code.
One reason for Dead Code existing is that requirements change. Developers write new code to replace existing code, but they don’t remove what was there previously. Programmers do this to reduce their effort in case the requirements change again. Unless rigorously managed by code reviews and other means, Dead Code builds up in all legacy applications.
Programmers can also introduce dead code when trying out language features or otherwise experimenting when unsure of some program behavior. They should remove the experimental code after they are done but sometimes forget.
Modernization is a great opportunity to prune the dead code instead of carrying it forward. But why are we saying that dead code removal is a best practice?
Reason 1: The risk of reactivation exists. The addition of new code elsewhere may cause parts of the hitherto dead code to become live. Newcomers to the team, unfamiliar with the program, may uncomment some dead code by mistake. The results will be unpredictable and perhaps disastrous.
Reason 2: The presence of dead code increases the program complexity. Developers, especially novices, may be unaware that it is dead code. So, they still go through the entire codebase to understand it. Also, the engineers who maintain applications may not be the ones who have built them. The dead code will confuse them.
Reason 3: Dead code takes up resources. It takes up additional disk space. The runtime uses more memory, deployments take longer, and the program loads slower. Dead code also causes static analysis tools to report incorrect metrics like lines of code. Build times increase, and tests take longer to run.
Reason 4: Technical debt accrues as dead code over time unless removed periodically. Developers continue to maintain it, and testers continue to test it. So, you are spending time and resources on code that does nothing.
Reason 5: Dead code is a potential security threat. This is because developers have probably not touched this code for ages. Secure coding practices have changed in the meantime, but programmers would not have applied those changes to the dead code. Automated scanners may have skipped that code. Hence, they may contain vulnerabilities.
Reason 6: It is difficult to identify dead code categorically. Usually, you cannot identify dead code by eyeballing it, though sometimes, an IDE (Integrated Development Editor) will identify and highlight the dead code. Another complication is that the software team may use some code in a test scenario but not in a production environment.
So, the bottom-line advice is to remove dead code from your application. And what better time to do it than during modernization? But remember that if you are lifting-and-shifting, you are not making any code changes. So, your technical debt relating to the dead code remains unpaid.
Best Practice #7 – Preventing Migration Remorse with Continuous Modernization Principles
Cloud Migration Remorse typically happens after a few months of running your monolith in the cloud. You realize that your monolith is still a monolith–expensive to maintain, hard to scale, difficult to update–with growing technical debt that threatens to bring the whole application to a grinding halt.
The best practices we have described above can help you assess how to bridge your migration tactics into an overall modernization strategy, pick out which apps to modernize, and how to effectively execute that modernization using a data-driven approach (selectively, iteratively).
Moving to the cloud requires a change in thinking compared with running on-premises. The best practices of the latter–over-provisioning, replacing hardware every few years, and keeping CPU utilization to a minimum–could lead to huge bills in the cloud.
What comes next? A strategic modernization must include a final step–Continuous Modernization–to prevent the next monolith from forming. What is continuous modernization? Think of it as a conscious effort to manage technical debt before it spirals out of control. A continuous modernization strategy helps improve your apps by applying new technologies. Specifically, you benefit from:
Cost Reduction: You can reduce costs by continuously monitoring and tweaking usage.
Improvement in Efficiency: As you learn more about the cloud, you can use cloud-native features like storage, networking, and others that your cloud host provides. Your cloud service will have tools that you can use to optimize your applications, like monitoring, alerting, and logging services.
Better Risk Management: Continuously refactor code and update your documentation to reduce technical debt.
Learning: You begin to understand how the cloud can help you beyond migration.
Tighter Security: you can improve your security posture by utilizing cloud-provided features like IAM.
Automation and DevOps Culture: You can improve the maturity of your CI/CD pipeline. Your cloud provider will likely provide a highly scalable infrastructure for hosting your build and deployment pipelines.
Include modernization checks in your CI/CD pipeline, so every new commit and release is tracked and baselined for things like:
- signs of dead code
- growing dependency entanglements
- aging frameworks requiring updates, security checks
- topology complexities creating over-chatty microservices
Hence, the conclusion is not to stop after migration. Follow continuous modernization principles to get all the available cloud benefits.
Use a Modernization Platform to Increase Your Chances of Success
In this article, we have taken a close look at the topic of migration (lift-and-shift). Migration is quick and straightforward but limited. You get some instant wins, but you can do so much better. Nevertheless, migration is a starting point, the first step in this process, and therefore, crucial.
On the other hand, full-blown modernization is the real deal. You end up with a microservices-based application that is easy to develop and maintain and a DevOps practice that can deliver customer updates several times a day.
So, why do many companies just dip their toes in migrations rather than take the plunge with modernization? The motivation, to some extent, is that migration offers some simple and immediate benefits.
The more important reason is that modernization is challenging and a bit scary. It is time-consuming and expensive. The fear of the unknown lurks everywhere. The risks are overwhelming. It is like going into a long, dark tunnel without knowing if you will emerge at the other end.
So, you need to take a strategic look at your modernization options. There are modernization platforms available that can automate all the steps we have discussed. They can perform an automated assessment that will map out linkages between classes and identify tech debt in the form of dead code.
Such platforms use powerful static analysis tools to understand your application in-depth and AI-powered algorithms that automatically break up a monolith into microservices. This kind of platform is scalable and repeatable. It can handle megaliths (huge monoliths) predictably and rapidly and deliver with a high rate of success.vFunction is the only application modernization platform that does all this and more. Contact us to set up a demo and see how we can help you.