The process of upgrading large third-party packages in equally large organizations is rarely, if ever, as easy as running an npm update and calling it a day in this context. Joran Quinten shares valuable lessons from his team’s experiences in upgrading third-party code that affects codebases across the entire organization.
If you work in software development, you probably know a thing or two about using and maintaining third-party packages. While third-party tooling has its fair share of downsides, there are plenty of advantages as well. The efficiency you get from code that someone else has already written speeds up development and is hard to deny. Sure, there are all sorts of considerations to take in before plopping code from a third party — accessibility, technical debt, and security, to name a few — but the benefits may make taking on those considerations worthwhile for your team.
Upgrades are also part of that set of considerations. Usually, your team may treat this sort of maintenance as a simple task or chore: upgrading dependencies and (automatically) validating that all of the features keep functioning as expected. You probably even have automated checks for keeping all package versions up to date.
But what if the third-party tooling you adopt is big? I mean big, big. That’s common in large organizations. I happen to work for a fairly large organization that leverages big third-party resources, and upgrading those tools is never as simple as running a package update and moving on. I thought I’d share what’s involved in that process because there are many moving pieces that require ample planning, strategy, and coordination. Our team has learned a lot about the process that I hope will benefit you and your team as well.
Some Context On My Organization
I work for Jumbo Supermarkten in the Jumbo Tech Campus (JTC), which is a department of over 350 developers working in agile teams on a range of digital products that help facilitate our core grocery and e-commerce processes.
We have a variety of responsibilities, where 70% of the work is allocated to the primary objectives for each team, and the remaining 30% is dedicated to anything a team wants, as long as it is beneficial to the JTC, which is very useful if you want to deliver value outside of your own team.
When we look at maintaining tooling and packages, balancing the goals of each team with the goals of JTC means that teams effectively maintain their own codebases while also collectively maintaining internally shared codebases that serve as the tooling and foundation of our applications.
Centralized Code As A Bottleneck
To build our applications with consistent standards, we rely on an internal design system and the component library we call Kompas (Dutch for “Compass”). We have built this system ourselves and rely on Vue to render our components and build interactions. Kompas is a hard dependency for virtually all of our applications to ensure uniformity.
This project was not allocated to a dedicated team. Instead, we adopted a strategy that introduced plenty of guidance to allow all front-end developers to contribute. Any developer can add new components to the library as well as features to existing components and keep everything in sync with the designs.
Teams normally work on business features since product owners love delivering customer value. The way we set up our process would allow a team to, in one sprint:
- Make the required change in Kompas,
- Have it reviewed by peers from both inside and outside a particular team,
- Publish the latest version of the component library, and
- Use that version in that team’s own application to deliver to the end user.
We can only do this with automation on repetitive processes — linting, formatting, quality assurance, testing, visual comparisons, and publishing — in order to provide enough room for developers to contribute to the process. Our component library is very much a living document of our design system, with multiple minor releases and patches a week. With semantic versioning, we can keep our own applications up to date easily and with confidence.
For bigger undertakings, such as setting up visual snapshot tests, we established temporary working groups alongside our existing teams that we called “front-end chapters” where members join on a voluntary basis. In these meetings, we discuss what needs to be done, and in the available 30% of free time we are allotted, the members of these teams carry out the work and report back to the chapter.
As you can imagine, we’ve spent a lot of time and effort ensuring the quality and making it a reliable part of our landscape.
This all began when Vue was in Version 2. That’s the version we baked into Kompas, which means we effectively forced our whole application landscape to follow suit. This worked perfectly for us; people could focus on their team’s needs while leaning on the support of the entire front-end chapter that works on Kompas.
Following the Vue ecosystem that we introduced, Vuex and Nuxt became part of our environment. And then Vue 3 was announced, and it was a massive breaking change from Vue 2! With the announcement, the end-of-life date for Vue 2 was set for December 31, 2023. We still have some time as of this writing, but the news had a massive impact that cascaded throughout our organization.
We Needed A Strategy
We needed to upgrade Vue from 2 to 3. The first thing that we needed to figure out was when we could reasonably start the process. To assess and strategize, we formed a small virtual team of developers consisting of members from various teams so that multiple perspectives were represented.
We figured that there would be a period of time when we would need to support both versions in order to allow time for migrating between teams. It would be nearly impossible to orchestrate a monolithic release. Thus, we prefer gradual incrementing over massive sweeping changes. On the other hand, having to maintain two versions of Vue for, basically, the same business feature presented costs in time and complexity.
So, in order to execute this process as responsibly as possible, we set out to figure out when we could start, taking into account the longevity of maintaining two codebases while getting early experience from upgrading. We started to map the different tech stacks for each team and plotted out potential bottlenecks for the sake of making the process of our work as widely visible as possible. At this time, our organization had a very flat structure, so we also needed to get internal stakeholders (i.e., product owners, architects, and managers) involved and convey the effect this upgrade would have on teams across the board.
Creating A Map
With our starting point set, we move on to establish a direction. Not having a dedicated team did pose some challenges because it meant that we needed to align everybody in a democratic way. This is, in Dutch culture, also known as polderen:
We try to find consensus in a way where everybody is equally happy, or unhappy, about the final direction.
And this can be challenging in a department that consists of many cultures!
One thing we knew we could rely on was the published best practices from official Vue resources to guide our decision-making process. Referencing the documentation, we did notice opportunities for incremental upgrades. The release of Vue 2.7 (Naruto) was really helpful in the sense that it backported features from Vue 3 back to a Vue 2-compatible version.
We also noted that in our landscape, not all applications were actually using Nuxt. A stable release of Nuxt 3 would be a prerequisite for those applications to even be considered for migration since the Vue major version is tightly coupled with the Nuxt major version. Luckily, some applications in our landscape are standalone Vue apps. These are ideal candidates for the first Vue 3-compatible components.
But first, we would need to have components that were compatible with Vue 3.
The Big Divide
By this point, we were confident enough to get to work. We had a plan and clear strategy, after all. The first order of business was to make sure that our component library was compatible with Vue 3, preferably while minimizing duplicative efforts.
We found a really nice way of doing this:
“
This only works because:
- The backported features in Vue 2.7 allowed us to move closer toward the Vue 3 composition API (among other things).
- The component syntax between Vue 2 and Vue 3 isn’t radically different anymore.
- Vue Demi allowed us to convert components, one by one, to be compatible with both versions.
- We made sure that Kompas-next runs isolated tests to ensure stability.
We did have to slightly modify each and every component to adapt to the new standards. We’ll get to that process in a minute.
That said, we were able to publish two versions of our component library: one that is compatible with Vue 2 (Kompas) and one that is compatible with Vue 3 (Kompas-next). This, in turn, meant that the teams that did not have Nuxt as a dependency could potentially start migrating!
Getting Organized
Up to this point, most of the groundwork had been done in a relatively small team. We were in charge of the investigations, communication, and alignment. But we still needed to get stuff done — a lot of stuff!
With every developer being able to contribute, we came to an agreement that fits with the way everybody was already contributing to the component library:
If you touch a component that is not yet compatible, convert it to be compliant with both Vue 2 and Vue 3 using Vue-demi. Add the existing component with tests to the imports of the Kompas-next folder.
Having communicated this strategy early in the process, we immediately saw the Kompas-next library growing. The Vue core team has put so much effort into closing the gap between the two versions, which made our lives much easier.
Feedback From Early Adopters
The teams that were not blocked by a Nuxt 3 release could spend their time migrating their complete app to Vue 3, providing feedback along the way on how we were setting up our packages and converting components.
Seeing the first applications using Vue 3 was a milestone we could all be proud of since we managed to reach it together, collaboratively, and with a united strategy. The strategy worked for us because it closely resembled the way we were already working.
There were indeed some components that were not migrated using this strategy, which indicated to us that they were stale in terms of development. We reasoned that “stale” equals “stable” and that it would be perfectly fine to migrate them by manual assignment and distribution since we can expect it to be close to a one-off migration per component.
We also started to add Vue 3-specific capabilities to our component library, such as our own composables. I think that’s a nice testament to the investment and adoption by our front-end chapter.
With the component library now supporting Vue, we cleared a significant migration hurdle in our organization. We enabled teams to start migrating to Vue 3, and we encouraged new applications to use the latest standards. As a result, we could start thinking about a deprecation path for our Vue 2 codebase. We were cautiously optimistic and aligned the end-of-life date for Kompas with the same date for Vue 2: December 31, 2023.
So, yes, we are not yet finished and still have work to do. In fact, we had…
Two (Minor) Elephants In The Room
To support communication between micro-applications that run on our e-commerce domain, we had resorted to using Vuex in the past. We used to register stores globally so other applications could dispatch actions and retrieve a shared state. This is now gradually being migrated in the sense that we are replacing Vuex with Pinia for internal state management.
For cross-app communication, we are in the process of decoupling Vuex as an external interface and promoting the use of custom events tied to a specific domain. This prevents us from locking ourselves out of future state management tooling.
We are also in the process of preparing our Nuxt applications to be cleared for migration as well. Within our e-commerce domain, we’ve been building specific modules that take a lot of overhead out of our hands: They handle tasks like setting meta headers, security, and analytics. These are being rewritten to use plugins rather than modules. The impact of this breaking change is smaller because it is limited to the teams that use these modules. We see that these teams are using a similar strategy, albeit on a smaller scale, to organize and structure the tasks at hand.
Looking Back
I believe we have a few key takeaways from how we upgraded (and continue to upgrade) from one version of a large third-party resource to another within our large network of teams and shared codebases. I believe the lessons we learned are relevant beyond Vue and can be applied to the processes of other large organizations migrating between versions of a core piece of architecture.
Let’s review what we learned:
- Ensure the transition period is clear and as short as possible.
- Facilitate breaking the work down into small steps that progress iteratively and solicit feedback from those involved in the process as early and as often as possible.
- Onboard key stakeholders to make sure your team has ample time and resources to do the work.
- Define a strategy that fits with your organization’s culture.
- Foster a collaborative mindset and establish clear communication between teams.
- Celebrate wins, even the smallest ones!
The Work Is Never Done, Really
As I mentioned earlier, maintenance is a never-ending piece of the software development process. As Vue creator Evan You stated in the State of the Vuenion 2023, Vue plans to ship more frequent updates and releases. This will keep impacting our work, but that’s okay. We have a plan and blueprint for future releases.
We’re not there yet, but we now know how to get there!