Strangling Old Applications

in Software Engineering, DevOps

Everyone at some point or another works on an old application with a shed load of technical debt. Sometimes this has come about through lots of little hacks gradually making the code worse, sometimes it's just been badly designed and maintained. Maybe it's something you've built or maybe you inherited it. Either way, there reaches a point where you end up thinking "maybe we should just rewrite it".

There are lots of good reasons not to do this, but if the amount of technical debt gets too high and the application gets too hard to change, there are better ways of rewriting than just making another version in parallel and switching it over once you've built it all.

One approach that we use at code is the strangler application pattern. Using this approach we gradually rewrite the old functionality in a new more maintainable application that we can start using straight away. There is an overhead here as we will be running both applications along side each other until we've completely 'strangled' the old application.

A common case we see here at Code is old CMS based websites, where the CMS and it's plugins are now so out of date that the cost of upgrading it would be prohibitive. We've recently been working on such a website where the old Umbraco V4 site couldn't be easily upgraded due to the amount of customisations that had been made to how the CMS worked. The CMS's APIs had changed in each new version and it was now 3 major versions behind.

We created a new application with the newest version of Umbraco V7 (and were careful to decouple the website from the application to avoid this happening again in the future, but that's a story for another day). We then added a proxy to reverse proxy traffic to the old site with specific rules to send traffic to the parts of the new website we had already built.

To do this we used an IIS web.config file, Application Request Routing (ARR) and the URL Rewrite module. We set the old website on a different hostname in IIS. In this example we'll use v4.myhostname for the old website and v7.myhostname for the new website. We didn't want these available externally so we also moved them to a different port which is blocked externally by the firewall. The two new hostnames need to be added to the hosts file too.

We have a simple rewrite rule that sends all traffic to v4.hostname by default.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.webServer>
  <rewrite>
    <rules>
      <rule name="v4" stopProcessing="true">
        <match url=".*" />
        <conditions>
          <add input="{HTTP_HOST}" pattern="^myhostname$" />
        </conditions>
        <action type="Rewrite" url="http://v4.myhostname:81/{R:0}" />
      </rule>
   <!-- ... -->

It's worth noting that the action type here is Rewrite, not Redirect, which means that ARR will undo the rewrite on the response so the user will never see our rewritten url. There is an option in IIS on the ARR feature's Server Proxy settings called Reverse rewrite host in response headers, which is turned on by default. If we turn this off the rewritten url will be exposed the response, so make sure this is ticked, especially in this case where we are rewriting to a different port as this may not be accessible externally.

Now all our traffic which is coming in for hostname is being reverse proxied to v4.hostname. Next we need to set up sending particular sections of the website to v7.hostname. We do this by matching on the URL. In our case the section of the website we want to turn on is going to be called under /users. We can add a simple rewrite rule above our default rule to send traffic for this URL and everything under it to our new V7 application.

<!-- ... -->  
        <rule name="v7" stopProcessing="true">
          <match url="^users.*" />
          <conditions>
            <add input="{HTTP_HOST}" pattern="^hostname$" />
          </conditions>
          <action type="Rewrite" url="http://v7.{HTTP_HOST}:81/{R:0}" />
        </rule>
        <!-- ... -->

Now any requests to /users and anything under it (eg. /users/james) will be sent to our V7 application and anything else will be sent to our old V4 application.

Over time as we are able to "strangle" more of our old application and move more functionality into our new application we can add more pages to the regular expression that will send traffic to our new application.

This isn't the only approach to tackling technical debt and certainly not one that should be undertaken lightly. You will be maintaining two code bases along with potentially two lots of infrastructure. But if you really have to do rewrite this approach allows for a relatively simple, incremental approach.