There are a few choices when it comes to migrating software. You can do a Full rewrite or an Incremental port.

Approaches

Full rewrite

A full rewrite is tempting, as you don’t have the baggage of technical debt of the previous iteration. However, it has the following issues:

  • Leads to a halt in shipping new features; long time to value
  • You run the risk of chasing a moving target, of having to keep the new codebase in line with the changes in the post codebase
  • Rewrites lead to edge cases as the port isn’t fully 1:1 with each other
  • It leads to a lot of unused code. Even if the code is well-tested, there are risks of bugs. Productionized code should be exposed to traffic to get feedback and build confidence, sooner rather than later.

Incremental port

The first step is usually the hardest, as a lot of groundwork has to be laid out. However, this has the least risks, as the changes are incremental, and there’s a single-source of truth for your codebase.

Learnings

Porting takes preparation

A lot of strategizing and testing is required. In terms of strategizing, identify and derisk the trickiest parts of the codebase, or the parts where there’s the highest amount of uncertainty. This can include POCs, and testing.

Testing also helps to nail down important code behaviour, including edge cases. Writing tests before coding may not work out super well, because you may bring over the idiosyncracies of the previous codebase or language with your migration, and you don’t get to take advantage of the benefits of your migrated language or platform.

What might be helpful is to do the following instead:

  1. Use integration or end-to-end tests to cover the initial migration. E2E should also be immediately applicable to the migrated code since it’s usually a generic call.
  2. Implement migration
  3. Create unit tests that mirror the intended tests from the old codebase.

https://vercel.com/blog/how-turborepo-is-porting-from-go-to-rust