How to tackle legacy code: Divide and conquer

How to tackle legacy code: Divide and conquer

"But the joy I felt soon became tempered by the realization that software systems almost always degrade into a mess.” - Robert C. Martin

It's rare that a software engineer starts a project from zero, to have influence on the architecture, the framework decision, lay the foundation of the code and spend a significant amount on implementation. This means, every software engineer working in some extent with legacy code in their career. There are many definitions of legacy code, some going that far that a code is legacy code as soon as it’s written. Most of the time source code what the team inherited and usually was built by others, using older technology, framework, language.

There are theories that rewriting an existing software (almost) never a good idea. This is also true for a startup, where the system is under rapid development, months after months significant new features are developed, existing functionality altered multiple times and over the years cumulative changes are in the codebase. The system become complex, codebase grow, technical debt usually high, dependencies are likely a mess. Often the early solutions which potentially served the system well, not the right fit for a bigger system anymore.

At PlantMiner we also inherited the codebase. We consciously aiming towards a solution which allow us to continue working with the original code, add the necessary new features and maintain the longevity of the application. The original system was a monolith, the whole codebase was in a single application. 

We decided to split the system into smaller parts one step in a time, and move towards a collection of loosely coupled applications. There are many reason behind the separation, one important is to reach a size for each application, that is small enough to be easily replaced by an other application which potentially using a different technology. We overgrew the original programming framework, it server well us for the beginning, but the system became bigger, we faced many limitation. We were looking for ways to leave the old code behind, but the size and complexity of the system not allowing us to maintain the speed what the business requires with new features development.

Our effort is divided into two types of separations. One focusing on the business domains. We separated system areas depending on specific user types. We moved our internal client management functions into a separate application in a way that we duplicated the original code, removed the unnecessary parts, setup separately on subdomains and put redirects in place on both applications. At the end the two worked independently and we assigned a dedicated teams for each. Application dependencies are not causing issues anymore, the database is the only commonly used element. Using the same method we also separated the account area for the two sides of the marketplace.

Parallel with the above we started extracting out common functionalities from each applications. These service apps are brand new, so we chose a better fit framework, redesigned the structure and removed the original code form related applications. We started building up our internal API, which one day will be the only data access layer. Also separated all transactional emails, sms and other communication from the system into a message queuing system. Next step is to move the authentication into a separate service and constantly looking for opportunities to repeat the same with other common parts as well. Any new feature can be build using the new methods and parallel we are also working on transferring existing functionality into the new structure. It’s a constant work in progress without an end date, but we already see immediate improvements in various areas because of the reduced dependencies, which often the reason of regressions.

There are plenty of benefits having much smaller applications. It’s a cleaner separation of concerns, every application is handling a well defined functionality. Each application can be assigned to a team, which can develop and release new features independently from others. The system and the team around more scalable. Because each application well separated from the others, it can be hosted on a load balanced collection of application specific machine instances varies for example in memory or CPU power. The architecture also a good fit for a containerised setup. Smaller applications also easier to rewrite, replace even with a different framework, language or technology. Beach each team need to focus on a smaller codebase, it’s easier to be familiar with the whole, new team members can be productive earlier.

With the above separation and constant improvements, we can develop a system which can serve us long term. Potentially we can replace each applications one after the other and in the transition no need to maintain parallel codebases for long. Divide and conquer originates from Ancient Greek times, where by braking up concentrated power into smaller pieces makes it easier to overrule. Similar idea with a bigger codebase which can be separated into smaller applications for faster future development and easier maintenance.

Change is the only constant, we can continue splitting each applications further into smaller components and long term we can reach a point where the system is just a collection of separate and independent functions, which could lead us to a serverless architecture.

How do you tackle legacy code?

Sometimes there is a lot to learn from older code bases, but starting over sometimes makes more sense because you can simplify your previous knowledge into something that is overall better.   I usually at least keep the functionality that makes most sense from the legacy code and throw out the rest.

Like
Reply

This is the story of all the monolithic applications which got developed before the concept of Interfaces came into existence. We have done the same with couple of big monolithic applications. Interfaces/COM brought a paradigm shift in the way application architectures were conceived.

Employing microservices, component-based architecture, and domain-driven design are really great help on ever-evolving enterprise systems.

Absolutely the way to go. Very common sense approach to solving an issue that nearly every tech company faces at some point. It’s also true about no end date, software is never “done” it’s always growing to help customers get more value out of it.

To view or add a comment, sign in

More articles by Tibor Halasz

  • Age of - remote - meetings

    I started to write this article before the COVID-19 isolation, which continued later when most of the companies worked…

    2 Comments
  • Evolution of our GIT workflow

    As a growing Engineering Team we constantly evaluating our approach how to build software and adjusting our processes…

    15 Comments
  • How to set WIP limits in Kanban

    PlantMiner is Australia largest online construction marketplace. Like most of engineering teams, we also adopted Agile…

    7 Comments

Others also viewed

Explore content categories