While we’d all love to be working on new technologies, nice clean code and have a generally easy life all of the time — this can’t always be the case. At some point in our careers we have to lift the veil of a legacy software system and gaze at what is beneath… “Oh wow… Struts 1, SOAP and ANT with no dependency management… Beautiful… 🤮” What can we do to make this somewhat situation a little more tolerable? undesirable Docker… Dockerise All Of The Things is a tool designed to make it easier to create, deploy, and run applications by using containers. Containers allow developers to package applications with all of the parts it needs, such as libraries and other dependencies, and ship it all out as one package. Docker Incorporating Docker into a project should become and I’d recommend is the first thing you do — before you even write any actual code. like a reflex You need to maintain an application which runs on an outdated application server, has no tests and no dependency management. You need to share this software environment with other’s in the team but installing the dependencies is a considerable effort. An Example: Write a for deploying the application and its dependencies. Create a Docker container / image for anything else pertinent to running the environment and use something like for orchestrating the containers in your multi-container development environment. A Solution: Dockerfile (e.g. a development database) Docker-Compose Benefits: You can now easily share the software environment with others in the team and they can get up and running working with the application relatively quickly. The environment is documented by in case it needs to be reproduced elsewhere. Dockerfile Acceptance tests can be written against the Docker container and a CI pipeline can be created for the application without the CI server requiring any knowledge of the software environment in which the application runs. You have opened up an option for containerised deployment of the application in production. Here’s a toy example from a project I’ve worked on in the past. The project used as an application server, to build the project and had no dependency management or unit tests: GlassFish Ant Dockerfile: FROM glassfish:latest LABEL maintainer="andymacdroo" COPY dist/App.war /App.war COPY lib/mysql-connector*.jar /usr/local/glassfish4/glassfish/domains/domain1/lib/ext/COPY scripts/configure-glassfish-datasource.sh /configure-glassfish-datasource.shCOPY scripts/deploy-war.sh /deploy-war.shRUN /configure-glassfish-datasource.sh EXPOSE 4848 8080 8181 # Start asadmin console and the domainCMD ["/deploy-war.sh"] And to build, simply run in the project’s directory: docker build -t andymacdroo/app . deploy-war.sh: #!/bin/sh asadmin start-domainasadmin -u admin deploy /App.warasadmin stop-domainasadmin start-domain -v : configure-glassfish-datasource.sh #!/bin/sh asadmin start-domainasadmin create-jdbc-connection-pool --user admin --restype javax.sql.DataSource --datasourceclassname com.mysql.jdbc.jdbc2.optional.MysqlDataSource --property "user=root:password=root:url=jdbc\\:mysql\\://mysql\\:3306/myDB" myDB;asadmin create-jdbc-resource --user admin --connectionpoolid myDB jdbc/myDBasadmin stop-domain docker-compose.yml version: '3' services:mysql:image: mariadb:10.3container_name: mariadbvolumes:- container-volume:/var/lib/mysql- ./data/dump.sql:/docker-entrypoint-initdb.d/dump.sqlenvironment:MYSQL_ROOT_PASSWORD: rootMYSQL_DATABASE: myDBports:- "3306:3306"myApp:image: andymacdroo/app:latestcontainer_name: appports:- "4848:4848"- "8082:8080"- "8083:8081"links:- mysql volumes:container-volume: With this setup, the environment can be easily ported to others in the team and is completely disposable 🤢_)._ (you also don’t need to dirty your own development machine with obsolete software The Adapter Pattern Do you actually need to write any code within the legacy project? Can you avoid doing so altogether? It could be helpful to consider the current legacy code or application as a 3rd party that you can’t control or change. What could you do? In many situations you can leverage the — taking legacy software output and converting it into a form that can be consumed by a new application or a system that you’re migrating to. adapter design pattern You need to migrate a legacy daemon process which takes data from a queue and makes API calls, to make those API calls to another 3rd party API. An Example: The code has no unit tests of any kind, is very difficult to read and has lots of embedded business logic and branching. The request body between the old system and the new 3rd party system are quite different. Write an adapter function to slot in between the and the new 3rd party API — the job of this adapter is to convert the current request body that gets generated into a form the 3rd party can consume and understand and to forward it to the 3rd party. A Solution: legacy daemon process You then just need to point the legacy process at the new adapter function and it takes care of the rest. Consider implementing your adapter using a serverless platform, e.g. Note: AWS Lambda or Google Cloud Functions. Be a Brain Surgeon, not a Steam-Roller It can be really tempting to take a legacy project and begin changing absolutely everything. But without good tests , what’s to protect you from introducing a defect or changing functionality in a major way? (as most legacy code seems to be without) Write tests that seem to be missing or broken, but if this is impractical, only change what be changed. must Imagine being a brain surgeon, delicately performing micro-surgery on an extremely complicated organ that no-one fully understands. You won’t truly understand the pain and anxiety of having to debug a legacy application — after you’ve casually implemented a massive sweeping change and there’s been a critical incident in production, until it’s happened to you. Trust me, it isn’t a fun experience… 😢 And, if you are writing additional tests… TDD — Red, Green, Refactor Write a test for a given unit of functionality / feature. Run the test; ensure your newly added test fails. Write the minimum amount of code for your test to pass run the test and ensure it passes. (don’t care about code quality here) — your code ; re-run your tests to ensure your code still passes_._ Refactor (now care about code quality again!) _Map out the tests you need to write, implement the code and move on to the next test. Eventually you get a work of art._medium.com TDD: Paint by Numbers Programming The Developer’s “Hippocratic Oath”: The Boy-Scout Rule Newly qualified medical doctors typically abide by the pledge / oath: “Primum non nocere” — “First, do no harm” For developer’s we should go one step further and follow the : Boy Scout Rule Boy Scouts believe in leaving the campground cleaner than it was found, developer’s should embrace this with the code they work on This is for one simple reason: : “Broken Window Theory” One broken window, if left unrepaired for a substantial amount of time, instills a sense of abandonment. So another window gets broken. People start littering. Graffiti appears. Serious structural damage begins. In a relatively short time, the building becomes damaged beyond the owner’s desire to fix it, and the sense of abandonment becomes reality — James Q. Wilson and George Kelling (1982) The same situation can happen to any code-base if we cut too many corners and don’t make the effort to maintain projects: avoiding repaying technical debt when opportunities present themselves to do so. Do you want this to eventually happen to your new projects? All legacy code and software started life as a new and shiny project at some point. Treat legacy projects with the same level of TLC as any new and shiny project. Be pragmatic, , but don’t make things worse. sometimes we have to be Thank you for reading. I hope this article has given you a handful of tips to help you navigate the difficulties of working on legacy applications. 😀