From Legacy Projects to CI/CD Pipelines
Recently I received the maintenance responsibility for two legacy projects in my company. One of the projects is 8 years old, the other is so old even our tester doesn’t know when the project started — definitely before he joined 12 years ago. Obviously, both projects use pretty old technologies like Ant-scripts and both have many features which aren’t even used by the customer any more.
So how do you deal with such an old project — as a motivated and proactive engineer?
In this blog, I’ll show you how you can make the best of such a situation, and how you can have a positive and significant impact. 🚀😎
CI / CD Theory
First off, here’s the theory for CI/CD, which stands for continuous integration, delivery and deployment:
From what I hear, there’s some misconceptions out there as to what CI/CD really means. In the graph above, you can see there are three parts to it:
- bringing the software to a test system
- and finally releasing it to the productive environment
Flee from the Problem?
Now if you’re ever stranded on a legacy project, one obvious solution is to polish up your LinkedIn and start sending out resumes😜
Remember though that every company has legacy projects. So rather than complaining, this article is about the positive impact you can have on a legacy project.
Before we get into the details, let me just mention that I’m lucky enough to only work part time on these legacy projects — the rest of my time, I spend on developing new software from scratch. There’s a brand-new web service I’m currently working on that uses the latest Java version, Spring Boot, Docker, an elaborate CI/CD pipeline and cloud-hosting, so this keeps me happy😊
When I started on these 2 legacy projects, the first thing I did was migrating to a decent version control system. Both projects used SVN for versioning, but luckily, migrating to a new version control system is straight forward and worth the time. So, I used
svn2git and created new Git repositories. After completion, I could use branches for quick experiments, plus
git is much, much faster than SVN, and finally, you can perform commits without the need to immediately push your changes to the server.
Next, I analyzed the build system of my legacy projects. The question looks easy: how do I compile this code? Well my legacy projects consisted of many sub-components, some of which were easy to compile, some of which were hard to compile. There were easy cases of plain old Java-Maven projects where I could just run
mvn clean package. One particular component though was much harder to compile: it was an old Grails app and getting all dependency versions right like Grails, Java and Maven was tricky.
In the end, you want to have a terminal command that will compile the project for you. Once you have that, you’re good, since you have a command, and this can be automated. So, in my case, I set up a Jenkins job for every component which performed the building process for me.
Note that in rare cases, there might be a need to re-engineer the compilation process. I’ve seen Java projects that use Ant and checked-in Jar dependencies. This is an outdated and cumbersome way to do things, and it would really make sense to modernize such a build system. Moving to Maven or Gradle and using a registry for dependencies would ease the pain here.
Is your application secure and your code clean? An easy way to advance on these questions and possibly improve your code quality is by using a static code analysis tool. Inspecting your code is non-intrusive, all you do is add a new step to your continuous integration pipeline. There are many great tools out there. For my projects I chose SonarQube
Deploy to Test System
First off: yes, you need a test environment. If your legacy project doesn’t have one, make sure you get one!💪 You can figure out the dependencies from the productive environment. Installation of third party software like the runtime environment will be easy, however installing (mock versions of) the external systems might be trickier.
Anyways, the specific legacy project I worked on used Ant scripts to deploy the distributable Java archive on the test system. What the Ant script basically did was take the tarball, copy it to the test system machine, inflate the package, stop the old application, and start the new one. The command for starting the application was a Java command including all kinds of important parameters like the JVM version, Maven configuration file, the log file location and so on. As it turned out, it was pretty simple to automate this deployment: just use some CD tool like Jenkins and run the Ant script. There were still some bits to get right, like adding additional support Jars to the class path of the Ant command, but I figured that out too.
If you ever work with a legacy app, your technology stack might be different from mine, but the basic concept of bringing your app to the test system and running it there remains the same.
Anyways, now I had automated things far enough so I could just hit a button to turn my source code into a running application on the test system. Now the last step was to automatically run end-to-end tests. For my legacy projects, I was lucky to have a plethora of existing E2E tests. My specific project used Java FitNesse tests. Since Jenkins offers a FitNesse plugin to run such tests, my job was straight forward.
Now what do you do if you have no automated E2E tests? Well the most basic thing you can do is perform a health check. Every component should have an HTTP health endpoint which will tell whether the app was started properly and if all external systems can be connected to like the database or queues. It’s even better though to perform functional tests. If your app has a REST or SOAP interface, you can use it to perform more advanced queries like read/write/update operations. Anyways, the important part is to program out these tests, so you don’t have to manually fire requests via Postman or SOAP UI. Moreover, if you’re starting from scratch with new E2E tests, there are many modern and powerful frameworks out there, like Cucumber and Gauge.
Remember to pay attention to external systems. Since you are performing end-to-end tests, you want to make sure all surrounding systems are present on your test system. Most often, engineers will create mocks for these systems with the most important functionality replicated, or you can ask the maintainers of those externals systems if they already have a mock.
The Final CI/CD Pipeline
Here’s a snapshot of the final result, the Jenkins pipeline for continuous integration and delivery:
Notice that I hadn’t changed any line in the old source code of the legacy app, yet I had made the maintenance so much more fun and easy.🎉🥂
If you ever work on a legacy project yourself, I hope this article will give you some helpful information. Cleaning up legacy code will mostly be tough, plus the customer will probably not want to pay for it. Instead, what I suggest is to first focus on everything surrounding your code base. With a CI/CD pipeline in place, you will be much more motivated to start on a change request, since you can just hit one button which will verify your change all the way from compilation to packaging, deployment and E2E testing.