Cover Image for Avoiding Updates Pitfalls: Lessons Learnt

Avoiding Updates Pitfalls: Lessons Learnt

Bartek Witczak
Bartek Witczak

In the PART 1, I’m discussing what are challenges in dependency management, why is it crucial to update & what are main update strategies. If you haven’t read it, take a look so you can get more context. Part 1 was more like a theory. Now I want to dive into practical side.

First I want to share my use cases to give you a glimpse of possible problems.

Updating nx

We used nx to manage project. nx is framework to manage monorepo. It assumes that all projects within monorepo use the same version of dependencies. Additionally, nx uses a lot of plugins for different JS frameworks. E.g there's @nx/next plugin that contains generators & executors for managing Next application. Personally, I really don't like that approach. (I used both pure yarn + lerna & turbo approaches. Both were much better & easier to manage.).

Still, we wanted to leverage new features of Next.js, so we had to update to the latest version of nx. nx is growing fast. It's constantly changing. As a matter of fact, we had to update several versions. To make things harder, it was first big update. There was no knowledge, nor process defined.

My initial attempt was to update to the latest version. That led to significant issues - no project within monorepo was building successfully. The amount of changes between major versions were huge. Many plugins changed & there was no backward compatibility (which is common in JS world).

After initial setback, the strategy shifted to smaller & more manageable updates. While some updates were smooth, others proved time-consuming. Analyzing the extensive changelog of nx was a daunting task. At that time, several team members attempted the update, but the success was elusive initially. It highlights the need for collaborative problem-solving in complex environments. Process was chaotic. No one really had an idea how to approach update. Especially, lack of nx knowledge led to long fight. But finally team effort won.

Side note: The monolithic approach of nx was particularly challenging. The need for an update for just one new project inadvertently affected all projects. It was a showcase of the ripple effect of dependency updates in a monorepo setup.

Security issue - Play Framework Update

The experience with updating the Play Framework provides another perspective, emphasizing the impact of delaying updates. For the big project, major update is a pain. Plus there's really no good time to update. The situation changes dramatically when a critical bug emerges. Especially when it's zero day security issue. To make things harder, imagine that there's no support for current version. It requires immediate fix & it requires you to fix that.

That's classic sample of postponing update. Of course, it could have been mitigated with a more proactive approach. The problem is that usually systems die before something really bad happens.on-the-edge.png

The pressure of time makes things harder. At first, you need to fix security issue. Updating whole application to the new major version is not an option. It takes too much time & the outcome is not clear. So you fix it with current version. But what's your next step after problem is resolved? Do you plan an update to next major version or do you keep your fingers crossed and stay with current version?

The Perils of Rushing to the Latest Version - Next.js 13 & the App Directory

This case study highlights the risks associated with updating to the latest version too quickly. Even if it's declared stable. When Next.js 13 introduced the beta version of the app directory, I was eager to get it running. Finally, app directory could be well structured to reflect business case (or at least I thought so initially. Now I have a bit different opinion, but that's different story). As soon as it was announced, I wanted to migrate from pages to app. However, due to unrelated delay caused by the nx update, I had to postpone it.

As it turned out, this delay was a blessing in disguise. Despite app directory being declared stable, it still was missing functionalities. That's not because Vercel team has declared something unclearly. I was reluctant to look at the whole picture. I didn't check everything deeply enough. Of course, there were also some issue with app directory. Still that's common in the big update. Unintentional wait allowed those issues to come to light. It became evident that immediate adoption would have cause more harm than good. Probably, I'd spent some time migrating to app directory, only to notice I need to revert.

When on the bleeding edge, you're the one bleeding

Context is the king. Sometimes waiting for a new version to be battle-tested can be a wiser strategy. It reminds that while staying current with the latest technology is important, a caution approach can sometimes save a lot of hours. Balancing the leverage of new functionalities with reliability is key in dependency management.

These case studies show the diverse challenges & considerations. From the disruptive impact of a major update, through risk of delaying updates to staying on the bleeding edge. I hope those offer valuable lessons for software teams navigating dependency updates.

Practical Tips for Updating Dependencies

Understanding the theory is first step, but it's not enough. It involves practical steps and strategies. Here are some tips to streamline the process:

  1. Read and Analyze Changelogs Carefully: Understand the impact of each update on your codebase. This step is crucial for identifying potential issues early.
  2. Implement a Robust Testing Framework: Automated tests, including unit, integration, and end-to-end tests, are vital to ensure that updates do not break existing functionalities.
  3. Gradual Implementation: For major updates, consider implementing changes incrementally. This approach helps in isolating issues and minimizing disruption.
  4. Automate Where Possible: Utilize tools for automating the update process. This reduces manual effort and helps in maintaining consistency.
  5. Document the Process: Keep a record of the steps, issues encountered, and solutions applied. This documentation can be invaluable for future updates.
  6. Establish a Routine: Create a schedule for regular updates, be it minor or major, to avoid falling too far behind in versions.
  7. Involve the Team: Ensure that the team is informed and trained on the update process. A collaborative approach can yield better results and faster resolutions.

Frequently Asked Questions

  1. How often should I update my project's dependencies?

    It depends on your project's needs and the strategy you adopt. If you're following a 'Update Fast, Update Frequently' approach, consider smaller, more frequent updates. Remember that you need bullet-proof process. Since updates are going to be common, take time to prepare environment properly. If you prefer 'Don’t Touch It If Ain’t Broken', update when necessary, such as for critical bugs or major performance improvements. Although updates are seldom, prepare before you need that. Otherwise, time pressure is going to be another stress factor.

  2. Is it always better to update to the latest version of a dependency?

    Not necessarily. While staying current has its advantages, it's important to weigh the benefits against the risks. Always evaluate the changes and test thoroughly before a major update. Your CI should be your first line of defence. In that case, good suite of end-to-end tests should do the job of initial validation.

  3. What should I do if an update breaks my project?

    First, analyze the issue to understand the cause. Refer to the dependency's changelog and documentation. If the problem is complex, consider rolling back the update. Often updating in small increments can solve an issue.

Balance is the key

Navigating the complex world of core dependency updates is a critical aspect of modern software development. There's no one-size-fits-all approach. The key lies in understanding your project's specific needs, the potential risks and benefits of each update. You need to adopt a strategy that balances innovation with stability.

Stay Ahead with Automation: Implement automation in your dependency management process wherever possible. Automated tools not only save time but also reduce the risk of human error, especially when dealing with large and complex codebases. This investment in automation can pay significant dividends in terms of efficiency and reliability.