Here at GumGum, we use a variety of apps to manage our products. The oldest one is our internal dashboard which is used by a wide range of teams for managing and reporting purposes relating to the advertising side of the company. Those teams include Account Managers, Publisher Managers, Engineers, testing users and so on, daily. It has evolved continuously since its creation, about 10 years ago, and with that comes a lot of technical debt.
This monolithic application is built with the classic MVC model in PHP with CodeIgniter, Propel and — as of recently, it’s deployed and running with Docker.
As time passed, and the demand for additional features grew, we realized that we needed to tackle the technical debt with more “passion” than we previously did. Especially since other tools were developed in parallel, and there was a desire to unify all of them.
What we thought or tried doing
1. Updating the whole app at once
This was our utopia and unrealistic due to the large size of the app. The app is core to our company, and no downtime is conceivable. On top of that, the team was only +/- 5 members with limited bandwidth.
2. Updating the overall design of the app first
Our design team created a new visual look called GumGum Design System to refresh the app’s look. When this was rolled out, we slowly updated the pages’ UI and code one by one. This allowed us to have a visual cue as to what was updated and what was remaining.
Here are the rules that we followed:
- For a bug, the priority is resolving the issue without updating anything else.
- For an improvement, depending on the size, we can update the UI, and if time allows, the back-end code too.
- For a new feature, we must update the front-end as well as the back-end code with Propel, optimizing it as much as possible and bringing it up to date with the standards (e.g: PHP Code Sniffer). We also should add tests and move important functions to a REST-like API.
From this XML file and the generated classes, we were able to automate parts of our code. For instance, validating and adding a log entry whenever an entity is updated:
To load the right set of CSS and JS files (i.e if the page design was updated or not), we used an asset management framework.
This worked to some extent, but this is a very slow process and didn’t solve the original issues:
- It’s still big, really big, and thus hard to maintain.
- Prone to bugs with an aging code base.
- Only a handful of people can update the code.
- Now the design is inconsistent between the pages and with the other apps.
- Still little to no test/code coverage.
3. Try a new and more flexible language
In early 2015, we started building an app called AdBuilder, which allows anyone to create custom ads dynamically with many interactions and design options. It started as a test project with Angular 1, and when Angular 2 arrived, they differed so much from one another that the level of effort to update it could have been as much as using a different language. At the time, React was starting to make noise, and it was decided to rewrite the app in React while it was still in its infancy. Vue.JS was also talked about as a alternative, so we decided to try both for our project for the sake of being fair.
We ended up choosing React for its ease of use and it was the preferred choice company wise. To make it work with our current PHP app, we use webpack to bundle the React component into JS files that are in turn inserted in views.
From this article by Martin Fowler, we qualify this as a Deliberate/Prudent debt. It's purposely made to reduce the learning curve for the engineers while knowing that the whole app wouldn’t be completely re-written in React, and these pages will have to be removed or re-worked on soon.
Ultimately, It just made more sense to use the same tech across all our apps. It’s easier to maintain, and all the developers can “on paper” work on all of the projects when they share the same knowledge and skill set. This resulted in the creation of a library of shared components that can be used across multiple products, ensuring a brand/unity for all of our apps.
What this first round taught us
Thanks to that experience, we knew it would not be possible to update the whole app. Period. We tried, we failed. It made more sense to break it into smaller more manageable apps, each dedicated to a specific side of the business. And now we had everything to do so:
- A team to help us make the move.
- Enough people to do so.
- A carefully selected language (React).
- A clear goal (breaking up the app).
The next move was to analyze the legacy app and choose a section to begin with that was in great need of an update, while trying to limit impacting a large amount of users. With that section selected, and with the help of Product Management, we can look deeper at each feature:
- Is it still needed? Why?
- Has the design of the page not been updated? Meaning the back-end code is most likely in need of an update too.
- How does the user actually use it? (We asked users directly as well as using Hotjar).
- How can we improve it in the new app? Our leading focus was to make everything more efficient for our users.
- Clean up unused code.
Removing the tech constraint by starting from the ground up also made it possible to add new features that we wouldn’t have been able to previously.
Where we are now
We’re still in the process of updating our code base, and we’ve successfully moved 2 big sections on the managing side to their own dedicated apps. Features are slowly being moved from the legacy app to the new one.
Now these 2 new apps use React, Redux, a common components library from GumGum, and a great new API to manage the data. The knowledge is not owned by a small team, but shared. More people can work on the project and debug since we use common components, logic and an API.
Overall, the communication with our users was improved as they were part of the update.
This is truly a group effort involving the whole engineering department. For instance, in parallel of what was described above, the DevOps team optimized and automated our release process with Drone and Docker. Previously, coding a feature locally meant using Vagrant to manage a virtual machine, and releasing the project was done manually by running a script on the server and deleting the old release folders.
Our Product team cleaned the backlog of tickets. They assessed which requests were still relevant, decided if they could be implemented on the new app instead, and updated their priority and specs as needed.
- Be realistic, this is a long process. If you paid attention, this started around 2015 and is still not finished.
- Break up your app. Chances are if you can’t maintain it properly, it’s too big.
- Look what is being done around you, but make sure it can work for you too.
- Communicate with everyone, not just people within technology.
- Ask questions, a lot.
- Bring in new key people to have a fresh point of view on the app (but not too many to avoid losing focus of the end goal).
- Be ready for the snowball effect (the desire to update the UI started a big change of tech).
- Stay focused. We definitely tried multiple approaches with suggestions and team members coming and going over the years, but the end goal was always the same — improving the code quality and user efficiency.