This post is part of a series about what we cover in our Software Design and Development Clinics, a 1-week series of classes where we dive into various aspects of software design and development principles. Legacy application migration is a core focus of this class. Learn more at http://dontpaniclabs.com/education/.
We have never done any formal polling to find out if most developers work on greenfield or brownfield systems, but our informal analysis would lead us to believe it is mostly brownfield, and brownfield by a large margin. Brownfield projects are legacy systems that have often outlived the original developers’ expectations and are now showing their age. At the other end of the spectrum are greenfield projects, which are new systems.
When is a system considered legacy? There is no easy way to define it, but I think the moment people start talking about doing an entire rewrite they are probably talking about a legacy system. Sometimes people will mention rewriting a system even before it ships, that probably isn’t a good sign.
Why do people want to rewrite these systems? They work, right? What makes people think working software needs to be thrown away? Well, it often comes to pain. People find the software painful to maintain and extend. Engineers find it painful to work in and business folks find it difficult to get the value they want in a timely manner.
Shouldn’t we just push through the pain? Won’t it get better?
Usually the vague “pain” is really a mask for many design smells that are wafting from the bits. The system is probably “fragile”, which means the system is easy to break. Another smell we will often receive is “rigidity”, where the system is often difficult to change. Yet another smell that often signals a system in real trouble is when the system makes it difficult to do the right thing, which is called “viscosity”.
If we are getting these smells it is time to rewrite, correct? Well, not so fast. In general, doing the big rewrite is the wrong decision. When a customer comes in and asks for the big rewrite we usually pump the breaks and encourage them to slow down and think it through.
Rewrites are typically a bad idea unless:
- The system is young
- Core requirements or intended use for the system have changed significantly
- Entire existing system is unmaintainable
If a customer comes in with a “let’s rewrite everything request”, we first try to discover some of the software’s core problems. We will listen to the business owners and developers, and we try to learn about their pain. This often leads us to the design smells, which will then point us to some of the core problems in the system. From this good starting point, we now should know how the system currently functions and where it is deficient.
Based on what we gather, we will have enough information to create a design for a new architecture that will better encapsulate the existing volatility into logical subsystems. Once we have a good final architecture, we will have a solid end design for the system. In other words, we have a plan.
If you don’t know where you are going, you might end up somewhere else. – Yogi Berra*
Once we know the starting and end points, we can create a roadmap to move the legacy system functionality to the new system.
But, there is a secret to doing this…YOU HAVE TO PROVIDE BUSINESS VALUE WHILE DOING THE MIGRATION. We have to make bringing business value a priority during the migration. We can’t do the migration for the sake of doing the migration.
The business value can mean more features, but it doesn’t have to mean more features. Business value could be realized through faster performance or better stability or even better scalability.
To do the migration we often use the strangler application pattern created by Martin Fowler. In this pattern, we slowly inject the new architecture into the existing architecture. Over time we will be left only with the new architecture; the legacy architecture will be just a memory.
When we find a good candidate for a migration, what do we do? We start by writing some integration tests, which we use protect the existing system behavior. These tests will be what we use to verify that our migration was successful. If we break these tests, we probably failed.
After creating the new tests, we will stub in the new architecture. Then we will update the existing code to call the new architecture. After we update the existing code to call the new architecture, we will have broken the unit tests. D’oh!
Now we need to implement the new architecture. While this is going on we should be fixing those broken unit tests. Also, we should be adding new unit / integration tests for the new (and hopefully better architecture). Once all of our tests pass, we should have a better system.
It’s important to remember that it’s going to be messy while you’re working on a legacy migration. That’s why you come up with the end game first. Create a plan and then constantly push through to the end. You might end up with duplicate code or have a system that doesn’t provide a completely clean migration. Just stay focused on the prize (and keep providing value all along the way).
Now to recap the steps for the migration process:
- Develop your ultimate design
- Look for opportunities
- Project existing legacy behavior (Façade / Integration Tests / Regression Tests)
- Identify the seams or interfaces in legacy
- Sketch out changes to the legacy design
- Create stubs and supporting files
- Execute changes
*When you see Yogi comments you should always remember the title of his book, “The Yogi Book: I Really Didn’t Say Everything I Said!”.