15 ms·
Go’s major versioning sucks – From a fanboy
- iio7 5y ago> In fact, we use Go on the front and backend at Qvault, and we’ve found that it’s wonderful to have standardized formatting, vetting, and testing across the entire Go ecosystem Well, that doesn't seem to be true as Qvault is clearly a Wordpress powered website.
- cryvate1284 5y agoAre you sure that's not just the sales website? Quite common for the sales website to be wordpress and separate from the frontend of the product.
- lowmagnet 5y agoI worked at a telco with Wordpress powered website. Our control panels and backend systems were not Wordpress.
- lanecwagner 5y agoThat's just the landing page/blog. The actual webapp you log into and use is a Go API plus Vue frontend
- icholy 5y agoI wrote a tool that automates: - discovering new major versions in your go.mod (gomajor list) - switching between major dependency versions. (gomajor get) - updating the major version of your own module. (gomajor path) https://github.com/icholy/gomajor https://github.com/icholy/gomajor It's not perfect, but it takes a lot of toil out of working with SIV.
- iandinwoodie 5y agoSIV = Semantic Import Versioning [0] 0. https://github.com/golang/go/issues/44550 https://github.com/golang/go/issues/44550
- henvic 5y agoI recognize that this might be a PITA, but for me there is no easy way out of problems related to major versioning, and the clear answer is sticking with version 0.x.x for a considerate amount of time. It seems people rush to publish version 1.0 (or 2.0, etc.) of their libraries, when they'd be better off just sticking with version 0.x. It's not like that a package isn't ready for production because it is < 1.0. It might as well be. If you're an early adopter, I'd say that is even welcome: you're aware that its API might chance, that its quite new, etc. It gives more confidence than a package with version 7.x (at least in informing you that it's prone to changes, and allowing you to make an informed choice), IMHO.
- no_wizard 5y agoLots of teams, companies, and general best practice all have policies and/or guidelines around versioning, and its usually something to the effect of wait till the package is 1.0 to use it in production. I think that's why there is always a "race" to the 1.0 I think we should just push for more date based versioning, for instance, CalVer[0] [0]: https://calver.org/ https://calver.org/
- earthboundkid 5y agoAll of my Go packages are CalVer v0.year.increment because I'm just a lone developer, so nothing I release can be v1. To be an actual v1, there needs to be a team of people who are committed to keeping a package going indefinitely. I understand why people want to use v1, because it sounds cool and stable, but realistically if you are just releasing code on your own, it is not and cannot be v1.
- zufallsheld 5y agoWhy would a package released as v1 need to be supported indefinitely?
- earthboundkid 5y agoBecause v1 is a social commitment to keeping the API stable and the library working for the foreseeable future. If you don’t want to make that commitment, there’s always v0, which I do in fact use.
- deleted 5y ago[deleted]
- geodel 5y agoKinda useless article. Maybe need to think why they need so many major versions so often to break backward compatibility. And if one just updates minor versions none of those problems occur.
- dilatedmind 5y agoThoughts on a couple of your points: - we don’t need any kind of backwards compatibility, we just update everything. if you don't care about backwards compatibility, then you can stay on v1 forever. Have you considered a monorepo? That would simplify updating packages and give you the behavior you want. - For the client to update, it’s not a simple path change in go.mod if a package moves from v1 to v2, there are breaking changes in either api or behavior. I think this implies more than a simple change to go.mod. This also allows importing both versions of a package if necessary.
- dmitriid 5y ago> there are breaking changes in either api or behavior. So instead off focusing on those changes I have to first fix potential dozens of files for no reason at all.
- paskozdilar 5y ago> So instead off focusing on those changes I have to first fix potential dozens of files for no reason at all. You don't have to, if you don't want to upgrade to a new major version. If you do want to upgrade to a major version, which means that there are breaking changes in a package's API or behavior - you sure as hell want to check the correctness of every single line of code written using that package. Since every file that uses that package must contain an import statement, the import statements are an easily greppable indicator of which files you have to check and potentially fix.
- dmitriid 5y ago> you sure as hell want to check the correctness of every single line of code written using that package Yes, I do. Doing the monkey job of changing every import line (which will be done with a global search/replace) is definitely not that. If you're relying on import statements to tell you which files to check, you're definitely doing something wrong
- deleted 5y ago
- kstenerud 5y agoI just keep all my go project major versions at 1, then use the second number as "major" and the third number as "minor". Anything incompatible increments major, anything compatible increments minor. This avoids all that v2, v3, v4 "nanny knows best" nonsense.
- maxmcd 5y agoYour users are going to have a bad time if they ever import two different libraries that both depend on different fake "major" versions of your library. The newer of the two will be selected, and any incompatibility that the different libraries are not prepared to handle will break things.
- paskozdilar 5y agoWhat purpose does that serve? Unless your version numbers signify something to the user, you might as well just use incremental integers - v1, v2, v3, etc.
- pgwhalen 5y agoThis is a comment on an article about Go package management, which enforces that packages a versioned with a semver-esque string.
- paskozdilar 5y agoYour point being?
- pgwhalen 5y agov1, v2, v3 are not valid module versions in Go, so it requires a slightly different solution.
- paskozdilar 5y agoIf you're not distributing your Go package to others as a library, then you don't need to follow the Go module versions. They are only enforced user-side.
- paskozdilar 5y ago>I agree with the sentiment that we should only increment major versions when making breaking changes, but more often than not breaking changes are really easy to accommodate for. Maybe on a small scale breaking changes don't cause issues, but in large-scale development, you simply can't afford to have a bunch of random CI/CD pipelines fail because a developer decided to "accommodate for" a backwards-incompatible change without separating it to a new import path. >Using different paths for different major versions makes more sense in situations where we may require two different versions of the same package, you know, diamond imports and all that. This is the exception, not the rule, and it seems strange to tap dance around a problem that doesn’t exist in most codebases. Diamond imports are not as rare as you think - it's just that you never run into it in your own code, but in the code of your dependencies. And in Go, the problem is already solved for you so you never even see the solution at work. Let's say you use two libraries for, say, backend and frontend - libbackend and libfrontend. Let's say they both depend on some parser library, say libparser, version 1.0. Now assume that libbackend upgrades the libparser dependency to 2.0, but the import path stays the same and your application - the one that has been successfully auto-updated on a CI/CD server for years and nobody remembers how it works anymore - breaks, and you have no idea why, compiler reports some weird API-incompatibility errors, and now you have to spend two weeks getting back into the codebase and debugging the issue, until you realize you can't really fix it, because the guy developing the language decided that diamond imports are "a problem that doesn’t exist in most codebases".
- jchw 5y agoHonestly, my only complaint about go mod major versioning is just how unintuitive versions after v1 are. While some may disagree about the tradeoffs, I think the reasoning behind it is totally solid, but there are definitely valid complaints about the exact behavior of modules being somewhat confusing. I recall confusion with trying to update a library with no tagged versions, too, for example. That said, I think people greatly undersell how well Go has done packaging. There’s, again, tradeoffs that are at least arguable; but, I hate when people say things like “Didn’t (npm|apt|cargo|…) already solve this?” Honestly, often times the answer is at best “kind of?” — a lot of these packaging and versioning problems remain without complete solutions. Meanwhile, Go has some novel design choices that set it apart. The module proxy is a slightly unfortunate tradeoff, but Go remains one of the only languages with a good story for mostly decentralized package management. The module proxy is more of a hack that gets you some of the advantages of centralization without strictly depending on it. But beneath that, it’s nice that you can pull packages from basically anywhere with several VCSes.
- cube2222 5y ago> Due to our size, we don’t need any kind of backwards compatibility, we just update everything. Then just use 0.y.z versions and be done with it? If the library constantly changes and everybody expects that, then that seems fitting. I like Go's major version handling very much. If it's backwards incompatible, it's basically a new library under the same name and development team. In my opinion making major version updates so painful also incentivizes not making backwards incompatible changes in libraries, which results in the Go ecosystem being very stable overall (something I value a lot in my day-to-day).
- bheadmaster 5y ago>If it's backwards incompatible, it's basically a new library under the same name and development team. This, this, and a hundred more times this. Incompatible is incompatible. There is no "kinda incompatible", "99% compatible" - when it comes to dependencies, they either work properly or they don't. Software should be eternal. Without being strict about semantic versioning, it is impossible to make it so.
- slimsag 5y agoHow do you define "compatible"? Semver doesn't define it. An HTTP server can remain API compatible, and still drop or break support for a major feature you care about. Surely you don't want that to ship in a patch release. Adding a new struct field, even a private one, can break API compatibility in Go if people use your struct without named fields. Do you want a new library in this case or not? Semver doesn't cover CLI compatibility, either, do you want a CLI redesign to remain v1 or become v2? Nuance matters. Stating to "just be strict about semantic versioning" doesn't help, semver is fuzzy.
- paskozdilar 5y ago> How do you define "compatible"? I define it as "provides the equivalent API".
- 5y ago
- earthboundkid 5y ago> This means major version changes are a fairly regular occurrence. Some say that we should just stay on v0, and that’s a reasonable solution. The problem is these ARE production packages that are being used by a wide number of services. We want to Semver. This is a lame problem. v0.Major.Minor-Patch. Done. Yes, semver includes an optional dash for a fourth parameter, and Go supports it.
- pa7ch 5y agoHow does the forth parameter affect version ordering though? Since its a pre-release I would assume v0.1.2-3 actually comes before v0.1.2. You'd have to ensure make all your versions have the dash I guess.
- munificent 5y ago> Since its a pre-release I would assume v0.1.2-3 actually comes before v0.1.2. That's correct. (The really fun weird corner of semver is build suffixes. According to semver 2.0.0, v1.2.3+4 and v1.2.3+5 have no specified relative ordering. According to semver 2.0.0-rc.1, build suffixes are ordered.)
- olliej 5y agoSorry, my reading of the article seemed to imply that I should have multiple copies of a project in a single repository, one for each major version? That’s what branches and tags are for, so what am I missing?
- paskozdilar 5y agoLet's say you write a Go program using a dependency "github.com/foo/bar" v1.0 and post it on the Internet as a regular file (or multiple files), without using git. Someone else downloads the program and runs the usual Go commands: go mod init example.com/program go mod tidy go build The go tool will see the "github.com/foo/bar" path in the import statements, download the code from the repository, compile and everything will work. Now, let's say the "github.com/foo/bar" module gets a backwards-incompatible change, but does not change the import path, and you attempt to do the above process again. This time, the go tool will download the incompatible version of "github.com/foo/bar" and the build will fail - or even worse, succeed but have some logic bugs that will go unnoticed until some massive shit happens.
- masklinn 5y agoSo the Go project used a lazy solution to a hard problem (packages are git repositories without even a ref as version) instead of, say, having an actual versioning scheme, therefore you get to implement this by hand using nonsense logic.
- gowld 5y agoThere is a versioning scheme. It's not golang's fault if a publisher doesn't use it. https://go.dev/doc/modules/version-numbers https://go.dev/doc/modules/version-numbers
- chabad360 5y agoI'm very confused. Go literally uses refs as versions. However to allow for situations where you'll need to update a module because of a bug fix or something like that, Go allows you to safely automatically update to the latest minor version, on the assumption that there are no API breaking changes. If there is such a change, Go wants the module owner to increment the major version so you don't get bugs just from updating. I will agree that the ergonomics of this method aren't perfect by any means. But what it does accomplish, is that it forces you to declare your dependency explicitly (which seems to be one of Go's underlying principles).
- john567 5y agoDon't make breaking changes. Make new APIs then transition users. Remove old APIs as needed when usage is very low. Niche people that depend on the old API will stick to an older version and be happy with that.
- jrockway 5y agoThat is not possible. You get no usage data if someone uses your open source library, and the maintainer of the library that depends on you could be dead. This is a fine approach when you mail your API users an invoice every month, just tack on "hey start using v2". But open source libraries don't have that invoice cycle. The most you can do is tack on a warning in a future patch version, but they would have to update to that first.
- jmyeet 5y agoI like Go but Go has made some serious misteps (IMHO), which the author touches on. Dependency management in Go is so incredibly bad it's hard to fathom. Did no one on the early design team ever deal with depenencies? Java is so much better here and came years earlier. Go would've been so much better had they just copied any of the Java options from Day One. So this versioning thing is just weird and I agree with the author. It's a strange thing to have opinions on and an even stranger opinion to have on that thing. But the thing that gets me is the whole putting "github.com/username/module" into code is just awful.
- philosopher1234 5y agoThis comment is empty of content. All you've said is "go is bad" over and over again. Who would be persuaded by this?
- zht 5y agosorry is github.com/username/module is much worse than com.arbitrary.x.y.z ?
- tkiolp4 5y agoIn the source file one could just say import “username/module” And in the go.mod file the whole URL can stay require github.com/username/module v1.2.3 I only see advantages with this approach and I imagine it’s a rather “easy to add” feature that can be even backwards compatible.
- klabb3 5y agoI would take this further, simply `import "module"` would be nicer. (I don't care about anyone's github username, and I often need to fork projects and today that means mindless search and replace) Conflict resolution within the same module could be handled in the go.mod file: `require alias github.com/...`
- philosopher1234 5y agoWhat if I publish gitlab.com/username/module and i dont know anything about your github project? This seems like a pretty obvious disadvantage, so its surprising you failed to see it. What are the advantages?
- dmitriid 5y agoAdditionally, `go build` fails when there's a major version mismatch and the error is obtuse, contains no good info on what the user should do
- fsociety 5y agoMy biggest pain with Go modules has been the fact that projects use 0ver [0] and some are backwards compatible updates and others aren’t. But it was designed in a way to force everyone to use semver. It gets worse if you have a mono-repo because OSS projects may depend on a module which depends on a module which uses 0ver but minor versions are backwards incompatible. This means that dependency conflicts become a nasty situation. Then you get into opinion-based world where: - everyone should just use semver properly. - everyone shouldn’t use v0 for production. Or variations on this where some are production-ready and others aren’t. - everyone should use v0 and only have backwards compatible updates. Then fork if you have an backwards incompatible update. Feels like everyone loses with how this works today. [0] https://0ver.org https://0ver.org
- donatj 5y agoI wrote about this a while ago, it was previously discussed here https://news.ycombinator.com/item?id=24429045 https://news.ycombinator.com/item?id=24429045 The post itself https://donatstudios.com/Go-v2-Modules https://donatstudios.com/Go-v2-Modules
- bww 5y agoI’ve taken to just making /v0 or /v1 the root of every package I maintain. Even though it’s not required I think it’s a good practice. It doesn’t affect the package name (unless you choose to), only the import path. So it eliminates any ambiguity about the interface you intend to use and it only affects import statements. Personally I think that’s a good balance.
- deleted 5y ago[deleted]
- renewiltord 5y agoThe idea is sound. A major version incompatibility means the library is a different library. In the Java world, Apache Commons Collections does this. There it is helpful since you can then have multiple versions of Collections in a single program. In the Golang case, encoding this is smart, especially since Golang's structural typing makes it easier to use. However, there is an ergonomics problem. I knew of all these things and I still had trouble upgrading a Golang FUSE driver recently. It's worth keeping in mind that I don't write code as a matter of course.
- yoursunny 5y agoI develop experimental software where backwards compatibility is the least of my concerns. To avoid the SemVer nonsense imposed by Go and NPM, I simply name every version to be 0.0.yyyymmdd derived from commit date. If Ubuntu and DPDK can name their versions by date, why can't I?
- p1necone 5y agoIt sounds like Go has implemented package versioning /absolutely right/, this is how things should be. Two pain points stand out to me in the article though: > Users of packages aren’t alerted about new major versions > For the client to update, it’s not a simple path change in go.mod The tooling should definitely be enhanced to make these things easier to do - probably a flag for -u to update to major versions, and a builtin script to globally update all the imports when a major version has changed. Or maybe a way to specify a single 'default' major version in one place and then only needing to be explicit when using one that differs from that one. But this means you'd have files that could use different package versions if moved to a different project which could be unexpected.
- thayne 5y ago> Users actually need to grep through their codebase and change each import statement to point to the new major version You also need to do this if the url for the package (for example if the repo changes to a different hosting service,or is moved to a different organization), or if you want to build with a fork at a different url.
- oxplot 5y agoFan boy? maybe. Informed? doesn't look like it: > this is redundant because the local go.mod file already has the semantic version of all dependencies tracked The whole point of having full version path in each file is to allow gradual upgrade to a new major version/a completely different package. Listen to Russ Cox go over this many times. > Users of packages aren’t alerted about new major versions Fair point. > For the client to update, it’s not a simple path change in go.mod It's a simple command: gofmt -w -r '"github.com/logrusorgru/aurora" -> "github.com/logrusorgru/aurora/v3"' ./ > Maintainers should be able to increment the major version via Git tags. Go is not tied to git or any other single SCM system. They can however be pragmatic about this. > I get why these rules exist, and I think they are great for large open projects You either don't, or you think that smaller/trivial project's requirements somehow take priority over larger ones. There are plenty of toy/application specific/scripting programming languages built to be used to write a few hundred lines or glue things together. Go specifically and repeatedly states that it's aimed at large, distributed and long term projects with lots of changes over time and many contributors. You know that going in. So you're criticizing (poorly I might add) the very notions that this language is built upon! This is like criticizing an industrial kitchen equipment for not having pretty colors and curvy shapes to match your home decor.
- deleted 5y ago[deleted]
- tsimionescu 5y ago> The whole point of having full version path in each file is to allow gradual upgrade to a new major version/a completely different package. Listen to Russ Cox go over this many times. That only works if v2 of the library is crazy enough to keep all of the v1 apis around as well. Otherwise, when I switch version the old import path stops working. > gofmt -w -r '"github.com/logrusorgru/aurora" -> "github.com/logrusorgru/aurora/v3"' ./ Cool. Does that also cleanup git history so that I don't have to look at hundreds of files which needlessly got changed?
- oxplot 5y ago> That only works if v2 of the library is crazy enough to keep all of the v1 apis around as well. Otherwise, when I switch version the old import path stops working. Gradual upgrade, as in you can update a single file to use the next major version. Not sure what you’re on about. > hundreds of files which needlessly got changed If you have hundreds of files and upgrading to a new major version all at once, you’re creating the exact risk that Go is trying to mitigate.
- AtNightWeCode 5y ago"In other words, for every major version, we are encouraged to maintain a new copy of the entire codebase." Use tags or branches.
- egberts1 5y agoX.Y.Z versioning: Bump Z up if the parameter arguments are the same but can take more argument options or a new API was added. Bump Y up if an existing function argument types got fundamentally changed. Bump X if it is a major rework, entailing new test suite, deleted obsoleted functions, engine redesign. Of course, you should do doing some form of versioning check for same library across disparate subsystems (frontend, backend, middleware). No more breakage.