二进制兼容规则

Akka 维护并验证模块版本之间的向后二进制兼容性。

在本文档的其余部分中,每当提到“二进制兼容性”时,都意味着“向后二进制兼容性”,而不是向前兼容性。

这意味着,只要你的构建不启用inliner(仅限 Scala 的限制),新 JAR 就是旧 JAR 的一个替代品(但并非相反)。

二进制兼容性规则的解释

二进制兼容性保持在:

  • 次要版本补丁版本请注意,“次要”的含义已经转变为对 Akka 2.4.0 更严格的含义,有关详细信息,请阅读「版本控制方案的变化」。

以下两者之间保持二进制兼容性:

具体示例(请阅读「版本控制方案的变化」,了解“2.4 之前”和“2.4 之后”的区别):

  1. # [epoch.major.minor] era
  2. OK: 2.2.0 --> 2.2.1 --> ... --> 2.2.x
  3. NO: 2.2.y --x 2.3.y
  4. OK: 2.3.0 --> 2.3.1 --> ... --> 2.3.x
  5. OK: 2.3.x --> 2.4.x (special case, migration to new versioning scheme)
  6. # [major.minor.patch] era
  7. OK: 2.4.0 --> 2.5.x
  8. OK: 2.5.0 --> 2.6.x
  9. NO: 2.x.y --x 3.x.y
  10. OK: 3.0.0 --> 3.0.1 --> ... --> 3.0.n
  11. OK: 3.0.n --> 3.1.0 --> ... --> 3.1.n
  12. OK: 3.1.n --> 3.2.0 ...
  13. ...

不保留二进制兼容性的情况

如果在 Akka 或 Akka 的短暂依赖中报告了安全漏洞,并且在不破坏二进制兼容性的情况下无法解决,则修复安全问题更为重要。在这种情况下,在发布次要版本时可能不会保留二进制兼容性。这种例外情况会在发布公告中注明。

一些模块被排除在二进制兼容性保证之外,例如:

  • *-testkit模块,因为这些模块仅用于测试,通常重新编译并按需运行。
  • *-tck模块——因为它们可能想要添加新的测试(或强制配置某些东西),以便发现 TCK 应该测试的现有实现中可能出现的故障。这里的兼容性并不能保证,但是它试图使升级过程尽可能顺利。
  • 所有被标记为「可能改变」模板,根据定义,这些模块会受到快速迭代和变化的影响。

何时将完全删除不推荐使用的方法

一旦一个方法被废弃,那么准则是,它将至少保留一个完整的次要版本发布。例如,如果在版本 2.5.2 中弃用它,那么它将一直保留到 2.5 的其余部分,以及整个 2.6。

这是一个指导原则,因为在极少数情况下,经过仔细考虑,可能会出现异常,并且之前删除了该方法。

版本控制方案的变化,自 2.4 以来兼容性更强

自从 Akka 2.4.0 发布以来,一个新的版本控制方案已经生效。

历史上,Akka 一直遵循 Java 或 Scala 的版本控制风格,因为第一个数字意味着epoch,第二个意味着major,第三个意味着minor,因此:epoch.major.minor样式的版本控制方案一直沿用到 2.3.x。

目前,自 Akka 2.4.0 以来,新的版本控制应用于语义版本控制,这更接近于许多人所期望的,版本号被解译为major.minor.patch。这也意味着 Akka 2.5.x 与 2.4 系列版本是二进制兼容的,除了被标记为”可能改变”的 API。

除此之外,Akka 2.4.x 已被制成与 2.3.x 系列兼容的二进制文件,因此没有理由保留在 Akka 2.3.x 上,因为升级是完全兼容的(此后许多问题已得到解决)。

不允许混合版本控制

在 Akka 项目下一起发布的模块将一起升级。例如,将 Akka Actor 2.4.2 与 Akka Cluster 2.4.5 混合是不合法的,尽管两者是二进制兼容的。

这是因为模块可能会假设跨模块边界的内部变更,例如集群中的某些特性可能需要在 Actor 中进行内部变更,但是它不是公共 API,因此这种变更被认为是安全的。

  • 注释:我们建议在构建文件中保留一个akkaVersion变量,并将其重新用于所有包含的模块,因此升级时,只需在这一个位置更改它即可。

“可能改变”的含义

模块描述和文档中使用了”可能改变”,以表示它们包含的 API 可能会在没有任何事先警告的情况下发生更改,并且不包含在二进制兼容性承诺中。可以通过阅读「模块标记为“可能改变”」了解更详细的信息。

API 稳定性注解和注释

Akka 向最终用户提供了非常强的二进制兼容性承诺。但是,Akka 的某些部分被排除在这些规则之外,例如,内部或已知的不断发展的 API 可能被标记为这样,并作为整体稳定模块的一部分进行装运。一般来说,任何破坏都是通过反预测和方法添加来避免和处理的,但是某些已知尚未完全冻结(或完全内部)的 API 被标记为这样,并且随时可能更改(即使尽最大努力保持它们的兼容性)。

INTERNAL API 和 @InternalAPI 标记

当浏览源代码和/或寻找可调用的方法时,尤其是从 Java 中,它没有像 Scala 那样具有丰富的访问保护系统,有时你可能会发现用/** INTERNAL API */注释或@akka.annotation.InternalApi注解的方法或类。

这些类没有兼容性保证。它们可能会在小版本中更改甚至消失,用户代码不应该调用它们。

Akka 在其内部广泛使用的 Scala private[akka]模式的细节:在 Scala 中充当“只能从给定包访问”的方法或类在原始 Java 字节码中被编译为public (!) 。Scala 理解的访问限制作为元数据存储在类文件中。因此,这样的方法被安全地禁止从 Scala 访问,但是 Java 用户不会被javac编译器警告这一事实。请注意这一点,不要调用内部 API,因为它们可能在没有任何警告的情况下发生更改。

@DoNotInherit 和 @ApiMayChange 注解

除了特殊的内部 API 注解之外,Akka 中还存在两个注解,并专门处理以下用例:

  • @ApiMayChange,它标志着已知尚未完全稳定的 API。
  • @DoNotInherit,它标志着 API 是在封闭世界的假设下设计的,因此不能扩展到 Akka 之外,否则这样的代码将面临二进制不兼容的风险。例如,可以使用此注解标记接口,虽然类型是公共的,但并不意味着由用户代码进行扩展。这允许向这些接口添加新方法,而不会冒破坏客户端代码的风险。此类 API 的示例是FlowOps特性或 Akka HTTP 域模型。

请注意,在必须更改 API 时总是采取最努力的方法,并且尽可能避免破坏,但是这些注解允许试验、收集反馈并稳定我们可以构建的最佳 API。

二进制兼容性检查工具链

Akka 使用了 LightBend 维护的「迁移管理器」,简称MiMa,用于强制实现二进制兼容性,并将其保持在承诺的位置。

所有Pull请求都必须通过 MiMa 验证(这是自动发生的),如果检测到失败,例如,如果更改发生在内部 API 中,则可以进行手动异常覆盖。

跨 Scala 版本的序列化兼容性

Scala 不维护跨主要版本的序列化兼容性。这意味着,如果使用 Java 序列化,则如果用 Scala 的不同版本序列化,则不能保证对象可以被反序列化。

内部的 Akka Protobuf 序列化程序可以通过enable-additional-serialization-bindings显式启用,也可以通过akka.actor.allow-java-serialization = off隐式启用(从安全的角度来看,这是可取的),这都不会受到序列化兼容性问题的影响。


英文原文链接Binary Compatibility Rules.