Unlike some of the legacy platforms, the .Net platform is a more thought out and strategically planned development platform, incorporating lessons learned and avoiding mistakes of earlier platforms. It’s similar in concept to the Java platform in that both platforms rely on a virtual machines to accept intermediate language code compiled from diverse 3GL languages and to provide final execution environment. Target of our analysis here is the .Net platform, so, let’s focus on some key areas that a migrating development team should carefully consider.
Follow migration objectives
Corporate objectives behind a migration should be explicitly identified and prioritized, because this will drive target platform choice, architecture and design decisions and mitigate among conflicting directives. Some examples of general objectives are:
- Extend application life – an older platform application may become progressively isolated and unsupported, yet still have business value to the organization, thus prompting migration rather than obsolescence
- Reduce application TCO – enhancing and/or supporting an existing application written on an older platform may become progressively more expensive and eventually lead to a tipping point whereby leading to the decision to migrate. This may occur for individual applications or a development environment as a whole encompassing a broader initiative
- Remain competitive – aside from lowering TCO of application space, technology driven solutions have the potential to help business processes be more efficient at execution
Fundamental .Net concepts
Some of the building blocks of the .Net framework may be unfamiliar to development teams experienced in other platforms. A solid understanding of some of these key concepts is crucial before majority of the .Net development begins, otherwise another beginning could also simultaneously occur, the maintenance and support nightmare especially pertaining to memory leaks and sluggish performance. The single most fundamental concept of the .Net platform is the Common Language Runtime (CLR). It is the virtual machine, mentioned earlier, that forms the core of the platform. Some other key concepts are:
- Full object orientation – everything in .Net inherits from object, some explicitly and others implicitly. Only single inheritance is supported whereby a type may inherit from only one type but may implement multiple interfaces, which may be used to implement polymorphism. An abstract type may be used to implement the most basic form of abstraction. Encapsulation is supported in 5 access modifier scopes: public, protected internal/friend, internal/friend, protected, and private
- Supported types – there are two high level types: value type and reference type. Value types form the basis of primitives, such as Integer, Boolean, etc., which give the platform a substantial performance boost that lacked in older platforms like SmallTalk. Value types are dynamically boxed/unboxed to support implicit inheritance from object
- Garbage collection – out of scope object instances are garbage collected, eliminating the need to explicitly reset them, unless they implement the IDisposable interface, which identifies an object to be or aggregate unmanaged resource(s), e.g., non-CLR type(s) and that it must be explicitly disposed before it may be garbage collected. Microsoft recommends a design pattern to cleanup disposable objects
- Multiple concurrent language support – a .Net solution may contain multiple projects, each in it’s supported language of choice, e.g., one project may be in VB.NET while another in C# and yet another in F#
- COM support – .Net supports a two way COM interoperability framework called the COM Interop. Any .Net library may consume or be exposed to COM objects via the COM Interop framework. Consumption is automatically handled by the IDE, which dynamically creates interoperability wrappers at the time of referencing, while exposure must be explicitly configured to avoid unintentional performance lags. Keep in mind that COM Interop is very expensive and should be avoided when possible
- Delegates – these are thread safe function pointers, e.g., c-style pointers with a strongly typed signature. The .Net event model along with countless other framework aspects have delegates at the core
- Generics – it’s the support for the use of templated types. A generic server may declares one or more generic type(s) in the type/method declarations and use them as correspondingly scoped type instance and/or parameters
Migration steps
Easiest and the quickest solution may be to run the migration wizard and manually resolve any resulting errors and call it a migrated application. However, that’s also potentially the most expensive approach, mainly because coding intentions are not accounted for and what may be a widely accepted good practice in one platform may be blasphemy in the other. Depending on the specifics of the to and from application types and platforms, the entire end product may run against the grain and may raise nightmare support and maintenance scenarios until re-written. A common migration scenario is moving from classic VB smart client application to ASP.NET web application. Timely developer training is the high risk scenario here, because moving from a stated environment mindset to a stateless one requires a good training schedule and an elongated transient period to sync in. Your context and exact scenario will dictate the specificity of your migration steps, but, here’s a rundown of the general steps:
- Prepare the source codebase – if it’s an option, it may be worthwhile to refactoring the codebase at the source and separate the model, view, and the controller tiers because it may be more expedient to do so in the source platform and because it reduces potential manual operations mainly centered around the UI (view)
- Label the codebase in source control
- Develop unit & integration test scripts, if not already available. These scripts would likely be easily transferable to the target platform and be used to certify both the initial and the final product
- Execute all test scripts and archive reports in source control
- Refactor the codebase and move as much logic out of the UI tier and into the middle tier(s) as possible. Note that this will have an impact on the overall application because the resultant public API will be altered and should be considered carefully for potential negative impact before refactoring
- Label the codebase in source control
- Prepare destination databases – the migrated product may or may not use the same databases as the pre-migration product. If the databases are the same then skip this step otherwise backup the source database and restore it as the destination database, so, you have an exact snapshot replica with both data and schema
- Generate the destination codebase – if a wizard driven migration is applicable, e.g., same before and after application types, initiate the wizard by starting the legacy application with the appropriate .Net IDE
- Let the wizard drive. If a wizard driven approach is not possible for the entire application, e.g., moving from classic VB to ASP.NET, consider migrating individual libraries one at a time using the wizard or using online language translators or manually typing, whichever is more expedient
- Review and resolve errors, there will generally be some conversion errors, review and resolve each as appropriate
- Add the project to source control
- Migrate all the test scripts to .Net using the migration wizard
- Execute all test scripts and verify expected behavior and archive reports in source control
- Label the codebase in source control
- Refactor the final product – this is likely to be the most time expensive part of the operation. Depending on how the application was originally written, a complete rewrite may be in order
- Reverse engineer existing design artifacts and compare and map them to existing domain models. Domain driven designs produce some of the lowest TCOs, as a general trend, regardless of migration
- Isolate different processes within the application to the extent possible with an eye on SOA, because that’s the upcoming and potentially prevalent platform, whereby, applications maybe rendered meaningless in its current sense and services interacting via discoverable interfaces (e.g., XML), regardless of their underlying platform, would form the basis of our software space
- Wrap service layers around the different and independently serviceable modules, if applicable
Every step of this potentially lengthy migration process, keep in mind the next migration for this very application and how it could be made more portable without sacrificing performance.