3 ms·
> However, code artifacts should be versioned separately from all of this - you create builds all the time, and they, by default, use fixed versions of both ext
by q3k 5y ago
> However, code artifacts should be versioned separately from all of this - you create builds all the time, and they, by default, use fixed versions of both external and internal libraries, as there is no reason to spend time updating a library that already does all you need - especially not while it is work-in-progress.
That is an anti-pattern from my experience, as it's a vicious cycle that ends up with stale builds. Library authors, unburdened by the requirement to always have a working library will start to ship breaking changes ('you should've pinned it!', they will say, not even thinking about possibly making the change non-breaking or at least having a grace period). This in turn makes library users pin to some 'stable' versions early, and these pins will never get updated. That in turn makes library authors care even less about not breaking master, and this escalates into a tangled ball of mutual version pins and no easy way to untangle it. The purest form of dependency hell. That in turn has negative effects on long-term maintenance: for example, if a library introduces a security fix, or some other change that needs to be rolled out ASAP to all users. Who is responsible for updating the dependencies then, or backporting the fixes? The library authors, who now have to backport fixes to all possible forks, with an urgent change blocked by having to chase down all library users and convincing them to pull in the backport? Or application authors, who might not know anything about the library codebase? What if there is some other transitive dependency in the chain, that is also pinned at an ancient version? This quickly gets very, very hairy, and tends to be a combinatorial explosion of complexity as new edges of the multiversioned dependency graph appear.
Contrast this to the radically different approach of never breaking HEAD. On every commit, CI picks up what build targets did that particular change influence, and re-runs tests for _all_ of them. Change your own code? That's fine, your affected tests run. Change a library? That's also fine, but tests of all code that depends on it now also run, and you better not have introduced a bug, or that change will simply not be allowed to be merged. That's it. No pinning, no releases for any library, just continue to get code changes merged, and make sure they pass tests. No special process for backports, no special coordination of version bumps, not having to declare stability contracts for every library, no having to mentally juggle a complex tree of versioned dependencies and the backports involved when trying to get a fix landed. Whatever code you push must work and that's it. It is then the job of release teams/automation to qualify a version of the software at a given monorepo version, and if anything breaks unexpectedly - just get it fixed in the code and cut a new release.
Granted, this requires tooling around your monorepo: proper CI, a fast build system that can re-run only affected targets, a proper code review system that lets you work on large changes in chunks, and then merge them at once onto HEAD. But that's something that's generally unavoidable with monorepos.
tl;dr: Choosing to 'not spend time updating a library that already does all you need' is just kicking the can down the road, whatever library you use you must be able to use a newer version of, and it better be early in the process when things can still be changed, rather than when things are on fire.
- tsimionescu 5y agoThe thing is, the advantages of depending on HEAD vs. versioning dependencies have little to do with monorepo vs multiple repos. That is more a concern that depends on how your VCS is managed and how your build system interacts with your VCS, outside of Go tooling. With normal build/package management systems, nothing stops you from setting up your CI/CD system to pick up the latest from master in 20 repos, and nothing stops you from configuring maven to pick up dependencies from different moments in history from various places in your monorepo. Also, this whole 'build everything from head' can't work when shipping versioned software to customers that demand patches and security updates, unless you have absolutely perfect test coverage AND absolutely perfect backwards compatibility, up to the internal API level. This would be like shipping the latest kernel to fix a security problem on a 5 year old system running 2.something.