Skip to main content

Technical Debt Removal with GenAI

· 9 min read

The new Spider release introduces a complete technical overhaul of all services.
This significant achievement was accomplished over just one month, thanks to the incredible assistance of AI agents!

Scope

Inspired by some ongoing projects at my day job, I decided to tackle a major upgrade of Spider’s microservices to remove technical debt and address potential security vulnerabilities caused by outdated libraries.

Spider's current vulnerability score wasn’t alarming, but staying updated is crucial for long-term stability and security! The primary focus was on replacing two unmaintained libraries:

Additionally, I upgraded all other libraries and third-party components to their latest versions:

  • Traefik: 2 → 3.5
  • Redis: 7 → 8.2
  • Elastic Stack: 7.17 → 9.1

With so many changes to tackle, the key questions became:

  • How could I implement these upgrades efficiently?
  • How could I control regression with such a large scope of work?

Introducing Automated Tests

Surprisingly, Spider didn’t have automated tests until now!
Manual testing had always sufficed, given the quality of the codebase and the rarity of regressions. However, with so many necessary changes, thorough retesting became unavoidable.

Since I’m always looking to minimize tedious work (a.k.a., I’m lazy), I chose to implement automated tests for each service before performing any upgrades.

The process became straightforward:

  1. Write automated tests for the service to be upgraded.
  2. Use AI to execute the required upgrades.
  3. Re-run the tests to ensure everything works as expected.

And this is 2025! You don’t write automated tests manually anymore; instead, you let GenAI handle that for you. 😎

Automating Tests with GenAI

For test generation, I used ChatGPT (it delivered the best results). Here’s how it worked:
I provided ChatGPT or Claude AI with the following:

  • OpenAPI documentation
  • Relevant documentation from this website
  • Process flow diagrams

The generated tests were functional but too simple, low added value.
After tuning prompts, refactoring steps for reusability, and manually refining the code, I achieved significant results.

Best Practice Learned: The most effective method involved providing detailed process flows, after which ChatGPT would write the test steps and data samples based on the OpenAPI specs.

Ultimately, I produced dozens of idempotent tests with hundreds of steps.
These tests ensured full coverage and could run in parallel within mere minutes — what a time to be alive in 2025!

Automating Technical Debt Resolutions

There were 40 services to upgrade:

  • Backend services: 36
  • UI servers: 3
  • Controller agent: 1

These encompassed 3 different internal architectures, without any shared libraries but with quite a bit of duplicated code.

Why Use AI for This?

While I had existing scripts to handle service upgrades, handling breaking changes and necessary code adjustments was anything but trivial.
Luckily, I had access to JetBrains Junie, an AI agent leveraging Claude to execute these upgrades.

First Trials with Junie

My initial approach to the upgrades was somewhat manual to define the patterns and understand the scope of the required changes.
By "manual," I mean that I still used GenAI, but step-by-step, without fully automating the process initially.

Once I had confidence in the methodology, I asked Junie to automate the remaining steps:

  1. Replace common files with their updated counterparts.
  2. Update package.json with dependency changes.
  3. Perform code adjustments for all breaking changes.

Prompt:

I need to upgrade several packages and improve code patterns across all our Node.js microservices. Can you help me implement the following changes throughout our codebase for better security and maintainability?

1. As a first step, to avoid unnecessary analysis and changes, replace these files with their updated version from teams service.
You don't need to analyze them for now, only replace the files.
Stop if it fails for any file.
Do not create a new file if the file does not exist in current project.
- Replace src/utils/requestAsPromise.js with ../teams/src/utils/requestAsPromise.js
- Replace src/utils/jwt.js with ../teams/src/utils/jwt.js
- Replace src/webpack.config.js with ../teams/src/webpack.config.js
- Replace src/dao/elasticsearchStore.js ../teams/src/dao/elasticsearchStore.js
- Replace src/stats/apiStats.js ../teams/src/stats/apiStats.js
- Replace src/stats/processStats.js ../teams/src/stats/processStats.js
- Replace src/jobs/token-refresh-job.js ../teams/src/jobs/token-refresh-job.js

2. Update package.json with the following updates:
- Remove request.js dependency from package.json
- Add axios (^1.10.0) dependency to package.json
- Remove moment.js dependency from package.json
- Add luxon (^3.7.1) dependency to package.json
- Remove bluebird from package.json except if used by redis-dao.js
- Upgrade @koa/cors from 3.1.0 to 5.0.0
- Upgrade jsonwebtoken from 8.5.1 to 9.0.2 if present
- Upgrade jwt-decode from 3.x to 4.0.0 if present
- Upgrade @koa/router from 10.1.1 to 13.1.1
- Upgrade joi to 17.13.3 if present
- Upgrade koa to 3.0.0
- Upgrade koa-bodyparser to 4.4.0
- Upgrade koa-jwt to 4.0.4
- Upgrade uuid to 11.1.0 if present
- Upgrade javascript-obfuscator to 4.1.1
- Upgrade webpack to 5.100.1
- Upgrade webpack-cli to 6.0.1

The next other actions concern the files of the project not already replaced

3. Replace, in JavaScript files, 'request' lib usage by 'axios'
- Search for usages of requestAsPromise file in the codebase and check compatibility with no library
Ensure in particular that the current call is not stringifying the json before calling requestAsPromise

4. Update, in JavaScript files, any Date manipulation with moment.js to use luxon's API

5. Remove, in JavaScript files, 'bluebird' dependency and use native promises
- Remove `const Bluebird = require('bluebird')` imports
- Replace `Bluebird.delay(t)` with Node.js native timers:
- Add `const { setTimeout } = require('node:timers/promises')`
- Replace `await Bluebird.delay(t)` with `await setTimeout(t)`
- Replace other Bluebird-specific methods with native Promise equivalents

6. Fix route.js with upgrade of @koa/cors lib usage from 3.1.0 to 5.0.0
- Enhance CORS configuration by adding:
- allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH,OPTIONS'
- credentials: true
- Keep existing exposeHeaders configuration

7. Upgrade, in javascript files, 'jwt-decode' lib usage from 3.x to 4.0.0
- Change import from `const jwtDecode = require('jwt-decode')` to `const { jwtDecode } = require('jwt-decode')`
- Ensure all usages of jwtDecode function are adjusted accordingly

8. Upgrade 'route.js' lib with change of @koa/router from 10.1.1 to 13.1.1
- Change router initialization from `const router = require('@koa/router')()` to `const router = new (require('@koa/router'))();`

9. Run 'npm install' in ./src folder

10. Run 'npm audit fix' in ./src folder

11. Update /src/Dockerfile
- Replace 'node:20-alpine' with 'node:22-alpine'
- Replace 'alpine:3.20' by 'alpine:3.22' in 'Dockerfile'

These changes should be applied consistently across the codebase to ensure security
and maintain consistent coding patterns throughout our microservices architecture.

Please scan the codebase for these patterns and implement the necessary changes while
preserving existing functionality.

The results were promising:
Junie successfully replicated my manual upgrades for 4 microservices.
The updated microservices functioned almost perfectly — straight out of the box!

However, there were limitations:

  • The process took 15–20 minutes per service, which was too slow.
  • Occasionally, Junie would stop mid-task, requiring manual intervention to continue.
  • Credits for using the AI consumed resources much faster than anticipated.

A Better Way: Generating Scripts

Instead of having Junie perform the upgrades, I pivoted to having it generate a custom upgrade script. The result?

  • The script was generated in just one minute.
  • It executed the upgrades in under one second, compared to Junie’s 15 minutes!

For more complex, non-scriptable changes, I used shorter, fine-tuned prompts.
Prompt:

Update, in JavaScript files:
- any Date manipulation with moment.js to use luxon's API
- Durations constants comming from Configuration (config object) are always in ISO, so use fromISO and not fromMillis
- To convert a duration to milliseconds, use toMillis() function, not as('milliseconds')
- any redis lib usage from 3.5.2 to ^5.7.0, removing bluebird promisification
- Pay particular attention to:
- `sadd` method that is now `sAdd`
- `batch` method that is now `multi`
- `evalsha` method is now `evalSha` and has a different signature (script, { keys, arguments } ). Please adjust and make sure all arguments are strings

- check that no call to request function
* is doing JSON.stringify of the body sent in POST, PATCH or PUT
* is doing JSON.parse of the response body

These changes should be applied consistently across the codebase to ensure security
and maintain consistent coding patterns throughout our microservices architecture.

Please scan the codebase for these patterns and implement the necessary changes while
preserving existing functionality.

By iterating and refining the prompts, I minimized errors and ensured consistent results.

Request → Axios → Undici

A Side Story:

Initially, I replaced the Request.js library with Axios.js.

It worked perfectly during development, but the first production release exposed some serious issues: the system began delaying parsing tasks, queues were piling up, and communications were being discarded!

Monitoring put Axios under scrutiny, and the results revealed:

  • API latencies of services remained excellent — even slightly better than before.
  • However, Service-to-Service latencies deteriorated significantly: An API call that took just 4ms on the server side could take over 200ms on the client side under load!

Researching online confirmed that Axios struggles with high-load scenarios.
This prompted me to try Undici.js, which came highly recommended.

Thanks to an AI-generated upgrade script combined with automated regression tests, I was able to replace Axios with Undici in all services in a couple of evenings!

The outcome? Undici proved to be not only much faster than Axios but also outperformed the older Request library.
The entire system is now faster and more efficient!

The Takeaway:

  • Axios is slow under heavy load, especially for Service-to-Service communication.
  • Undici provides better performance and handles high-load scenarios effectively.
  • AI automation made the upgrade process quick, seamless, and thoroughly tested.

Lessons Learned and Final Results

By the end of August, I had successfully upgraded all 40 services, while simultaneously developing regression tests to ensure stability. 💪

Here’s why this approach worked so well:

  1. AI Saves Time: Using GenAI to address breaking changes in the context of your code beats spending hours reading documentation.
  2. Documentation Matters: While AI is powerful, having proper documentation like OpenAPI specs sped up the process dramatically.
  3. Iteration Optimizes Results: Refining prompts and automating parts of the workflow optimized both efficiency and cost.

All this was achieved with an incredibly low GenAI cost of €20!
The time saved was invaluable, and Spider’s services are more secure and maintainable than ever.