”工欲善其事,必先利其器。“—孔子《论语.录灵公》
首页 > 编程 > Monite 的 API 版本控制

Monite 的 API 版本控制

发布于2024-11-08
浏览:438

我们都喜欢拥有闪亮的新工具,但讨厌不断更新它们的苦差事。这适用于任何事物:操作系统、应用程序、API、Linux 软件包。当我们的代码因为更新而停止工作时,这是痛苦的,而当更新甚至不是我们发起的时,痛苦会加倍。

在 Web API 开发中,您始终面临着每次新更新都会破坏用户代码的风险。如果你的产品是API,那么每次这些更新都会令人恐惧。 Monite 的主要产品是我们的 API 和白标 SDK。我们是一家 API 优先的公司,因此我们非常注意保持我们的 API 稳定且易于使用。因此,重大变更的问题几乎是我们优先考虑的问题。

一个常见的解决方案是向您的客户发出弃用警告并很少发布重大更改。突然之间,您的发布现在可能需要几个月的时间,并且某些功能必须保持隐藏甚至不合并,直到每个下一个版本为止。这会减慢您的开发速度并迫使您的用户每隔几个月更新一次集成。

如果您加快发布速度,您的用户将不得不过于频繁地更新他们的集成。如果延长发布之间的时间,公司的进展就会变慢。你给用户带来的越不方便,对你来说就越方便,反之亦然。这当然不是最佳方案。我们希望按照自己的节奏前进,而不会对现有客户造成任何破坏,而常规弃用方法是不可能做到这一点的。这就是我们选择替代解决方案的原因:API 版本控制.

这是一个非常简单的想法:随时发布任何重大更改,但将它们隐藏在新的 API 版本下。它为您提供了两全其美的优势:用户的集成不会经常被破坏,并且您将能够以您喜欢的任何速度移动。用户可以随时迁移——没有任何压力。

考虑到这个想法的简单性,它对任何公司来说都是完美的。这就是您期望在典型的工程博客中读到的内容。遗憾的是,事情并没有那么简单。

注意价格

API 版本控制非常困难。一旦你开始实施它,它虚幻的简单性很快就会消失。遗憾的是,互联网从未真正警告过您,因为有关该主题的资源少得惊人。他们中的绝大多数人争论 API 版本应该放在哪里,但只有少数文章试图回答:“我们如何实现它?”。最常见的是:

  • 将同一 Web 应用程序的不同版本放入单独的部署中
  • 复制版本之间已更改的单个路由
  • 复制每个版本的整个版本化应用程序

单独的部署可能会变得非常昂贵且难以支持,复制单个路由不能很好地扩展到大型更改,并且复制整个应用程序会创建大量额外的代码,以至于在几个版本之后您就会开始淹没其中。

即使您尝试选择最便宜的,版本控制的负担也会很快赶上。一开始,它会感觉很简单:在这里添加另一个模式,在那里添加业务逻辑的另一个分支,并在最后复制一些路由。但是,如果版本足够多,您的业务逻辑将很快变得难以管理,许多开发人员会弄错应用程序版本和 API 版本,并开始对数据库中的数据进行版本控制,您的应用程序将变得无法维护。

您可能希望永远不会同时拥有超过两个或三个 API 版本;您每隔几个月就可以删除旧版本。如果您只支持少数内部消费者,确实如此。但您组织之外的客户不会享受每隔几个月就被迫升级的体验。

API 版本控制很快就会成为基础设施中最昂贵的部分之一,因此提前进行深入研究至关重要。如果您只支持内部消费者,那么使用 GraphQL 等工具可能会更简单,但它很快就会变得与版本控制一样昂贵。

如果您是一家初创公司,明智的做法是将 API 版本控制推迟到开发的后期阶段,此时您有资源来正确执行此操作。在那之前,弃用和附加变更策略可能就足够了。您的 API 并不总是看起来很棒,但至少您可以通过避免显式版本控制来节省大量资金。

我们如何实现 API 版本控制?

经过几次尝试和许多错误后,我们处于十字路口:我们上面提到的先前版本控制方法的维护成本太高。经过我们的努力,我设计了以下完美版本控制框架所需的要求列表:

  1. 维护大量版本很容易”以确保版本控制不会减慢我们的功能开发速度
  2. 删除旧版本很容易”以确保我们可以毫不费力地清理我们的代码库
  3. 创建新版本不太容易”以确保我们的开发人员仍然有动力尝试解决没有版本的问题。
  4. 维护版本之间的变更日志很容易”以确保我们和我们的客户始终能够确定版本之间的真正差异

遗憾的是,我们现有的方法几乎没有其他选择。这时我想到了一个疯狂的想法:如果我们尝试构建一些复杂的、适合工作的东西——比如 Stripe 的 API 版本控制,会怎么样?

经过无数次实验,我们现在拥有了 Cadwyn:一个开源 API 版本控制框架,它不仅实现了 Stripe 的方法,而且显着构建于其之上。我们将讨论它的 Fastapi 和 Pydantic 实现,但核心原则与语言和框架无关。

凯德温的工作原理

版本变更

所有其他版本控制方法的问题是我们重复太多。当我们的合约只有一小部分被破坏时,为什么我们要复制整个路由、控制器甚至应用程序?

借助 Cadwyn,每当 API 维护者需要创建新版本时,他们都会将重大更改应用于最新的架构、模型和业务逻辑。然后他们创建一个版本更改——一个封装新版本和先前版本之间所有差异的类。

例如,假设以前我们的客户可以创建一个具有地址的用户,但现在我们希望允许他们指定多个地址而不是单个地址。版本更改如下所示:

class ChangeUserAddressToAList(VersionChange):
    description = (
        "Renamed `User.address` to `User.addresses` and "
        "changed its type to an array of strings"
    )
    instructions_to_migrate_to_previous_version = (
        schema(User).field("addresses").didnt_exist,
        schema(User).field("address").existed_as(type=str),
    )

    @convert_request_to_next_version_for(UserCreateRequest)
    def change_address_to_multiple_items(request):
        request.body["addresses"] = [request.body.pop("address")]

    @convert_response_to_previous_version_for(UserResource)
    def change_addresses_to_single_item(response):
        response.body["address"] = response.body.pop("addresses")[0]
Cadwyn 使用

instructions_to_migrate_to_previous_version 为较旧的 API 版本的模式生成代码,这两个转换器函数是让我们能够维护任意数量版本的技巧。该过程如下所示:

  1. Cadwyn 使用change_address_to_multiple_items 转换器将所有用户请求从较旧的 API 版本转换为最新的 API 版本,并将它们传送到我们的业务逻辑
  2. 业务逻辑、其 API 响应和数据库模型始终根据最新的 API 版本进行定制(当然,即使在新版本中删除了旧功能,它仍然必须支持旧功能)
  3. 业务逻辑生成响应后,Cadwyn 使用change_addresses_to_single_item 转换器将其转换为客户端请求者当前使用的旧 API 版本。

我们的 API 维护人员创建版本更改后,他们需要将其添加到我们的 VersionBundle 中,以告诉 Cadwyn 此 VersionChange 将包含在某个版本中:

VersionBundle(
    Version(
        date(2023, 4, 27),
        ChangeUserAddressToAList
    ),
    Version(
        date(2023, 4, 12),
        CollapseUserAvatarInfoIntoAnID,
        MakeUserSurnameRequired,
    ),
    Version(date(2023, 3, 15)),
)

就是这样:我们添加了一项重大更改,但我们的业务逻辑仅处理单个版本 - 最新版本。即使我们添加了数十个 API 版本,我们的业务逻辑仍然不受版本控制逻辑、不断重命名、if 和数据转换器的影响。

版本链接

版本更改取决于 API 的公共接口,我们几乎从不向现有 API 版本添加重大更改。这意味着一旦我们发布了该版本,它就不会被破坏。

因为版本更改描述了版本内的重大更改,并且旧版本中没有重大更改,所以我们可以确定我们的版本更改是完全不可变的 - 它们永远不会有理由更改。不可变实体比业务逻辑的一部分更容易维护,因为它总是在发展。版本更改也会被一个接一个地应用——在版本之间形成一系列转换器,可以将任何请求迁移到任何较新版本,并将任何响应迁移到任何旧版本。

API Versioning at Monite

副作用

API 合约比模式和字段复杂得多。它们由所有端点、状态代码、错误、错误消息,甚至业务逻辑行为组成。 Cadwyn 使用我们上面描述的相同 DSL 来处理端点和状态代码,但错误和业务逻辑行为是另一回事:它们不可能使用 DSL 进行描述,它们需要嵌入到业务逻辑中。

这使得此类版本更改的维护成本比所有其他版本更改都要昂贵得多,因为它们会影响业务逻辑。我们将此属性称为“副作用”,并且由于其维护负担,我们会不惜一切代价避免它们。所有想要修改业务逻辑的版本更改都需要标记为具有副作用。它将作为一种了解哪些版本更改是“危险”的方法:

class RequireCompanyAttachedForPayment(VersionChangeWithSideEffects):
    description = (
        "User must now have a company_id in their account "
        "if they want to make new payments"
    )

它还允许 API 维护者检查客户端请求是否使用包含此副作用的 API 版本:

if RequireCompanyToBeAttachedForPayment.is_applied:
    validate_company_id_is_attached(user)

没有银弹

Cadwyn 有很多好处:它大大减轻了我们开发人员的负担,并且可以集成到我们的基础设施中以自动生成变更日志并改进我们的 API 文档。

然而,版本控制的负担仍然存在,即使是复杂的框架也不是灵丹妙药。我们尽力仅在绝对必要时才使用 API 版本控制。我们还通过设立一个特殊的“API 委员会”来尝试使我们的 API 在第一次尝试时就正确。在任何实施开始之前,所有重要的 API 更改都会由我们最好的开发人员、测试人员和技术编写人员进行审核。

特别感谢 Brandur Leach 在 Stripe 上发表的 API 版本控制文章,以及他在我实现 Cadwyn 时向我提供的帮助:没有他的帮助,这是不可能的。

版本声明 本文转载于:https://dev.to/monite/api-versioning-at-monite-3ba3?1如有侵犯,请联系[email protected]删除
最新教程 更多>
  • 如何使用替换指令在GO MOD中解析模块路径差异?
    如何使用替换指令在GO MOD中解析模块路径差异?
    在使用GO MOD时,在GO MOD 中克服模块路径差异时,可能会遇到冲突,其中可能会遇到一个冲突,其中3派对软件包将另一个带有导入套件的path package the Imptioned package the Imptioned package the Imported tocted pac...
    编程 发布于2025-03-12
  • 为什么Microsoft Visual C ++无法正确实现两台模板的实例?
    为什么Microsoft Visual C ++无法正确实现两台模板的实例?
    The Mystery of "Broken" Two-Phase Template Instantiation in Microsoft Visual C Problem Statement:Users commonly express concerns that Micro...
    编程 发布于2025-03-12
  • 如何使用FormData()处理多个文件上传?
    如何使用FormData()处理多个文件上传?
    )处理多个文件输入时,通常需要处理多个文件上传时,通常是必要的。 The fd.append("fileToUpload[]", files[x]); method can be used for this purpose, allowing you to send multi...
    编程 发布于2025-03-12
  • 如何使用Regex在PHP中有效地提取括号内的文本
    如何使用Regex在PHP中有效地提取括号内的文本
    php:在括号内提取文本在处理括号内的文本时,找到最有效的解决方案是必不可少的。一种方法是利用PHP的字符串操作函数,如下所示: 作为替代 $ text ='忽略除此之外的一切(text)'; preg_match('#((。 &&& [Regex使用模式来搜索特...
    编程 发布于2025-03-12
  • \“(1)vs.(;;):编译器优化是否消除了性能差异?\”
    \“(1)vs.(;;):编译器优化是否消除了性能差异?\”
    答案: 在大多数现代编译器中,while(1)和(1)和(;;)之间没有性能差异。编译器: perl: 1 输入 - > 2 2 NextState(Main 2 -E:1)V-> 3 9 Leaveloop VK/2-> A 3 toterloop(next-> 8 last-> 9 ...
    编程 发布于2025-03-12
  • Python读取CSV文件UnicodeDecodeError终极解决方法
    Python读取CSV文件UnicodeDecodeError终极解决方法
    在试图使用已内置的CSV模块读取Python中时,CSV文件中的Unicode Decode Decode Decode Decode decode Error读取,您可能会遇到错误的错误:无法解码字节 在位置2-3中:截断\ uxxxxxxxx逃脱当CSV文件包含特殊字符或Unicode的路径逃...
    编程 发布于2025-03-12
  • 如何使用Java.net.urlConnection和Multipart/form-data编码使用其他参数上传文件?
    如何使用Java.net.urlConnection和Multipart/form-data编码使用其他参数上传文件?
    使用http request 上传文件上传到http server,同时也提交其他参数,java.net.net.urlconnection and Multipart/form-data Encoding是普遍的。 Here's a breakdown of the process:Mu...
    编程 发布于2025-03-12
  • 如何检查对象是否具有Python中的特定属性?
    如何检查对象是否具有Python中的特定属性?
    方法来确定对象属性存在寻求一种方法来验证对象中特定属性的存在。考虑以下示例,其中尝试访问不确定属性会引起错误: >>> a = someClass() >>> A.property Trackback(最近的最新电话): 文件“ ”,第1行, attributeError:SomeClass实...
    编程 发布于2025-03-12
  • 如何从PHP中的数组中提取随机元素?
    如何从PHP中的数组中提取随机元素?
    从阵列中的随机选择,可以轻松从数组中获取随机项目。考虑以下数组:; 从此数组中检索一个随机项目,利用array_rand( array_rand()函数从数组返回一个随机键。通过将$项目数组索引使用此键,我们可以从数组中访问一个随机元素。这种方法为选择随机项目提供了一种直接且可靠的方法。
    编程 发布于2025-03-12
  • Java是否允许多种返回类型:仔细研究通用方法?
    Java是否允许多种返回类型:仔细研究通用方法?
    在Java中的多个返回类型:一种误解类型:在Java编程中揭示,在Java编程中,Peculiar方法签名可能会出现,可能会出现,使开发人员陷入困境,使开发人员陷入困境。 getResult(string s); ,其中foo是自定义类。该方法声明似乎拥有两种返回类型:列表和E。但这确实是如此吗...
    编程 发布于2025-03-12
  • 大批
    大批
    [2 数组是对象,因此它们在JS中也具有方法。 切片(开始):在新数组中提取部分数组,而无需突变原始数组。 令ARR = ['a','b','c','d','e']; // USECASE:提取直到索引作...
    编程 发布于2025-03-12
  • 如何限制动态大小的父元素中元素的滚动范围?
    如何限制动态大小的父元素中元素的滚动范围?
    在交互式接口中实现垂直滚动元素的CSS高度限制问题:考虑一个布局,其中我们具有与用户垂直滚动一起移动的可滚动地图div,同时与固定的固定sidebar保持一致。但是,地图的滚动无限期扩展,超过了视口的高度,阻止用户访问页面页脚。$("#map").css({ marginT...
    编程 发布于2025-03-12
  • 为什么PYTZ最初显示出意外的时区偏移?
    为什么PYTZ最初显示出意外的时区偏移?
    与pytz 最初从pytz获得特定的偏移。例如,亚洲/hong_kong最初显示一个七个小时37分钟的偏移: 差异源利用本地化将时区分配给日期,使用了适当的时区名称和偏移量。但是,直接使用DateTime构造器分配时区不允许进行正确的调整。 example pytz.timezone(...
    编程 发布于2025-03-12
  • 为什么使用Firefox后退按钮时JavaScript执行停止?
    为什么使用Firefox后退按钮时JavaScript执行停止?
    导航历史记录问题:JavaScript使用Firefox Back Back 此行为是由浏览器缓存JavaScript资源引起的。要解决此问题并确保在后续页面访问中执行脚本,Firefox用户应设置一个空功能。 警报'); }; alert('inline Alert')...
    编程 发布于2025-03-12
  • HTML格式标签
    HTML格式标签
    HTML 格式化元素 **HTML Formatting is a process of formatting text for better look and feel. HTML provides us ability to format text without us...
    编程 发布于2025-03-12

免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。

Copyright© 2022 湘ICP备2022001581号-3