Semantic Releases (Part 2): Collaboration Requires Compromise
In part 1, we looked at a simplified workflow for maintaining concurrent semantic releases. We saw that the process introduces significant development overhead, but we didn’t delve into the benefits that balance the additional work. In part 2, we’ll start to discuss the rationale behind semantic releases.
Comparing stability levels: unstable, stable, and feature-stable releases
(I should stress that “stable” does not mean “reliable” in the context of release models — it only describes the process of maintaining a release that does not intentionally break backward compatibility.)
In addition to the versioning system, the concepts underlying Semantic Versions provide us with the foundation for definitions of several release models with varying levels of change impact.
An unstable (or “rolling”) release generally doesn’t have defined life cycles, and in some cases doesn’t provide version numbers at all. It is a single release sequence, and each update in that sequence is allowed to have any level of impact, including breaking changes.
A stable release model will have releases that provide at least major version numbers (it may omit minor version and patch version numbers). During the lifetime of a major-version’s release, changes are allowed to introduce new features and fix bugs, but breaking changes are not allowed.
A feature-stable release model will have releases that provide at least major and minor version numbers. During the lifetime of a minor-version’s release, changes should only fix bugs. New features can be added in new minor releases, and breaking changes can be made in new major releases.
As release models become more stable, they introduce additional overhead. Maintaining multiple branches simultaneously is, obviously, more work than maintaining a single branch.
There are several trade-offs involved in the decision to branch, how often to branch, and how long to maintain branches. Life cycles with more overlap allow developers to support customers with more diverse schedules for updating from release to release. Branching more often allows developers to publish new features more often to the users that want them. Maintaining branches for longer periods allow developers to support users that do not want new features in products where low change volume is a top priority.
Every choice that the developer makes to support more users comes at the cost of more work on their part, so the life cycle plan they settle on represents a set of compromises made between the developer and the various groups of users they want to support.
The benefits of branching
Among the use cases for which feature-stable branching is a hard requirement are heavily regulated industries where change must be minimized for legal or contract reasons. These include government and military contracts, medical, aeronautical, and automotive industries, to name a few. Software updates in many of these cases requires re-validation which can be time consuming and costly. Updates may contribute to negative perception of reliability, as well. In the automotive industry, updates to a vehicle’s software may be classified as a “recall.” Minimizing the number of updates in a release channel and clearly communicating the severity of updates that are published can help customers avoid unnecessary changes. Feature-stable branches help minimize change within a specific, predefined period of time, to maximize the value of validation.
A related concern is the certification and validation of software components against standards such as FIPS 140. In the case of certified binary builds of components, a developer might choose to preserve a version for as long as possible, and even share the same build of the component across multiple minor releases. Doing so requires a feature-stable branch in which to produce the binary build, and compatible runtime interfaces in any later branches that will share the component.
Scientific researchers often cite the desire for a computing platform with extremely minimal change volume for the duration of a research project. This helps ensure that software changes don’t alter the results of processing data. It may be more important for their results to be repeatable than it is for them to receive feature updates during a project.
Enterprise customers may have very long processes for accepting feature updates. Enterprises are often composed of multiple businesses who’ve merged or been acquired, in various stages of integration. An enterprise workflow might involve a platform team producing and testing a basic OS image with security instrumentation that must be used by all business units. They may build and then test various container images, VM templates, and other base layers. Once that has been published, business units may further adapt those images to their own environments. They might prepare them for operation within their authentication domains, and adjust administrative access. They’ll also test those images and then publish them internally. Individual service or application groups within those business units then consume the images, build application-specific images, test them, and deploy. In order for those services to remain patched for security, all of this work has to be completed within the migration window that exists in the overlap between the life cycle of releases, and in very complex environments, that means that every step needs to be planned in advance.
In addition to supporting those users directly, producing feature-stable software branches also supports the other software vendors that work with them. In order to build an application that users can run on their systems, the operating system and build environment that third-party developers use must have the oldest version of interfaces that could be present on the run-time systems. If a third-party developer builds on a system with newer software interfaces, the resulting build may require those newer interfaces. The availability of feature-stable branches ensures continued access to build environments for the ecosystem of developers who want to work with the kind of environments described above.
Ultimately, this complex arrangement is about collaboration. It allows an ecosystem of developers and their customers to work on schedules that don’t need to strictly align. That’s important because some customers are going to work in two week sprints, and some customers are going to plan in quarters. If you produce new versions at regular intervals, then customers can plan work further out ahead, knowing what you will be providing them at that time.
Does that mean that every developer needs to support semantic versions and concurrent releases?
Maintaining multiple branches significantly increases development overhead, and it’s a legitimate point of view that volunteer developers do not owe that level of support to users who aren’t paying for it. For projects that don’t publish semantic releases, enterprise software vendors may have an opportunity to create additional value for their customers by applying semantic release development practices to extend the life cycle of an upstream release.
Does every user need semantic versions and concurrent releases?
After describing the mechanics of semantic releases and their utility, it might surprise you when I say “no.” Some use cases are served as well or better by a less stable release model.
Unstable releases are common for stand-alone software. Software that doesn’t load external, third-party plugins, which doesn’t communicate with network services whose protocols might change, and which isn’t intended to be used as a component (i.e. a dependency) in scripts or higher-level applications may have very little reason to maintain concurrent releases. If the developers maintained older release series in parallel with newer ones, there may be no reason to believe that the older release series has any users. The user community may universally prefer the latest release. In those cases, there’s no benefit to the additional work required to continue maintaining previous release branches. Unstable release models are also ideal if they are provided as a source code repository to developers who want to branch that code on their own in order to compose custom builds that include changes according to their own preferences. As examples, Arch and Gentoo are unstable releases. Those distributions are widely used as self-contained software distributions, and they’re also used to create stable systems like SteamOS and ChromeOS.
Stable release series have an even broader set of suitable use cases. Stable release series can efficiently deliver bug fixes and new features to users with minimal friction, while maintaining backward compatibility to ensure that those updates don’t disrupt established workflows. Development overhead is slightly higher than unstable releases, but less so than feature-stable release models. Users get the benefit of predictable life cycles, and migration windows for feature changes or deprecation that can require them to adjust their workflows, and developers incur minimal additional work. For most installations which don’t require enterprise-level support, a stable major release is probably ideal. Examples of stable releases include Debian and CentOS Stream.
I think the question of stable vs feature-stable is at the root of a lot of debate about release models and software distribution right now, and in part there’s a bit of a mental shortcut involved. I’ve heard a number of users observe that a product has semantic releases, and conclude that if they are present then there must be a reason for them, and if there is a reason for semantic releases, then it applies to their use case, without a specific understanding of what the reasons are or what use cases they support. There is also, in part, a matter of familiarity. It has long been common for software vendors to use semantic versions. We’re accustomed to that. It has been much less common for vendors to publish both minor-version stable release series and a major-version stable release series of the same product. Because that’s uncommon, it’s harder to grasp what to expect from such an arrangement. Some of the implications are quite surprising. For example, if a vendor publishes version “2” as a major-version stable series and versions “2.0,” “2.1,” etc, then users should expect that a fully patched installation of version “2” will always be newer than any installation of a minor release. In this case, “2 > 2.1”. It would be fair to make the point that semantic versions communicate changes very clearly, and a bare major-release version doesn’t. However, the major-release version remains advantageous for the audience that wasn’t interested in release notes and feature announcements. Users who manage self-supported environments, built on stable platforms that don’t break backward-compatibility, are often better served by a major-version stable release model.
Though, it seems that it will take some time to get used to the idea.