变更记录
- 2022-04-27:初稿
状态
提议中摘要
当前的 SDK 以单一的单体 Go module 形式构建。本 ADR 描述了我们如何将 SDK 重构为更小、可独立版本化的 Go module,以便于维护。背景
Go module 对软件项目在稳定版本号(任何高于 0.x 的版本)方面提出了一些要求,即任何 API 破坏性变更都必须提升主版本号,而这在技术上会创建一个新的 Go module(带有 v2、v3 等后缀)。 以这种方式保持模块 API 兼容需要相当多的审慎思考与工程纪律。 Cosmos SDK 是一个相当庞大的项目,诞生于 Go module 出现之前,并且尽管多年来一直用于生产环境,却始终处于 v0.x 发布阶段。这并不是因为它不是生产级软件,而是因为对于这样一个大型项目而言,遵守 Go module 所要求的 API 兼容性保证相当复杂。到目前为止,通常被认为更重要的是在必要时能够破坏 API,而不是要求所有用户为了适应破坏性变更而更新所有包的导入路径,以配合 v2、v3 等版本发布。除此之外,还存在与 protobuf 生成代码相关的其他复杂性,这些问题将在另一份 ADR 中处理。 尽管如此,社区对语义化版本控制的诉求一直非常强烈,而单一 Go module 的发布流程使得及时发布针对孤立功能的小改动变得非常困难。发布周期通常超过六个月,这意味着原本一两天就能完成的小改进,会被整个单体发布周期中的其他事项所拖慢。决策
为改善当前状况,SDK 正在当前仓库内被重构为多个 Go module。关于如何推进这项工作,已经有过大量讨论,其中一些开发者主张更大的模块范围,而另一些则主张更小的范围。这两种方式各有利弊(将在下文的影响部分讨论),但当前采用的方法如下:- Go module 的范围通常应限定为一组特定且内聚的功能(例如 math、errors、store 等)
- 当代码从核心 SDK 中移除并迁移到新的 module 路径时,应尽最大努力通过别名和包装类型避免现有代码中的 API 破坏性变更(如 链接 和 链接 所示)
- 新的 Go module 在打上
v1.0.0标签之前,应先迁移到独立域名cosmossdk.io下,以便为未来可能更适合迁移到独立仓库的情况预留空间 - 所有 Go module 在打上
v1.0.0标签之前,都应遵循链接中的指导原则,并使用internal包来限制暴露的 API 表面 - 如果存在明确可改进之处,或者需要移除遗留依赖(例如 amino 或 gogo proto),新的 Go module API 可以偏离现有代码;但前提是旧包仍应尝试通过别名和包装器避免 API 破坏
- 在尝试仅仅将现有包转换为新的 Go module 时需要谨慎:链接。总体来看,通常更安全的做法是直接创建新的 module 路径(必要时追加 v2、v3 等),而不是试图把旧包变成一个新的 module。
影响
向后兼容性
如果遵循上述指导原则,在现有 API 中使用指向新 Go module 的别名或包装类型,那么对现有 API 应当不会产生破坏性变更,或者只会产生非常有限的破坏性变更。积极影响
- 独立的软件组件将能更早达到
v1.0.0 - 面向特定功能的新特性将能更快发布
消极影响
- SDK 自身以及各个项目中需要更新的 Go module 版本会更多,不过希望其中大多数会是间接依赖
中性影响
进一步讨论
进一步的讨论主要发生在链接以及 Cosmos SDK Framework Working Group 内部。参考资料
Changelog
- 2022-04-27: First Draft
Status
PROPOSEDAbstract
The current SDK is built as a single monolithic go module. This ADR describes how we refactor the SDK into smaller independently versioned go modules for ease of maintenance.Context
Go modules impose certain requirements on software projects with respect to stable version numbers (anything above 0.x) in that any API breaking changes necessitate a major version increase which technically creates a new go module (with a v2, v3, etc. suffix). Keeping modules API compatible in this way requires a fair amount of fair thought and discipline. The Cosmos SDK is a fairly large project which originated before go modules came into existence and has always been under a v0.x release even though it has been used in production for years now, not because it isn’t production quality software, but rather because the API compatibility guarantees required by go modules are fairly complex to adhere to with such a large project. Up to now, it has generally been deemed more important to be able to break the API if needed rather than require all users update all package import paths to accommodate breaking changes causing v2, v3, etc. releases. This is in addition to the other complexities related to protobuf generated code that will be addressed in a separate ADR. Nevertheless, the desire for semantic versioning has been strong in the community and the single go module release process has made it very hard to release small changes to isolated features in a timely manner. Release cycles often exceed six months which means small improvements done in a day or two get bottle-necked by everything else in the monolithic release cycle.Decision
To improve the current situation, the SDK is being refactored into multiple go modules within the current repository. There has been a fair amount of debate as to how to do this, with some developers arguing for larger vs smaller module scopes. There are pros and cons to both approaches (which will be discussed below in the Consequences section), but the approach being adopted is the following:- a go module should generally be scoped to a specific coherent set of functionality (such as math, errors, store, etc.)
- when code is removed from the core SDK and moved to a new module path, every effort should be made to avoid API breaking changes in the existing code using aliases and wrapper types (as done in Link and Link)
- new go modules should be moved to a standalone domain (
cosmossdk.io) before being tagged asv1.0.0to accommodate the possibility that they may be better served by a standalone repository in the future - all go modules should follow the guidelines in Link
before
v1.0.0is tagged and should make use ofinternalpackages to limit the exposed API surface - the new go module’s API may deviate from the existing code where there are clear improvements to be made or to remove legacy dependencies (for instance on amino or gogo proto), as long the old package attempts to avoid API breakage with aliases and wrappers
- care should be taken when simply trying to turn an existing package into a new go module: Link. In general, it seems safer to just create a new module path (appending v2, v3, etc. if necessary), rather than trying to make an old package a new module.
Consequences
Backwards Compatibility
If the above guidelines are followed to use aliases or wrapper types pointing in existing APIs that point back to the new go modules, there should be no or very limited breaking changes to existing APIs.Positive
- standalone pieces of software will reach
v1.0.0sooner - new features to specific functionality will be released sooner
Negative
- there will be more go module versions to update in the SDK itself and per-project, although most of these will hopefully be indirect