Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Semantic Versioning 2.0 (2013) (semver.org)
42 points by gorenb on Oct 3, 2023 | hide | past | favorite | 55 comments


It's really unclear from the submission here (no date in TFA either!) but v2 was 'released' 10y ago fwiw.

https://github.com/semver/semver/commit/9eca13516c58f0e9456c...


Hah, I was thinking: This sounds like stuff that I’ve seem before to me.


Thanks! Year added above.


I thought semantic versioning doesn’t use dates like this. Or doesn’t this apply to titles on HN?


That's just the HN convention when an article is over a year old.


Hey folks! Don't know why this is here. Hasn't been many changes to semver lately. I would like to get some going, but been pretty burned out after Rust. I'd love to bring the spec in line with the implementations, so it's gonna be a change in theory but not in practice.

Happy to answer any questions but won't be around the thread for a bit.


Related to rust, one thing I'd love to see semver clarify is that if you do what rust does, where you just make up your own rules and then claim you use semver, you're not using semver. If the scheme is major.minor.patch with 0.x.y meaning "not semver yet, the numbers can mean whatever someone wants them to", and something like rust goes "we use semver, except we've changed what the numbers mean", then the text on semver.org should have already made it clear that that's simply not using semver, and any claims that you are, are invalid.

It's not semver until you hit 1.0.0 and start following the major.minor.patch system. Until then, your promise to your users is that your use of 0.* indicates a prerelease, and that once 1.0.0 comes out, the version numbers will reflect major.minor.patch changes.

(and the unwillingness of some people who assign some special meaning to "reaching 1.0", somehow treating that number as more than a number, should literally be the sign that they don't want to use semver. It'd be great if the spec/site had a big, bold, "you are not your version number" that makes it clear that version numbers are intended for versioning, and that semver numbers are a utility. They don't hold special meaning beyond unambiguously denoting major.minor.patch transitions)


Hi there!

> if you do what rust does,

The major motivation I currently have is to make it more clear that this is not actually incorrect. Rust here does exactly what Ruby and JavaScript do as well. The semver spec is misleadingly written compared to the real-world implementations that have existed nearly as long as the spec has. Furthermore, choosing these semantics for ^ (which is not in the current semver spec at all, mind you: none of the ranges are) still is allowed even with a strict reading of Rule #4: it says MAY, not MUST. Less breakage than allowed is okay.

> It's not semver until you hit 1.0.0

Semver describes pre-1.0.0 versions as well. That is what Rule #4 is about, in fact.

> and the unwillingness of some people who assign some special meaning to "reaching 1.0",

I agree that this is often an issue. I myself advocated that Cargo start projects out at 1.0.0, like npm, rather than 0.1.0, like Bundler. I lost that battle.

However, sometimes this is due to practical reasons, and not people psyching themselves out over a specific version. For example, the libc crate in Rust will probably never 1.0.0; there can only be one version of it in a dependency tree, and it's so widely used. 0.1.0 -> 0.2.0 was already a mega painful transition, and Rust's ecosystem is vastly larger than it was when that happened.

> It'd be great if the spec/site had a big, bold, "you are not your version number"

I fully agree that it would be great if people get over this, and I would like the next version of the spec to not emphasize 1.0.0 so much, but I doubt that some bold text is going to fix what is ultimately a psychological issue.


> Semver describes pre-1.0.0 versions as well. That is what Rule #4 is about, in fact.

I know, but rule 4 doesn't mean anything if there is an explicit "we wont" intention to ever make switch to 1.0.0 - if you're never going to move past 0.*, you're not semver'ing, you're just using your own "looks like semver, might smell like semver, doesn't taste like semver" scheme.

> I agree that this is often an issue. I myself advocated that Cargo start projects out at 1.0.0, like npm, rather than 0.1.0, like Bundler. I lost that battle.

I'm sorry to hear that =(

> [...] but I doubt that some bold text is going to fix what is ultimately a psychological issue.

You'd be surprised how far the authority of a spec goes. A ton of people will have massive opinions until they're confronted with the spec, an authority they recognize, clearly stating that their interpretation is explicitly ruled out. Specs allow people to cede authority to it.

And of course, don't let any of that detract from a massive thanks for the semver.org effort, no one can deny it's been a net positive on versioning overall.


> if you're never going to move past 0., you're not semver'ing,

Eh that's fair. I think that reality is often messier than this, people intend* to eventually release one, and then don't, for various reasons. But I get what you're saying, sure.

> don't let any of that detract from a massive thanks for the semver.org effort,

Just to be clear, I have mostly inherited a commit bit, tried to get some work done, and failed. I fully agree, but those results are the work of other people :)


Not a question but I’m still bitter that PEP440 is not more popular. It had great ideas that Semver does not implement.


hi, i posted this because i was impressed by semver. i didnt know u worked on the rust book. im extremely impressed. What are some tips about Rust that I could use? As a Rustacean myself, I really need help actually sticking to a project. What are some things that can make my code less verbose too?


Thank you!

> I really need help actually sticking to a project

I am not sure I can help with that :) Self control is hard, there's so many cool things to build.

> What are some tips about Rust that I could use?

I'm sorry, this is too vague.

> What are some things that can make my code less verbose too?

Not all kinds of verbosity are a bad thing. But usually there are libraries to help with this. For example, writing your own error implementations can be verbose, but thiserror can help with that.


thank you so much for responding! this actually means a lot to me. if there isn't a library for something, should i write my own?


It certainly can't hurt, and you'll probably learn a lot while doing so :)


thank u so much. i am impressed by all of ur work, this is amazing wow.


Why are you this handsome. It's not fair



What are the meaningful changes?


https://youtu.be/oyLBGkS5ICk?si=F7R2thY7vkNbbo3a&t=1800

The semantics of semantic versioning: you might be screwed



Well they're wrong and so is their language of choice. Any language that pollutes the global namespace when you import a lib did it wrong. For all of JS's faults, they nearly got this one right.

Also, even in JS this is still a problem when you really do want all packages to share the same version of the lib, regardless if you're able to import multiple. Sometimes you just don't want multiple instances.

Npm and yarn offer a solution for that too, by forcing a certain version resolution but that then you're on your own if it isn't compatible.



I would contend that this is an issue with namespace collision / poor namespace management than it is an issue with semver.

For example when I have a REST API endpoint I always prefix it with /<module>/<major-version>/ - would never expect to host both old and new versions on the same base URL


The thing is, just updating the name/artifact is a half-assed solution required by existing packaging solutions with a flat and/or non-versioned namespace.

If more packaging solutions allowed for graceful coexistence of multiple library versions, this would be a non-issue. The problem isn't really semver vs naming.


Even if coexistence of multiple library versions is supported, there are scenarios where things break.

For example, the "libc apocalypse" situation in Rust https://github.com/rust-lang/libc/issues/547

Here after the "libc" module released a major version, the definition for the `void` C type in two versions of the lib are considered by the compiler as two different types, resulting in breakages everywhere around the library ecosystem.

There are also scenarios for dynamic languages / runtime errors.

My main problem with the current SemVer spec, is that it does not mention the multiple lib versions problem, and promises the dependency hell issues can be avoided simply by updating major version number. Thus encouraging to break backward compatibility freely.

Also note, it's not the case that SemVer is intended only for languages supporting multiple library versions in one app. SemVer is a product of Ruby community, and Ruby has a global namespace for classes and unable to have several versions of a lib simultaneously.

In 2000s Ruby library authors were breaking compatibility left and right, neglecting elementary compatibility practices. If you were working on an application, practically every time when you update dependencies, the application would break.

So (in 2011 ?) they came out with this "manifesto" (Why such a big name? This scheme of versioning was well established in linkers and sonames of all Unix-like systems for decades - it goes back to at least 1987 paper "Shared Libraries in SunOS").

It's a good thing SemVer acknowledged finally that compatibility is a serious matter. Only that it's better to discourage compatibility breakages. An in cases when it's really needed (I agree such cases exists), there are things to take care of in addition to simply increasing major version num.


It's 2023 and I'm still losing the battle in the workplace to build semantic versioning into each of our APIs.

them: "Don't worry, we compile the git commit hash in. Sometimes." me: silent sobbing


2.0.0... Which means they are breaking backward compatibility too.


Don't worry, this was released ten years ago, so if you haven't run it any issues with it yet you're probably fine.


They are. They have changed the conditions that they suggest incrementing minor version numbers on.


Does the prior version or the new version determine whether a change is a breaking change?


Outside of dependencies, application code is basically

   1.{infinitely_incrementing_integer}
How many application APIs expose v2, v3, v4? You just append to v1 and add proxies to map old APIs to new ones.


Nobody uses semantic versioning for online APIs, because it's expensive to maintain all of the existing versions of the API for all of time. That means that you make very few backwards-incompatible releases, which has a tendency to clump your backwards-incompatible changes into large, planned releases. Stripe's API is a great example of this.

Semver is meant for library versioning. When they say "API" they mean the API that the library exposes.


Many developers said this to me (about API versioning) when I was a PM and somehow I struggled to believe it was as intractable as they said.

I suspect the reality is the human cost of dealing with versioning in an API is the problem. Not that "it can't be done" but that nobody wants to incur the downstream costs in their lived work, either of running a v1 and a v2 binding, or of managing transitions, or of the whole "is a superset capable of managing with the reduced subset" thing.

If you want to be light hearted, IPv4 -> IPv6 is a versioning example where clean break is demanded, and we're at 35% worldwide. Clearly, sometimes, you have to wear a 20+ year versioning cost.

I used to thought experiment "what if we wrote a complete functional client in python, for every version change we release, and gave it to every client" type ideas. Incur the cost in them, to convert for our benefit.

Tech leads always went to no.


> I suspect the reality is the human cost of dealing with versioning in an API is the problem.

Well, yes.

- You release a new API version. Now you find a bug. Now you need to fix the bug twice.

- You decide to change the way a feature works. You can't get rid of the old code. Now you have two copies of the same API that work differently and need to be documented and run tests. Perhaps they each need to maintain their own data models.

- Every API version means more code. That's more room for security vulnerabilities.


It’s certainly tractable, but the amount of effort can vary significantly on the amount of difference between the two versions.

In the simplest case, you can start by duplicating all of your existing API code, and make it addressable as a new endpoint (e.g. https://example.com/v2).

Now imagine you make a change like renaming all the API operations or all the parameters. You can make this directly in the v2 code without changing anything else; and you can maintain the two APIs in parallel like this indefinitely.

However, the maintenance costs of this approach can become significant over time, e.g.

1) Perhaps you develop a new feature, made available as a new API operation. Do you need to implement this for both API versions? Maybe some customers are still using V1 and they want the feature. Depending on how much V1 and V2 implementations have drifted, this effort might be significantly more than copying the code.

1b) And anytime you duplicate effort like this, you may also need to duplicate tests, canaries, monitors, client SDKs, and any other moving part.

2) If you radically change the data layer underneath the API, or some other critical aspect of its implementation, then you may need to make that change twice, in both V1 and V2 — unless it is encapsulated sufficiently such that the code for both can call it.

For example, if you have decided that you need to add caching to a common read API, then you may need to implement that in both of the V1 and V2 code path (unless the implementation of both versions calls some module common to both, and you can change this module to add caching - but this isn’t always possible).

3) There are some crosscutting concerns that inevitably affect all API versions. For example, if you are changing the way that authorization logic works, or changing the way that actions interact with resources, then for correctness/safety/security reasons, it may be necessary to ensure that all API versions operate identically. If you build a new access control feature for the V2 API, then that doesn’t do you any good if the V1 API ignores the same constraints. The same may also be true for observability changes like logging and monitoring — you may need to apply them to all APIs.

If you have sufficient reason to make a breaking change to the API, then it is likely that whatever motivated the new API will also cause their implementations to begin drifting apart. The more they drift, the greater the maintenance effort can be.

On the other hand, if the design of your system is such that you can largely leave the code for V1 alone, and you are sure you won’t need to change V1 even as you are significantly improving V2 (including all downstream dependencies, which the V1 code might also call into) then it might not be so problematic to operate multiple API versions this way. You would also duplicate all of your tests, etc. and leave the ones for V1 alone.

Indeed — if this is what your comment is getting at — in some systems you could conceivably implement the V1 API in terms of a call into the V2 code, as a sort of compatibility layer. (And that would also be a safer approach, if it is feasible, with respect to crosscutting concerns like authorization)

If you do end up actively maintaining and developing both API versions, then the total effort can in some cases become greater than 2x the cost of one API version, due to the additional effort of making sure that both code paths work correctly for all possible interactions.

If you end up having this conversation with someone in the future, then ask why the effort is greater than copying the V1 code to a new V2 endpoint and proceeding to change V2. This should focus the conversation on the specific implementation challenges unique to the situation.


It almost defeats the point of versioning on networked/web APIs really.

On the one hand you have library code that is semantically versioned, and for good reason those versions are immutable so if something changes you have to pull down a version. The old version is always going to work unless it's wrapping a poorly designed web API.

Web APIs are mutable, also for good reason, but it seriously hammers home the fact that whatever you build is forever and once it's out there, you can't walk it back.

The end result of that though isn't incredible API design, it usually forces your hand.


It's actually quite common to use the major version of semver for API versioning. Users of the API don't get to choose minor and patch versions though, they also don't need to because there's no breaking changes in them (ideally :) ).


Semver is hardly just major version numbers.


When you do want to maintain multiple simultaneous versions of an API that can be used simultaneously, then you might employ a SemVer style approach. Really, each version of the endpoint is addressed separately (as its own endpoint).

AWS has employed this strategy before when making significant changes to APIs. I believe DynamoDB is on version 2 for example.

I’d agree that versioning services this way is uncommon due to the expense of maintaining multiple simultaneous code stacks (which can be more than 2x the effort of one stack - see my other comment in this thread). But if client experience is paramount and/or migration is difficult then it may make sense.


I always thought semver was specifically for dependencies. It even mentions packages and dependencies multiple times at the start.

Anyone deploying their web app or something with meticulously selected semver changelogs is wasting a lot of time.


The main difference appears to be about deprecation.

From

> Minor version Y (x.Y.z | x > 0) MUST be incremented if new, backwards compatible functionality is introduced to the public API. It MAY be incremented..

To

> It MUST be incremented if any public API functionality is marked as deprecated.

Plus a new section on how to handle deprecating functionality.


I just wish so many libraries didn't get compliance with SemVer by never releasing a 1.0.0


The worst apart about SemVer is that people expect that anything that LOOKS like SemVer follows the SemVer spec, however mediocre it may be. SemVer doesn't count as an Engineering spec, its a bunch of arbitrary shit that you can or not follow, and has absolutely no meaning or enforcement.


Did you just argue that version numbers that do not have a spec are less arbitrary than the ones that do? Surely its entire point is to (at least attempt to) solve the gripe you seem to have.


The main problem remains not solved: https://github.com/semver/semver/issues/771


I agree with the first comment on that issue that that is not an issue with semver, and arguably not even an issue with the package manager.

It protects you against yourself, and it forces maintainers to keep their libraries up to date less all their users disappear for inability to upgrade.


What does that have to do with the semantic versioning scheme?


This remains a problem regardless of whether a library uses semantic versioning or not. How would the semantics of version numbers affect whether multiple copies of a library are included in a project?


This is answered multiple times in the ticket.

> The main problem with the current Semantic Versioning doc is that by not mentioning this issue it encourages developers to break backward compatibility, giving them impression that increased major version number will protect the clients from trouble.

> Instead, breaking changes should be discouraged - in majority of cases it is trivial to keep backward compatibility. (For example, instead of altering function behavior, keep the old function as it is and introduce a new function).

> If breaking change is still needed, the developer has to make sure the new version of the library can be loaded simultaneously with the old version. In many languages that will require giving the library new identifier, using new namespace / package in the library source code.

--------

> In the current form the document is an invitation to break backwards compatibility any time, under a false promise that changing major version number solves all dependency hell issues

--------

> Once again, the point of this ticket is to:

1. Remove the false promise that SemVer solves dependency hell by simply increasing major version.

2. Discourage unnecessary compatibility breakages, when it's trivial to maintain compatibility.

3. Several sentences of advice for the cases when comparability breakage is really needed.


> Discourage unnecessary compatibility breakages

You can't standardize "unnecessary"

> when comparability breakage is really needed.

This isn't a problem with semver, it's a problem with developer attitudes and behavior. Arguably semver does nothing to affect this either way, and adding "several sentences" will do exactly nothing to change it.


Please stop trolling. The several sentenses are not suggested to change "it".


Encourages to break compatibility? I'd say it does the opposite. It forces developers to fess up that they broke compatibility. I avoid any lib that has rapidly increasing major versions. Tells me the maintainer is wishy washy and gives 0 hoots about stability.


You arguments here and in another comment support my view actually, although shaped as if they were opposing it.

> Tells me the maintainer is wishy washy and gives 0 hoots about stability.

Maybe the maintainer have read the famous engineering best practice manifesto SemVer, and rests assured that increased major version number allows him to introduce incompatible changes safely?


Well, it is safe Just not good for users.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: