Upgrading NodeJS version and packages: What did we learn at Jodel about this?

Upgrading NodeJS version and packages: What did we learn at Jodel about this?

At Jodel, we have a micro-services architecture that consists of around 20 services that deliver different functionalities that serve the business and the tech needs. 75% of these services are NodeJS services.

The NodeJS version was version 10, and each service is using a lot of packages, and most of these packages were outdated.

As we are still relatively a small team (we are hiring by the way) we don't have explicit ownership for each service yet, so we decided to tackle this big technical debt as part of our OKR in Q1.

Our goal was to finish Q1 not only by having all the services run the latest NodeJS stable version (It was 14.15.4 at that time) but also to upgrade all the packages to the latest version as well.

We also did a lot of code improvements during this project, we learned a lot, and overall It was a very exciting and interesting process at the same time.

In this blog, I would like to highlight learnings and talk about our findings as well, which might help other teams that want to go through the same process.

A language expert is a game-changer

In my opinion, there are two types of programmers, one that deals with a programming language very well, knows how to get things done using it, and another does all the previous but also knows the ins and outs of that language and always up-to-date with everything related to it from deprecations, to new additions, ending with tooling.

One of our colleagues (and a newcomer to the team) is extremely good at Javascript and the NodeJS ecosystem. He helped us a lot during this project by identifying breaking changes before doing them and introduced very useful tips to us. I don't think we could have done this upgrade as well as it is right now without him.

Before starting such a project, I would suggest hiring an expert in the tech stack that you are upgrading in case the team is lacking this knowledge. This will not only save time but also reduce the risks.

Consistent patterns among the services

One of the things that helped us do this upgrade in 3 months only was the fact that most of our services have the same structure and uses the same packages. This was key to carry learnings from one upgrade to the next. If we upgraded, for example, the Postgres client on one service we would do the same upgrade on other services that use Postgres easily because we learned what changes we needed to make. We would have needed to learn everything again if that service was using a completely different Postgres client.

Go small first

As a follow-up to the previous point, we targeted first "small" services (by small, I mean the number of functionalities that service provides). This helped us to compound our learnings with each service, so when we started upgrading relatively big services, we were able to do that very quickly as we have dealt with most of the breaking changes before working on this service.

I believe also that picking up the rhythm and celebrating small wins, in the beginning, can be a great motivator, especially in these dry and technical projects. If we have started with a service that delivers so much business logic we might have ended up breaking many functionalities and causing downtimes, and this could've made us discouraged to continue this project.

Read Changelogs

I think this point is a no-brainer. The problem for us was that we needed to read multiple versions' changelogs, as most of our packages were at least 3 major versions behind. Gladly most of the packages that we are using have changelog that we were referring to all the time.

It is very important to choose maintainable packages here, and if that wasn't the case, replace what you have with maintainable ones. We did that actually for a lot of packages that we had.

Custom Package Manager

We found out that most of our services are using almost the same code to create a custom implementation for a use case, like implementing a custom logger for our needs.

Here, what could have made the upgrade smoother, is having our own package manager, where we can create a custom package and pull that package into the services instead of re-writing the same code in every service (hence, upgrade the dependencies would be in one place and not 20 as well).

Define Processes

One version upgrade is a doable task, multiple versions upgrade at the same time is not fun at all.

In my opinion, you should not reach this point at all where you need to upgrade everything at once if that possible.

To avoid that, try with your team to define processes around when you should upgrade, and what types of packages are allowed to be used. It is important here to not choose a package that is not maintained frequently or not well documented.

This is one of the reasons why you should not switch to micro-services architecture in case you have a small team as this will introduce maintenance overhead on the team instead of maintaining one monolithic backend.

Unfortunately, not everything was easy and doable in 3 months time frame. When we tried to upgrade the MongoDB client on the biggest service we have, we saw huge spikes in MongoDB's CPU and disk usage. We tried a lot to pinpoint the change that was responsible for this but we couldn't do that. We believe that the reason might be related to one of MongoDB's connection parameters but we decided to tackle this upgrade as a standalone project as we realized it is not a very straightforward upgrade.

Another time-consuming upgrade, replacing "request-promise" package (which is not maintained anymore) with "Axios" We made this upgrade on all the services except the biggest backend service we have as it will require a lot of code changes and this cannot be done within the remaining time.

Overall, I am really proud of my team for pulling off this huge project.

If you like working on such projects with amazing talented engineers, go and check our open positions here and apply.

Muhammad Ahmad

SWE @ B2Broker | Golang | Node.js

2y

Good job brother 👍 I have two "side" notes to add, I think you may consider on your next optimization: 1. for a better native experience, you must rethink about using https://undici.nodejs.org instead of axios, which is more native, clean-code and faster http client that supported by Node.js team 2. by reading between the lines, I guess you still use express.js "I may be wrong ;)" As we all know, this project is not active for about couple of years, despite the facts of its not solved problems such as bad async/await handling in middlewares Express.js has been stable enough to be used widely and in a safe-ish manner to write enterprise level projects this is was the case until Node.js 14 LTS Starting from Node.js 15, there is no more UnhandledRejection warning when using async/await, this will crash just because Express.js not handling this for you by nature I guess you can see the memory leaks that we don't know of once we upgrade to next LTS This is a tedious job to fix and maintain, which must be done already by the team behind it This point I guess is important since we don't know if v5 will fix all this caveats, and may consider a few options atm such as fastify.js for example. Forgive me for the long reading ;)

Ronald Placzek

Talent Partner Lead🕵🏻 Recruiting | Social Media | The Hyperlocal Community 🌍 | We are Hiring! ✍🏼

3y

Great Job! 👍

To view or add a comment, sign in

Insights from the community

Others also viewed

Explore topics