暴露 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