Modernizing legacy application is a journey of consistent and iterative takes rather than a big bang. Your modernization journey should adhere to following tenets:
- With minimal to no risk, quickly build confidence in your modernization approach (i.e. blueprint).
- Minimize disruptions to other developers working in parallel on your existing system.
- Continuously show incremental modernization gains with the flexibility to pause/stop modernization while retaining the investment.
To help achieve these goals, in this module you will learn about baseline approach to modernization and two modernization patterns: 1/ Strangler Fig and 2/ Branch by Abstraction.
Anecdote: There is no silver bullet, perfect architecture, or perfect approach. Avoid analysis paralysis and move forward with trade-offs that work best for your organization.
click to expand
Let's review common hurdles that Enterprises may face on their modernization journey.
-
Where to start chipping away at the legacy application; identify specific component(s) to modernize first.
-
You may find yourself in conundrum: how to efficiently leverage in-house bandwidth to achieve balance across parallel tracks like:
- Continue to ship bug fixes and features for your legacy application.
- Support both legacy and modernized code-bases: share common libraries, copy/fork the code, etc.
- Ensure the legacy and modernized versions continue to co-exist with minimal to no interruptions.
-
Additional hurdles to be mindful off (not covered here)
- Transition Authentication/Authorization from home grown solutions to a managed Identity Provider (e.g. Okta).
- Move database access layer towards object-relational mapper (ORM) frameworks like Entity Framework Core.
Sections below will provide suggestions to help address these common problems.
click to expand
Following steps should be the starting point to plan your modernization journey.
-
Overall, follow the mantra to modernize legacy application in small increments (e.g. component by component).
- While modernizing, delay or freeze any functional or behavioral changes. Otherwise, you will have to accept that roll-backs would become harder.
- Duration of component modernization should be between few days to weeks. The longer it takes to modernize components, the more pressure/risk you may incur to allow behavioral changes into the legacy version of the component (e.g. bug fixes).
-
Start with the analysis of your legacy application (yes, captain obvious here!)
- Build out a dependency graph that shows 1/ breakdown of application's components (your services and 3rd party libraries) and 2/ relationships across components.
- Analyze portability of these components to .NET Standard 2.0 and .NET Core. Targeting .NET Standard 2.0 will help you share common libraries between your legacy (.NET Framework) and modernized (.NET aka .NET Core) codebase.
- In your organization, identify component owners to 1/ validate your analysis and 2/ coordinate modernization effort and timelines.
You can leverage no-cost tools like AWS .NET Extractor or AWS .NET Porting Assistant.
FYI: .NET Framework 4.6.1 is the earliest version to support .NET Standard 2.0.
-
From the analysis graph tree, identify leafs as the starting point; component(s) with minimal incoming dependencies.
- You want the team to quickly build a modernization blueprint: 1/ gain confidence in porting approach (e.g. .NET Framework 4.7 -to- .NET LTS), 2/ stand-up necessary infrastructure, 3/ establish an observability approach for your distributed system, and 4/ build out the DevOps mechanisms to enable Continuous Integration (CI) and Continuous Deployment (CD).
-
Also, for first round of components selection, go after high business value. This should help ensure your company continues to invest in modernization efforts.
-
Optionally, where possible, ring-fence the components to modernize by business domain (unit of function).
- Approaches like Domain Drive Design or Event Storming can help define the boundaries. These approaches do require time investment across SMEs working collaboratively: developers, architects, business SMEs, QA, and UX.
-
Strongly recommended: Adequate unit/integration test coverage to help validate the modernized components and incorporate in your automation (CI).
- Adequate test coverage is a common challenge. In its absence, ensure to plan for ample manual functional validation time for your modernization journey.
click to expand
With the modernize baseline in place (discussed above), pick one or mix the modernization patterns below.
-
Strangler Fig
-
When to use it: Suitable to modernize component with 1/ minimal to no upstream dependencies and 2/ outside system calls can be intercepted at the perimeter.
-
In depth walk-through: Click Here
-
Example below illustrates this pattern.
-
-
Branch by Abstraction
-
When to use it: Suitable to modernize component that are deeper in the call stack with upstream dependencies.
-
In depth walk-through: Click Here
-
Example below illustrates this pattern.
-
click to expand
-
Prior to modernization, ensure to discuss and adapt a development strategy that works best for your organization. Common approaches:
- Trunk based development with feature flags.
- Short lived feature branch.
End goal is to avoid keeping your changes on an island for a long time and then deal with reconciliation pains; merging back to development/master.
-
If you’re still maintaining the ASP.NET app, it may be helpful to avoid static references to ConfigurationManager and replace them with access through interfaces. This will ease the transition to ASP.NET Core’s configuration system.
-
Logging: You can reference the Microsoft.Extensions.Logging package from .NET Framework apps as long as they’re using NuGet 4.3 or later and are on .NET Framework 4.6.1 or later. Once your app has referenced this package, you can convert your logging statements to use the new extensions before migrating the app to .NET Core.
click to expand
-
[Blog] - Branch by abstraction pattern
-
[Blog] - Feature flag toggle approach - v1
-
[Blog] - Feature flag toggle approach - v2
-
[Tool] - AWS .NET Microservice Extractor: Simplifies the process of refactoring applications into independent services.
-
[Tool] - Scientist.NET (gitHub link): A .NET Port of the Scientist library designed to perform experiments comparing multiple implementations of a solution to each other without introducing adverse effects to end users.
-
[Tool] - Visual Recode (paid product link): Tool to help upgrade WCF to gRPC for .NET Core.