暴露 SDK 中的微版本¶
虽然我们正在努力设计易于使用的 OpenStack API,但各种编程语言的 SDK 始终是开发者体验的重要组成部分。本文档包含有关如何在面向 OpenStack 的 SDK(软件开发工具包)中处理 微版本 的建议。
本文档识别出两种类型的交付物,我们通常称之为 SDK。它们在暴露微版本给消费者方面所推荐的方法会有所不同。
高级 SDK 或简称 SDK 是隐藏底层 API 细节,构建自身抽象层的 SDK。它对向后和向前兼容性以及功能发现的处理方式独立于底层 API 使用的方式。 Shade 是 OpenStack 中此类 SDK 的一个示例。
语言绑定 紧密遵循底层 API 的结构和设计。它通常尝试在底层 API 之上构建尽可能少的额外抽象层。示例包括所有 OpenStack
python-<service-name>client库。
注意
如果您不确定,您应该编写一个高级 SDK。使用 SDK 的好处是以一种对编程语言和使用的框架来说自然的方式来使用 API。微版本对于不专门从事 API 设计的开发者来说,可能看起来很陌生和令人困惑。
本文档中使用的概念
- 消费者
与 SDK 交互的编程代码,以及其作者。
- 微版本
API 版本,如 :doc:microversion_specification 中定义。为简单起见,本指南将 版本 作为 微版本 的同义词使用。
注意
在您的 SDK 中使用
微版本这个词时,请小心避免与语义版本控制产生关联。微版本与补丁版本不同,在语义版本控制的意义上甚至可能是主要的。- 主版本
实际上并不是 :doc:microversion_specification 中定义的 API 版本,而是 API 的一个独立代,与其他代在相同的 HTTP 端点树中共存。
主版本通过 URL 中的
/v<NUMBER>部分来区分,并且是微版本的第一个组成部分。例如,在微版本1.42中,1是主版本。注意
我们似乎还没有为第二个组成部分建立一个确定的名称。
由于主版本可能会实质性地改变 API 的结构,包括改变微版本机制本身,因此 SDK 通常应尝试保持在请求的主版本范围内(如果有)。
- 协商
客户端和服务器之间就最合适的通用版本达成一致的过程。协商应只发生一次,其结果应在整个会话中缓存。
注意
我们将在所有示例中使用 Python 编程语言,但建议适用于任何编程语言,包括静态编译的语言。 在这里,我们将使用虚构的“猫即服务”API 及其 python-catsclient SDK。
高级 SDK¶
通常,SDK 不应将底层 API 微版本暴露给用户。输入和输出数据的结构不应依赖于使用的微版本。应采用特定于编程语言和/或所使用数据格式的手段来指示某个特征或行为的存在或缺失。
例如,在 Python 中,当前微版本中缺失的字段可以用
None值表示,在 Java 中可以用null值表示,或者在 Rust 中其类型可以是Option<ActualDataType>。import catsclient sdk = catsclient.SDK() cat = sdk.get_cat('fluffy') if cat.color is None: print("Cat colors are not supported by this cat server") else: print("The cat is", cat.color)在此示例中,SDK 在
get_cat调用期间协商 API 微版本,以返回尽可能多的信息。如果结果版本不包含color字段,则将其设置为None。
SDK 应协商能够最好地满足消费者需求的最高微版本。但是,它绝不应协商超出其编写和测试范围的微版本,以避免因 API 的未来更改而导致令人困惑的破坏。 显而易见的是,SDK 不应对服务器返回的任何微版本崩溃或表现出未定义行为。任何不兼容性应尽快以对给定编程语言来说自然的形式表达出来。
例如,Python SDK 应该在调用无法在 SDK 和服务器都支持的任何微版本中表达的方法时引发异常。
import catsclient sdk = catsclient.SDK() cat = sdk.get_cat('fluffy') try: cat.bark() except catsclient.UnsupportedFeature: cat.meow()在实际使用它们之前,允许检测受支持的功能也很有用。
import catsclient sdk = catsclient.SDK() cat = sdk.get_cat('fluffy') if cat.can_bark(): cat.bark() else: cat.meow()在此示例中,
can_bark使用协商的微版本来检查bark调用是否可以正常工作。
注意
如果可能,SDK 应该告知消费者所需的 API 微版本以及为什么无法使用它。这可能是微版本泄漏给消费者的唯一地方。
如果可能,主版本应以相同的方式处理,不应暴露给用户。如果不可能,SDK 应该从可用版本中选择最新的主版本。
语言绑定¶
本质上只是 API 的语言绑定的低级 SDK 紧密遵循底层 API。因此,它必须将微版本暴露给消费者,并且必须以与 API 相同的方式进行。
import catsclient
client = catsclient.v1.get_client()
cat = client.get_cat('fluffy') # executed with no explicit version
try:
cat.bark(api_version='1.42') # executed with 1.42
except catsclient.IncompatibleApiVersion:
# no support for 1.42, falling back to older behavior
cat.meow() # executed with no explicit version
注意
我们建议所有调用都接受一个显式的 API 微版本,该版本直接发送到底层 API。如果未提供,则不应发送任何版本。
import catsclient
client = catsclient.v1.get_client()
cat = client.get_cat('fluffy') # executed with no explicit version
with cat.use_api_version('1.42') as new_cat:
new_cat.bark() # executed with 1.42
在某些编程语言中,特别是那些没有函数默认参数的语言,将版本参数添加到所有调用中可能不方便。可以使用其他方式来实现相同的结果,例如临时上下文对象。
主版本¶
低级 SDK 应该明确说明它正在使用哪个主版本。可以通过命名空间 API 或将显式主版本作为参数来完成。首选方法取决于 API 的主版本差异有多大。
import catsclient
client = catsclient.v1.get_client()
或者
import catsclient
client = catsclient.get_client(1)
使用 Python 作为示例,可以是
支持的版本¶
import catsclient
client = catsclient.v1.get_client()
min_version, max_version = client.supported_api_versions()
cat = client.get_cat('fluffy') # executed with no explicit version
if max_version >= (1, 42):
cat.bark(api_version='1.42') # executed with 1.42
else:
# no support for 1.42, falling back to older behavior
cat.meow() # executed with no explicit version
强烈建议提供一种查询服务器支持的版本范围的方法。
最低版本¶
import catsclient
try:
client = catsclient.v1.get_client(api_version='1.2')
except catsclient.IncompatibleApiVersion:
sys.exit("Cat API version 1.2 is not supported")
cat = client.get_cat('fluffy') # executed with version 1.2
try:
cat.bark(api_version='1.42') # executed with 1.42
except catsclient.IncompatibleApiVersion:
# no support for 1.42, falling back to older behavior
cat.meow() # executed with version 1.2
应用程序通常具有它们能够工作的 API 最低版本。建议提供一种接受该版本并将其用作默认版本(如果未提供显式版本)的方法。
如本例所示,使用此方法的 SDK 必须提供一种明确的方式来指示请求的版本不受支持,并尽快进行指示。
版本列表¶
import catsclient
try:
client = catsclient.v1.get_client(api_version=['1.0', '1.42'])
except catsclient.IncompatibleApiVersion:
sys.exit("Neither Cat API 1.0 nor 1.42 is supported")
cat = client.get_cat('fluffy') # executed with either 1.0 or 1.42
# whichever is available
if client.current_api_version == (1, 42):
# Here we know that the negotiated version is 1.42
cat.bark() # executes with 1.42
else:
# Here we know that the negotiated version is 1.0
cat.meow() # executes with 1.0
# The default version can still be overwritten
try:
cat.drink(catsclient.MILK, api_version='1.66') # executed with 1.66
except catsclient.IncompatibleApiVersion:
# no support for 1.66, falling back to older behavior
cat.drink() # executed with either 1.0 or 1.42 whichever is available