[HN Gopher] Go's major versioning sucks - From a fanboy ___________________________________________________________________ Go's major versioning sucks - From a fanboy Author : wagslane Score : 91 points Date : 2022-03-04 17:39 UTC (5 hours ago) (HTM) web link (wagslane.dev) (TXT) w3m dump (wagslane.dev) | dilatedmind wrote: | Thoughts 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 wrote: | > 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. | bww wrote: | I'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. | jmyeet wrote: | I 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 wrote: | This comment is empty of content. All you've said is "go is | bad" over and over again. Who would be persuaded by this? | jrockway wrote: | I disagree with all of this. Go's package management system is | a breath of fresh air compared to all other languages. Nobody | ever has any problem grabbing dependencies. There is only one | way to do it and it's built into the language runtime; if you | have Go installed you can install the modules. Other | applications on your system can, with no effort (virtualenv, | etc.) use different versions of those modules. Dependency | installation can't print messages to my terminal. | | It's a joy. | | Importing modules from a particular URL is a great way to | handle them. No central server required! Everyone can name | their module "utility utils" or whatever. And, nobody is | forcing you to use Github to host your modules, you can put | them on any web server you control and import them as | example.com/your-thing. Decentralized. Easy to use. | | If I had two complaints, they would be: | | 1) Library authors that think "replace" directives propagate up | to consumers. They do not. They are ignored when you depend on | the module. If you depend on one of these libraries and want | the workaround that the author has smoothed over with a | "replace" directive, you have to copy that directive to your | own go.mod. | | 2) Library authors that distribute one go module for their | super-complicated server and their super-simple client. This | results in unnecessary dependency explosion. (Loki 1.x was an | example of this pattern. Their server depends on things like | Kubernetes, where their client only depends on net/http. But if | you naively import their client, suddenly your project depends | on Kubernetes.) | | (2a would be no concept of test-only modules. Some modules are | only needed for the tests; it would be nice to not propagate | these up to consumers. It isn't an actual problem, though, just | a "would be kind of nice" or "I could see why they did that" if | it existed.) | | Neither of these are Go issues, just maintainer issues that can | happen with any language. | zht wrote: | sorry is github.com/username/module is much worse than | | com.arbitrary.x.y.z ? | tkiolp4 wrote: | In 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. | philosopher1234 wrote: | What 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? | didip wrote: | I absolutely disagree with everything said here. | | Maven naming convention is absolutely atrocious. And don't tell | me that naming convention is optional. To be part of Java | ecosystem Maven naming convention is a requirement. | dmitriid wrote: | Additionally, `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 | kstenerud wrote: | I 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. | mfcl wrote: | Why not v0 instead? It's better suited for that purpose. | maxmcd wrote: | Your 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 wrote: | What 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 wrote: | This is a comment on an article about Go package management, | which enforces that packages a versioned with a semver-esque | string. | paskozdilar wrote: | Your point being? | pgwhalen wrote: | v1, v2, v3 are not valid module versions in Go, so it | requires a slightly different solution. | paskozdilar wrote: | If 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. | pgwhalen wrote: | True, though if you have code in separate repositories | you might want to stick to _some_ versioning scheme to | use the module tooling to your advantage. | icholy wrote: | I 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 | | It's not perfect, but it takes a lot of toil out of working with | SIV. | iandinwoodie wrote: | SIV = Semantic Import Versioning [0] | | 0. https://github.com/golang/go/issues/44550 | paskozdilar wrote: | >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 wrote: | Honestly, 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. | tikkabhuna wrote: | It would be great to be able to declare a dependency with an | alias at the go.mod level. Rather than requiring the publisher | of a dependency to update their paths you can choose. | | That way if a dependency does a minor upgrade that breaks your | code but you also need some new functionality, you could depend | on the old code in a particular package and new code in | another. | cube2222 wrote: | > 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). | paskozdilar wrote: | >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. | pcwalton wrote: | Well, that's clearly not the way Go operates. Go makes | incompatible changes between minor releases; they just don't | break _type signatures_. For example, debug /pe | ImportedLibraries(), which is supposed to return all the | libraries that a module imports, was stubbed out to return | "nil, nil" in a minor release [1]. This is clearly an | incompatible change, but as it didn't cause code to fail to | compile the Go team deemed it semver compatible. | | Edit: Apparently this is wrong! See replies below. | | [1]: https://fasterthanli.me/articles/abstracting-away- | correctnes... | slrz wrote: | _For example, debug /pe ImportedLibraries(), which is | supposed to return all the libraries that a module imports, | was stubbed out to return "nil, nil" in a minor release | [1]_ | | I just looked at the Git history and this is plain false. | It already looked that way when the big source tree move | (src/pkg/ -> src/) was done in 2014. Tracing it back | further (to before Go 1.0 times, when there wasn't even a | builtin error interface yet and the function returned | os.Error), ImportedLibraries was *never* implemented in | debug/pe. | philosopher1234 wrote: | I think taking a highly abstract definition of backcompat | is not useful. We need a practical definition of back | compat. If there are no (or effectively no) downstream | consequences of a change, it is clearly backcompat. If | there are some downstream consequences, you get into | judgment call territory, but it still may be worth it. We | cannot create a perfect universal rule here, and Amos is a | fool for holding that standard so rigidly. | pipe_connector wrote: | It's worth noting that the author of that article was | mistaken, there was likely some other issue with their | software than what they described here. ImportedLibraries() | in the pe package has never done anything other than | returned nil, nil. This wasn't changed in a minor release. | You can browse the source history here: https://github.com/ | golang/go/blame/master/src/debug/pe/file.... | slaymaker1907 wrote: | No, it really shouldn't be. It makes it impossible to | distinguish between those "theoretically this a breaking | change" kind of changes and the "you're going to need to | rewrite a bunch of code" kind of changes. At the very least | libraries should be allowed to define how the library must be | used in order for their semantic versioning to apply. For | languages which allow "import * from x" style imports, the | library should still be allowed to add in new functions | without that being a breaking change. | | If you want your software to be pristine forever, you really | need to pin your dependencies, ideally via copying the source | of the library into your repo so you aren't reliant on a | package manager being available in the future. For library | developers, regardless of versioning scheme you need to avoid | ANY breaking changes whatsoever. Instead of changing an | existing function, introduce a new one so that your | downstream users can still access the old behavior while | keeping up to date. Trust me when I say this will be much | easier most of the time than patching old versions with | security updates and bug fixes. | paskozdilar wrote: | API is API. Either a library implements it or not. There is | no room for "just this small API change uwu" in large-scale | development. | slimsag wrote: | How 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. | pgwhalen wrote: | This is a good point, arguably even fixing any bug breaks | compatibility, at least if you're a kernel maintainer. An | (in)famous Linus quote: If a change | results in user programs breaking, it's a bug in the | kernel. We never EVER blame the user programs. | slaymaker1907 wrote: | I don't think this is all that useful of a quote outside | of operating systems (and even there I still find it of | questionable value). You really need to define how people | can use your software (at least at a high level) and | receive backwards compatibility guarantees. | | Even in the Linux example, kernel modules do not receive | compatibility guarantees because it is difficult to keep. | You may need to rewrite your module depending on how it | is written when upgrading the kernel. It also doesn't | apply in case of security vulnerabilities and certain | classes of bugs. Technically viruses can be user programs | which rely on those vulnerabilities and even excluding | those, there are cases where some API seems benign but | later turns out to be flawed (like precise timers in the | context of browsers). | noctune wrote: | I think it's a difference in what one considers to be | "the API". Linux is very much to the de facto API side, | whereas some other project might be more one the de jure | API side with a rigid specification and allowing any | change within that. Most things are probably somewhere | between the two. | paskozdilar wrote: | > How do you define "compatible"? | | I define it as "provides the equivalent API". | viraptor wrote: | There's an extra dimension here though - support. Projects | don't have unlimited resources which in majority of the cases | means that only one major version is live. | | For downstream consumers that gives 2 options: get stuck on | an old version silently sometimes, or deal with an occasional | breakage during usual dependencies updates. If the old | version is used for talking to some external service, you | will break one day. | Groxx wrote: | There absolutely is fuzziness in incompatibility. | | I can change one part of an API and leave another untouched - | that's part compatible, part incompatible. It's only an issue | if you used the changed part. | | (If you think that first one always counts... what if the | changed part is literally called | YouMustNotUseThisFunctionOrYourCodeWillAlwaysBreak() ? It's | clearly implied to not be part of your _intended_ API, | despite technically being part of it.) | | I can add something to a type, in a way that's backwards | compatible at compile time... but common reflection patterns | might cause everyone's code to explode. | | I can make a change that solves a bug that someone was | accidentally relying on by doing the wrong thing, but doesn't | affect compile-time behavior, nor runtime for anyone using | the library the way they should. But that bug-user's code is | now broken, is this an incompatible change? | paskozdilar wrote: | > I can change one part of an API and leave another | untouched - that's part compatible, part incompatible. It's | only an issue if you used the changed part. | | When you commit to a version 1, you assume that every user | of the package is using every feature you provide through | your official API. If you break any slightest piece of the | API, you've broken compatibility. It might "kinda work" for | many users, but it will almost surely cause significant | pain for many others. | | > I can make a change that solves a bug that someone was | accidentally relying on by doing the wrong thing, but | doesn't affect compile-time behavior, nor runtime for | anyone using the library the way they should. But that bug- | user's code is now broken, is this an incompatible change? | | It is not an incompatible change, and it is the | responsibility of the bug-user's code to fix the bug in his | program. | | Of course, when such a thing happens on a large-enough | scale, the API developer sometimes cannot afford to "fix" | the behavior and force countless users to fix their | programs, so the quirk just becomes de-facto part of the | API. | ithkuil wrote: | > I can make a change that solves a bug that someone was | accidentally relying on by doing the wrong thing ... | | There is even a "law" for that: https://www.hyrumslaw.com/ | mftb wrote: | This is the answer. It's all internal, he says in his case. | They know what they're doing with their own stuff. Staying on | v0 is just another signifier that it's one of their internal | things, they need to handle specially. | bborud wrote: | > Then just use 0.y.z versions and be done with it? | | Yeah, that would work, except a lot of people read 0.x.y | versions as "alpha quality". Regardless of the actual code | quality. | pgwhalen wrote: | Is this a problem inside a small company though? I would | expect there to be much better signals about how alpha-ish a | library is in that setting (i.e. talking to your coworkers). | bborud wrote: | Funny you should ask. Yes, it can be when you use third | party software and people have version number hangups. | pgwhalen wrote: | Version number hangups are indeed a problem, I don't mean | to suggest my organization has escaped them. But if you | can wade through those successfully, the technical | solution itself often does make sense. | gowld wrote: | If you expect a certain version to be valuable long term, | promote it to Z.0 version. It's OK to to have | AwesomeButRapidlyChangingLibrary-2022.03.0 branched from | 0.y.z. | bborud wrote: | Or, as some people recommend, skip directly to V2 as the | first version. | philosopher1234 wrote: | This is true and unfortunate, but I think the engineering | value of meaningful versions is important and I expect that | with enough time people will adapt to understanding a | different meaning of 0.x. | | And besides, people are right to understand 0.x is risky b.c | you are not guaranteeing backwards compatibility. | digisign wrote: | If it is changing all the time, then it is "alpha quality." | pgwhalen wrote: | >> 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? | | FWIW, I also work in an organization that thinks of libraries | this way, and we've found success and simplicity in versioning | (for production) our Go libraries as 0.X.0 where X is just a | monotonically increasing number generated by the build | pipeline. | mananaysiempre wrote: | ... Zerover[1]? | | [1] https://news.ycombinator.com/item?id=28154187 | pgwhalen wrote: | Yep! I think it's fair to needle open source software, but | it absolutely makes sense for a lot of internal development | to adopt this sort of versioning policy. | earthboundkid wrote: | > 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 wrote: | How 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 wrote: | _> 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.) | henvic wrote: | I 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 wrote: | Lots 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/ | earthboundkid wrote: | All 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 wrote: | Why would a package released as v1 need to be supported | indefinitely? | [deleted] | paskozdilar wrote: | Semantic versioning serves a purpose - it makes it possible | to automatically update dependencies to the latest compatible | version without breaking code. | | If you don't need automatic updates, then any kind of | versioning is fine. Hell, you could get away with just using | a single incrementing integer. v1, v2, v3, ..., v225883, etc. | fsociety wrote: | My 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 | john567 wrote: | Don'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. | olliej wrote: | Sorry, 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 wrote: | Let'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 wrote: | So 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. | paskozdilar wrote: | Actually, Go used a very elegant solution to a hard problem | of modularization: directories. A directory == a package. | Import path specifies a path to the directory that contains | a package. | | A package may, or may not, decide to have a stable API and | document it. If it does, and it commits to the version 1.0, | then it basically gives a promise: "This package will not | change its API in a way that will break correctness of | currently-correct programs that use it". | | Since a package == a directory, if you want to keep package | compatibility, you must not change the contents of the | import directory. Therefore, you need to create a new one, | preferably called v2/, to put the new code in. | tkiolp4 wrote: | It's an elegant solution for packages that don't use | versioning features like git tags. For every other | package out there that uses git tags, it's just a lazy | solution: why on earth would I want to specify the | version in the path? Git tags solves that problem in a | more elegant way (both from the side of the maintainers | and users). | paskozdilar wrote: | > it's just a lazy solution: why on earth would I want to | specify the version in the path? | | It's not a lazy solution, just opinionated. | | Personally, I love the fact that just by seeing the | import path I know exactly which codebase is used. I | don't have to open my go.mod, see which version I'm | using, clone the repository, dig through the history to | check out a specific tag and see what code is actually | used in my program... I just open the repo in my browser | and browse through the directories. There isn't a single | system on earth that doesn't support directories! | | I also love the fact that I don't have to know s**t about | git to use Go. If Go was to suddenly switch to git tags, | not only would it break a massive amounts of existing | code, but would basically force everyone to learn about | git tags just to be able to see what code are they using, | which would raise the amount of paperwork I have to fill | in order to work on the thing I care about. Go is | fundamentally against needless paperwork. | chabad360 wrote: | I'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). | gowld wrote: | There is a versioning scheme. It's not golang's fault if a | publisher doesn't use it. | | https://go.dev/doc/modules/version-numbers | [deleted] | geodel wrote: | Kinda 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. | donatj wrote: | I wrote about this a while ago, it was previously discussed here | | https://news.ycombinator.com/item?id=24429045 | | The post itself | | https://donatstudios.com/Go-v2-Modules | iio7 wrote: | > 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. | lowmagnet wrote: | I worked at a telco with Wordpress powered website. Our control | panels and backend systems were not Wordpress. | cryvate1284 wrote: | Are 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. ___________________________________________________________________ (page generated 2022-03-04 23:00 UTC)