question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

RFC: Changes to Vendure's versioning policy

See original GitHub issue

Background

⚠️ This is a long document, but important! Please take the time to read any leave any feedback you have 🙏

Vendure follows semantic versioning (aka semver). This means that breaking changes are only allowed to be introduced with a major version bump, e.g. v1.x -> v2.0.

Semver is valuable in that it is designed to provide information to package consumers about what can be expected from a new version of the package:

  • 1.7.4 -> 1.7.5 patch, just bugfixes. Safe to upgrade with no further work needed.
  • 1.7.4 -> 1.8.0 minor, new (backwards-compatible) features have been introduced. Safe to upgrade with no further work needed, unless you want to use new APIs.
  • 1.7.4 -> 2.0.0 major. Breaking changes. You may need to make changes to your code in order for the package to still work.

Since Vendure v1.0.0 was released in May 2021, we have strictly followed this practice.

What is a “breaking change”?

“Breaking change” is not explicitly defined on the semver website, but since v1.0 I have taken it to mean:

  • any change to the DB schema
  • backwards-incompatible changes to the GraphQL schema. E.g. removing a field is breaking, but adding a new field is not.
  • backwards-incompatible changes to Vendure’s TypeScript APIs.
  • updates to underlying libraries that would necessitate consuming code to be changed. Example: updating to the latest version of TypeORM which deprecates a bunch of APIs and would require some work in any custom plugins that use them.

The basic goal of the above is that anyone can update a patch or minor version of Vendure without requiring any work on their side. It should just be a matter up changing the version in package.json, installing the new packages, and everything should just work.

For this RFC, I will refer to this interpretation as strict semver.

Problems with Strict Semver

Inability to upgrade core dependencies

The main issue with this strict semver interpretation is that it makes it impossible to upgrade some of our underlying dependencies without a major version bump. Examples:

  • We are pinned to NestJS v7.6. The current latest version is v9.1.4. That’s a lot of new features, fixes, and potential perf improvements we miss out on.
  • Likewise with TypeORM: pinned on v0.2.45, latest is v0.3.10.
  • Same with Angular for the Admin UI: pinned on v12.2.16, latest is v14.2.7

Over time this factor becomes more and more of an issue, as the overall ecosystem moves forward with the assumption of newer versions, but we get stuck with potentially incompatible older versions.

Delaying features

There are some really useful new features that I have developed that I cannot release on the v1.x branch because they involve (sometimes rather trivial) changes to the DB schema. A good example is the ability to toggle Collection filter inheritance. I actually implemented this feature long ago on the major branch but it requires the addition of a single new boolean flag on the Collection entity, which is a breaking change to the DB schema.

Big bang pressure on major releases

Being able to upgrade minor versions with zero worry about breaks is great! It’s a huge upside that I want to re-iterate. But on the downside, it means that all breaks are then buffered for the next major release, leading to a “big bang” of breaking changes all at once.

It is probably preferable to handle a larger number of small breaks, rather than many breaks all at once. At least, that has become my personal preference after many years of software development.

Solutions

Just do major versions as & when needed

Why not just bump the major version as and when needed? We could have upgraded our core dependencies long ago, added a bunch of new features, and happy be working with Vendure v17.7.4 right now!

There are 2 problems with this:

  1. We lose information about the magnitude of a change in a major version. My personal conception is that “Vendure v2 is gonna have some big new stuff”, rather than “it added a boolean flag to a model as part of a minor implementation detail”. Therefore the developer doesn’t really know whether a major version bump means “ok, potentially a bunch of work to do on my side, but with some major improvements/updates as a reward” vs “need to run a migration because of some meh change to the data model”.

    There is a marketing/planning aspect to this and it is also important, much as developers like to dismiss such concerns 😆 . By now, Vendure has a lot of companies, large and small, depending on it as a central part of their infrastructure. Their technical leadership needs and wants a clear and understandable indication of what major changes are coming, and when. The major version number is a key mechanism to communicate this. If we are on Vendure v1.7 now and you hear that a new key feature will be released “in v5”, you immediately don’t really know when that will be and neither do we! Plus it might easily become v6 because we overlooked some other breaking change that was needed before then.

  2. I plan to introduce a compatibility API where plugins can declare what Vendure version they are designed to work with. This would probably be scoped to which major version(s) a plugin is known to support. When there’s little control on unbounded major version growth, such an API becomes almost useless.

There’s an excellent discussion of these aspects on the TypeScript repo. Here are some good arguments around these points:

The trade-off for getting millions of dollars of engineering investment in the TypeScript project is that marketing gets to control version numbers to a certain extent.

It’s not really an unalloyed good anyway. If we followed semver rules exactly, literally every single release would be a major version bump. Any time we produced the wrong type or emitted the wrong code or failed to issue a correct error, that’s a breaking change, and we fix dozens of bugs like that in every release. The middle digit just isn’t useful for TypeScript in a strict semver interpretation. – RyanCavanaugh commented on 17 Feb 2017

Personally, I think using language version 81 and browser version 127 is terrible. It looks ugly and these high numbers quickly become meaningless. – gcnew commented on 7 Apr 2017

What semver means in practice is that the major version bump is “You will probably need to update your code in a lot of places”, and the minor version bump is “You should always be OK for the most part”. TypeScript never makes updates of the first kind. We only make compat-breaking changes where we believe you should always be OK modulo a small number of fixes we think you’ll be happy making (because we found new bugs).

We’re not going to take a major version bump because there was a bug in the compiler where it failed to identify early errors, even though that’s technically a breaking change - we think you should be “along for the ride” on that one if you didn’t shrinkwrap. That’s how semver is used in practice by everyone anyway. – RyanCavanaugh commented on 7 Apr 2017

Do major version more frequently

The Angular team uses a release cadence of major release every 6 months. This allows consumers to have a reliable expectation of when breaking changes are coming, and yet prevents the “big bang” and “stale dependencies” issues from persisting for more than a few months.

This approach gives most of the benefits of “just bump the major version whenever needed”, but still allows a degree of predictability and ability to lay out a sane roadmap of upcoming versions and features.

Relax the interpretation of semver

Another approach would be to simply be a bit less strict on what we classify as a breaking change. For example we could say:

  • Breaking schema changes are allowed in a minor version so long as we provide migration scripts
  • Upgrades to core dependencies are allowed in a minor version so long as any potential work needed by the consumer is minimal & thoroughly-documented.

Major versions would still be reserved for changes that involve breaks in the Vendure APIs or dependency upgrades that would require significant work to integrate in existing plugins.

If we look at the e-commerce ecosystem in particular, we see that it is rather common to have a versioning scheme similar to this - even more relaxed in fact:

  • Magento: claim to follow semver “on the individual module level” but the main version is explicitly a “marketing version”. Notoriously minor version bumps to the marketing version can entail significant work to upgrade.
  • Sylius: according to this migration guide, there are a lot of breaking changes in a minor version bump, 1.8 -> 1.9, mostly seemingly due to updates to core dependencies like Symfony.
  • Medusa: In the latest minor v1.4, they list breaking changes to their Admin UI due to an update to an underlying library it uses. Other than that I believe they also sometimes introduce DB schema changes that require migrations (even in patch versions), in which case they provide a migration script, e.g. in v1.3.6 they introduced a new sales channel feature.
  • Saleor: They introduce breaking changes quite regularly with minor versions.
  • Shopware: Does not use semver - it has a 4-part versioning scheme. The first part 6.x.x.x is marketing and the remainder seem to be somewhat semver-like, with the second part being effectively semver-major. E.g. the v6.4 release announcement includes breaking changes due to upgrades to the underlying framework (Symfony) as well as a minimum PHP version change.

So a relaxed approach to breaking changes seems to be somewhat of the status quo in e-commerce dev.

Feedback wanted

You - a Vendure user - will be directly affected by any changes we make to our versioning policy (you are already affected by the downsides of the existing policy). So I want to hear feedback from you on this before we make any decisions about any changes.

What seems like the best way forward?

  • New major versions as & when needed
  • Planned regular major versions e.g. 2x per year
  • Relax breaks for minor versions, major version every ~1-2 years.
  • Other suggestions?

Thanks for reading!

Issue Analytics

  • State:open
  • Created a year ago
  • Comments:5 (2 by maintainers)

github_iconTop GitHub Comments

3reactions
toonjanssenscommented, Oct 20, 2022

I prefer the relax interpretation of semver. With the necessary migration scripts and documentation, it should be an easy upgrade. You never should just blindly upgrade the Vendure packages in your project without checking the documentation on the changes, except for patch upgrades. The major upgrades can be reserved for real big new features in Vendure or major changes in the folder or api structure.

I rather have 1 major release once a year of even after 2 years, then needing to refactor my code every 6 months. The changes will probably be less with a shorter interval, but still more work to upgrade your code 4 times instead of doing it once after 2 years. When a major release only happens after 1 or 2 years, this will mean that a lot of features will be in a minor update which results in faster delivery of new features.

2reactions
martijnvdbrugcommented, Oct 20, 2022

It is probably preferable to handle a larger number of small breaks, rather than many breaks all at once. At least, that has become my personal preference after many years of software development.

This can be considered a fact more than personal preference as far as I’m concerned 😃


I personally prefer the option to relax breaking changes in minor versions for the following reaons:

  1. With the strict semver interpretation, the removal of any public function would be considered breaking, because a plugin can depend on it. (Because we can just import any provider via NestJS) So, both patch and minor versions can mostly only add code. To me, that makes it feel like they are very similar in magnitude: Why would I care if it’s a patch or a minor, when both can be upgraded by just increasing the version number, because neither will have breaking changes?
  2. Adding a field to the DB doesn’t feel that breaking to me. Not sure if there are any standards that say this should be a breaking change?
  3. I just like the feeling of a major version being an actual big change, and not all breaking changes are a big in terms of functionality or business value
Read more comments on GitHub >

github_iconTop Results From Across the Web

RFC 2291: Requirements for a Distributed Authoring and ...
Changes to HTTP WebDAV adds a number of new types of objects to the Web: properties, collections, version graphs, etc. Existing HTTP methods...
Read more >
RFC 3253 Versioning Extensions to WebDAV
Standards Track [Page 1] RFC 3253 Versioning Extensions to WebDAV March 2002 ... Each client makes changes to its workspace, and can transfer...
Read more >
National Committee on Vital and Health Statistics; Meeting ...
The agenda for the hearing dedicates one day to the X12 proposed standards updates and one day to the proposed updated and new...
Read more >
NCVHS Request for Comment (RFC) on Proposals for ...
The National Committee on Vital and Health Statistics' (NCVHS), Subcommittee on Standards will host a hearing on January 18-19, 2023.
Read more >
Semantic Versioning 2.0.0 | Semantic Versioning
Major version X (X.y.z | X > 0) MUST be incremented if any backwards incompatible changes are introduced to the public API. It...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found