@alvinsng: https://x.com/alvinsng/status/2077114275412512868
摘要
Alvin Sng 解释了他们的团队为何放弃使用 Stripe、WorkOS 和 Slack 的客户端 SDK,转而通过集中式包装器直接调用其 REST API。他们认为,SDK 会隐藏关键的调试细节,在生产环境中不稳定,并且容易引发反模式,而借助 AI 辅助编码,这些反模式如今可以更轻松地避免。
查看缓存全文
缓存时间: 2026/07/15 15:55
为什么我们不再使用 SDK
我们已停止使用 Stripe、WorkOS 和 Slack 的客户端 SDK,并正在逐步迁移其余部分。取而代之的是,我们通过一个名为 HttpBaseClient 的小型包装类直接调用它们的 REST API。
这听起来有些倒退。SDK 本应为你节省时间。但你肯定已经听说过 AI 正在将更多代码移植为原生运行。同样的道理也适用于 SDK。为什么?
- SDK 隐藏了原始细节,比如 HTTP 响应头和原始响应体,而这些对于正在调试问题或将详细信息发送给上游团队进行排查的智能体来说至关重要。
- SDK 期望得到格式正确的响应,但实际上来自防火墙、负载均衡器和网关的响应往往是格式错误的。这掩盖了我们需要的调试细节。
- SDK 绕过了集中式入口点所施加的约束(重试、错误处理和可观测性),使得智能体可以重写自己的处理模式。
- SDK 体积臃肿,通常是自动生成的(来自 OpenAPI),而我们只使用其中一小部分端点。
想想 SDK 为何兴起。它们诞生是因为公司希望让集成变得容易:交付一个共享库,节省客户时间,并隐藏重复性的集成工作。但 AI 改变了这个成本曲线。如今,使用 SDK 进行集成的工作量通常与直接调用 HTTP API 相当,而且为精确使用的端点编写一个定制化的 REST 客户端成本低廉,同时还能提供更好的统一可观测性。
无休止的打地鼠游戏
如此“优美“的 JSON 响应
如此“优美“的 JSON 响应
这就是我们 Sentry 错误仪表盘曾经的样子。我们会发现一小部分请求从某个代码路径失败,然后我们猴补丁那个路径来处理 SDK 的失误。第二天,另一个代码路径又会触发意外错误;我们继续修补,这个游戏永无止境。
SDK 在一种常见的生产故障模式下很脆弱:服务器说出了问题,但响应不是 SDK 预期的 JSON 格式。
一个过载的 Nginx 网关返回了意想不到的 HTML。Cloudflare 阻止了一个请求。防火墙弹出一个限速页面。供应商的 API 通常是 JSON,但它前面的东西并不总是供应商 API。
当这种情况发生时,许多 SDK 尝试解析响应,失败后返回一个通用的解析错误,并丢弃有用的信息。原始响应体丢失。状态文本被隐藏。HTTP 头通常无法以可用方式返回。如果供应商支持要求提供来自 HTTP 头的请求 ID,我们就麻烦了,因为 SDK 不返回它。
智能体滥用了 SDK 的直接性
我们的客户端 SDK 调用曾经是“西部荒野“。Stripe 有一种模式。WorkOS 有另一种模式。Slack 有自己的古怪之处。其他集成有的使用原始 fetch,有的使用 SDK 调用,有的有零散的重试逻辑,有的根本没有重试逻辑。
SDK 让错误的事情看起来很轻松。智能体总可以直接从任何路由调用 stripe.customers.create(...)。这看起来高效,但它绕过了本该统一的认证、重试、指标、日志和错误转换的地方。我们的代码库中散落着这样的闭包包装器:
如果你漏了一个地方,忘记包装 SDK 调用,就会遭遇糟糕的一天。Stripe 的速率限制和 WorkOS 的速率限制对我们的产品来说含义相同:上游要求我们放慢速度。但在类型层面,它们是完全不同的对象。有些代码捕获 SDK 特定的异常,有些捕获通用的 Error,有些什么都不捕获。这就变成了 Sentry 打地鼠游戏:在一个调用点修复一个 429,然后在其他地方等待同样的故障类别出现。
SDK 臃肿
NPM 包 openai 和 anthropic-ai/sdk 是由 Stainless 根据 OpenAPI 规范自动生成的。Stripe 也是根据其 OpenAPI 规范生成的。这是跨几十种编程语言为大型 API 维护公共 SDK 的规模化方式。
但一个优秀的公共 SDK 必须服务于所有人。我们的后端不需要每个人的 SDK。它只需要我们使用的八个 Stripe 端点、WorkOS 的用户和组织端点,以及我们实际调用的 Slack 方法。Stripe 大小 6.5 MB,workos-inc/node 6.9 MB,slack/web-api 7.7 MB,linear/sdk 34 MB。极端情况下,googleapis 达到了 198 MB。
生成的 SDK 携带了整个平台:数百个方法、重载、分页助手、重试行为、环境检测、兼容性垫片,以及因为有人依赖而无法删除的旧接口。在我们自己的后端内部,这种通用性通常就是臃肿。更糟糕的是,它横亘在我们与网络层之间。
我们忘记了 HTTP API 就是 API 契约
人们有时谈论 HTTP API 时,仿佛它们是较低级别的实现细节,而 SDK 才是真正的稳定接口。公共 REST API 就是一个契约。 供应商不能随意破坏它。SDK 作者也知道这一点,因为他们不能强制每个客户升级。许多客户在生产环境中运行着多年前的 SDK 版本,这意味着旧的网络契约必须继续工作。
我们自己的 HttpBaseClient
HttpBaseClient 是我们用来替代客户端 SDK 的组件。Provider 子类提供供应商特有的部分:基础 URL、认证头、内容类型、错误映射,以及我们实际使用的端点的窄方法。HttpBaseClient 负责其余部分:序列化、解析、传输错误、结构化日志、指标、状态映射和持续时间跟踪。这统一了可观测性,使每个供应商遵循一致的标准。以下是简化后的结构:
这其中的诀窍在于:该类并不试图建模整个 Stripe。它建模的是我们希望每个供应商调用都具备的 HTTP 行为。Stripe 仍然使用表单编码。WorkOS 仍然使用 Bearer 认证和 JSON 主体。Slack 仍然有它在 HTTP 200 下的奇怪 ok: false 行为。但我们后端的其余部分看到的是统一的结构。
你可以在此处查看 HttpBaseClient 更长的版本,已修改为更通用且更易读的示例代码。
我们仍然使用 SDK 的地方
我们当前的方法是混合式的:运行时使用我们自己的 HTTP 客户端,但当 SDK 的类型仍然能节省时间时,我们会保留它们。StripeHttpClient 可以返回 Stripe.Customer,SlackHttpClient 可以借用 slack/web-api 的参数类型,WorkOS 的类型仍然可以描述网络响应。
我预计随着时间的推移,我们也会逐步淘汰这种用法。随着 AI 在处理我们需要的精确请求和响应类型方面变得更好,为类型而保留整个 SDK 包的理由会越来越弱。但运行时行为是痛苦的来源,所以迁移从那里开始。
当 SDK 本身就是产品边界,而不仅仅是 REST 的包装器时,我们仍然使用 SDK。可观测性是最明显的例子。对于 Sentry,SDK 负责运行时检测、错误捕获、作用域传播、发布元数据以及我们不想自己重新实现的集成。这与将供应商 SDK 作为普通后端 HTTP 调用的瘦客户端不同。
并非所有 API 都是基于 HTTP 的 REST,这没关系。数据库调用就是一个很好的例子。我们的底层抽象是 BaseClient:它为每个客户端提供了相同的指标、日志和错误处理契约,同时允许子类覆盖其传输层的“fetch“含义。
我们未来的方向
开发者会将 API 文档视为真正的集成指南,而将 SDK 视为参考实现:用于认证、负载、分页、重试和边界情况的模板。“交付 SDK“的下一个版本可能是“交付智能体技能”,它教会智能体如何正确调用 API、重用正确的模式,并避免让每个运行时调用都经过供应商包。
相似文章
@rohanpaul_ai:Stripe 正面临一种杰文斯悖论:工程师能在更短时间内开发出更多软件,但待完成的软件项目积压量却似乎在增加…
Stripe 正经历一种杰文斯悖论:AI 让工程师能更快构建软件,但有价值的软件积压工作却持续增长。Will Gaybrick 讨论了 AI 如何改变 Stripe 的产品开发流程。
@katelyn_lesse: https://x.com/katelyn_lesse/status/2073902681668931927
Katelyn Lesse 的一条 Twitter 主题讨论,认为团队应该直面项目中最困难的部分,而不是逐步回避,并以 Stripe v2 账户的故事为例说明。
@sashimikun_void: 又一天目睹基于智能体的Slack初创公司被清理。我听到创始人说,“别担心,他们没时间去…
一位开发者反思AI智能体如何消除Slack初创公司的利基市场,同时ClaudeDevs透露Claude Code现在为他们产品团队编写了65%的代码,包括Claude Tag工具本身。
@garrytan: https://x.com/garrytan/status/2061454423034110372
Garry Tan 认为,开发者在用AI智能体时过度工程化,编写了过多代码;相反,他们应该信任模型,构建基于指令的极简软件,他的开源项目GStack就是例证。
@RhysSullivan: https://x.com/RhysSullivan/status/2070989582850793947
Rhys Sullivan认为,公司应该使其API、技能和知识对用户自己的AI代理开放,而不是强迫每个人都使用应用内代理,这样高级用户可以利用他们偏好的模型和本地上下文,同时仍然为普通用户提供简单界面。