名称: 设计SDKs 描述: 设计具备重试逻辑、错误处理、分页和多语言支持的生产就绪SDK。用于构建API的客户端库或创建面向开发者的SDK接口。
SDK设计
通过直观的API、健壮的错误处理、自动重试和跨编程语言的一致模式,设计具有卓越开发者体验的客户端库(SDKs)。
何时使用此技能
使用此技能当构建REST API的客户端库、创建内部服务SDK、实现具有指数退避的重试逻辑、处理认证模式、创建类型化错误层次结构、实现带有异步迭代器的分页,或设计用于实时数据的流式API时。
核心架构模式
客户端 → 资源 → 方法
按层次组织SDK代码:
客户端(配置:API密钥、基础URL、重试次数、超时)
├─ 资源(用户、支付、帖子)
│ ├─ create()、retrieve()、update()、delete()
│ └─ list()(带分页)
└─ 顶层方法(便利性)
基于资源(Stripe风格):
const client = new APIClient({ apiKey: 'sk_test_...' })
const user = await client.users.create({ email: 'user@example.com' })
用于少于100个方法的API。优先考虑开发者体验。
基于命令(AWS SDK v3):
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'
await client.send(new PutObjectCommand({ Bucket: '...' }))
用于多于100个方法的API。优先考虑包大小和树摇优化。
详细架构指南,请见references/architecture-patterns.md。
语言特定模式
TypeScript:仅异步
const user = await client.users.create({ email: 'user@example.com' })
所有方法返回Promise。避免回调。
Python:同步/异步双模式
# 同步
client = APIClient(api_key='sk_test_...')
user = client.users.create(email='user@example.com')
# 异步
async_client = AsyncAPIClient(api_key='sk_test_...')
user = await async_client.users.create(email='user@example.com')
提供两种客户端。用户根据架构选择。
Go:带上下文的同步
client := apiclient.New("api_key")
user, err := client.Users().Create(ctx, req)
使用context.Context处理超时和取消。
认证
API密钥(最常见)
const client = new APIClient({ apiKey: process.env.API_KEY })
将密钥存储在环境变量中,永不硬编码。
OAuth令牌刷新
const client = new APIClient({
clientId: 'id',
clientSecret: 'secret',
refreshToken: 'token',
onTokenRefresh: (newToken) => saveToken(newToken)
})
SDK在到期前自动刷新令牌。
每个请求的Bearer令牌
await client.users.list({
headers: { Authorization: `Bearer ${userToken}` }
})
用于多租户应用。
OAuth流程、JWT处理和凭证提供者,请见references/authentication.md。
重试和退避
带抖动的指数退避
async function retryWithBackoff<T>(fn: () => Promise<T>, maxRetries: number): Promise<T> {
let attempt = 0
while (attempt <= maxRetries) {
try {
return await fn()
} catch (error) {
attempt++
if (attempt > maxRetries || !isRetryable(error)) throw error
const exponential = Math.min(1000 * Math.pow(2, attempt - 1), 10000)
const jitter = Math.random() * 500
await sleep(exponential + jitter)
}
}
}
function isRetryable(error: any): boolean {
return (
error.code === 'ECONNRESET' ||
error.code === 'ETIMEDOUT' ||
(error.status >= 500 && error.status < 600) ||
error.status === 429
)
}
重试决策矩阵:
| 错误类型 | 重试? | 原理 |
|---|---|---|
| 5xx、429、网络超时 | ✅ 是 | 暂时性错误 |
| 4xx、401、403、404 | ❌ 否 | 客户端错误不会自行修复 |
速率限制处理
if (error.status === 429) {
const retryAfter = parseInt(error.headers['retry-after'] || '60')
await sleep(retryAfter * 1000)
}
尊重429响应的Retry-After头。
抖动策略、断路器、幂等键,请见references/retry-backoff.md。
错误处理
类型化错误层次结构
class APIError extends Error {
constructor(
message: string,
public status: number,
public code: string,
public requestId: string
) {
super(message)
this.name = 'APIError'
}
}
class RateLimitError extends APIError {
constructor(message: string, requestId: string, public retryAfter: number) {
super(message, 429, 'rate_limit_error', requestId)
}
}
class AuthenticationError extends APIError {
constructor(message: string, requestId: string) {
super(message, 401, 'authentication_error', requestId)
}
}
实践中的错误处理
try {
const user = await client.users.create({ email: 'invalid' })
} catch (error) {
if (error instanceof RateLimitError) {
await sleep(error.retryAfter * 1000)
} else if (error instanceof AuthenticationError) {
console.error('Invalid API key')
} else if (error instanceof APIError) {
console.error(`${error.message} (Request ID: ${error.requestId})`)
}
}
在所有错误中包含请求ID以便调试。
用户友好消息、验证错误和调试支持,请见references/error-handling.md。
分页
异步迭代器(推荐)
TypeScript:
for await (const user of client.users.list({ limit: 100 })) {
console.log(user.id, user.email)
}
Python:
async for user in client.users.list(limit=100):
print(user.id, user.email)
SDK自动获取下一页。
实现
class UsersResource {
async *list(options?: { limit?: number }): AsyncGenerator<User> {
let cursor: string | undefined = undefined
while (true) {
const response = await this.client.request('GET', '/users', {
query: { limit: String(options?.limit || 100), ...(cursor ? { cursor } : {}) }
})
for (const user of response.data) yield user
if (!response.has_more) break
cursor = response.next_cursor
}
}
}
手动分页
let cursor: string | undefined = undefined
while (true) {
const response = await client.users.list({ limit: 100, cursor })
for (const user of response.data) console.log(user.id)
if (!response.has_more) break
cursor = response.next_cursor
}
提供自动和手动选项。
游标与偏移分页和Go通道模式,请见references/pagination.md。
流式处理
服务器发送事件
async *stream(path: string, body?: any): AsyncGenerator<any> {
const response = await fetch(url, {
headers: { 'Accept': 'text/event-stream' },
body: JSON.stringify(body)
})
const reader = response.body!.getReader()
const decoder = new TextDecoder()
while (true) {
const { done, value } = await reader.read()
if (done) break
const chunk = decoder.decode(value)
for (const line of chunk.split('
')) {
if (line.startsWith('data: ')) {
const data = line.slice(6)
if (data === '[DONE]') return
yield JSON.parse(data)
}
}
}
}
// 用法
for await (const chunk of client.posts.stream({ prompt: 'Write a story' })) {
process.stdout.write(chunk.content)
}
幂等键
防止重试期间的重复操作:
import { randomUUID } from 'crypto'
if (['POST', 'PATCH', 'PUT'].includes(method)) {
headers['Idempotency-Key'] = options?.idempotencyKey || randomUUID()
}
// 用法
await client.charges.create(
{ amount: 1000 },
{ idempotencyKey: 'charge_unique_123' }
)
服务器通过键去重请求。
版本控制
语义版本控制
1.0.0→1.1.0:新功能(安全)1.1.0→2.0.0:破坏性更改(需审查)1.0.0→1.0.1:错误修复(安全)
弃用警告
function deprecated(message: string, since: string) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value
descriptor.value = function (...args: any[]) {
console.warn(`[DEPRECATED] ${propertyKey} since ${since}. ${message}`)
return originalMethod.apply(this, args)
}
return descriptor
}
}
@deprecated('Use users.list() instead', 'v2.0.0')
async getAll() { return this.list() }
API版本固定
const client = new APIClient({
apiKey: 'sk_test_...',
apiVersion: '2025-01-01'
})
迁移策略,请见references/versioning.md。
配置最佳实践
interface ClientConfig {
apiKey: string
baseURL?: string
maxRetries?: number
timeout?: number
apiVersion?: string
onTokenRefresh?: (token: string) => void
}
class APIClient {
constructor(config: ClientConfig) {
this.apiKey = config.apiKey
this.baseURL = config.baseURL || 'https://api.example.com'
this.maxRetries = config.maxRetries ?? 3
this.timeout = config.timeout ?? 30000
}
}
提供合理的默认值,仅要求apiKey。
快速参考表
认证模式
| 模式 | 使用案例 |
|---|---|
| API密钥 | 服务到服务 |
| OAuth刷新 | 基于用户的认证 |
| 每个请求的Bearer令牌 | 多租户 |
重试策略
| 策略 | 使用案例 |
|---|---|
| 指数退避 | 默认重试 |
| 速率限制 | 429响应 |
| 最大重试次数 | 避免无限循环(3-5次) |
分页选项
| 模式 | 语言 | 使用案例 |
|---|---|---|
| 异步迭代器 | TypeScript、Python | 自动分页 |
| 生成器 | Python | 同步分页 |
| 通道 | Go | 并发迭代 |
| 手动 | 所有 | 显式控制 |
参考文档
架构:
references/architecture-patterns.md- 资源与命令组织
核心模式:
references/authentication.md- OAuth、令牌刷新、凭证提供者references/retry-backoff.md- 指数退避、抖动、断路器references/error-handling.md- 错误层次结构、调试支持references/pagination.md- 游标与偏移、异步迭代器references/versioning.md- SemVer、弃用策略references/testing-sdks.md- 单元测试、模拟、集成测试
代码示例
TypeScript:
examples/typescript/basic-client.ts- 简单异步SDKexamples/typescript/advanced-client.ts- 重试、错误、流式examples/typescript/resource-based.ts- Stripe风格组织
Python:
examples/python/sync-client.py- 同步客户端examples/python/async-client.py- 异步客户端与asyncioexamples/python/dual-client.py- 同步和异步
Go:
examples/go/basic-client.go- 简单Go客户端examples/go/context-client.go- 上下文模式examples/go/channel-pagination.go- 基于通道的分页
最佳实践SDK示例
研究这些生产SDK:
TypeScript/JavaScript:
- AWS SDK v3(
@aws-sdk/client-*):模块化、可树摇、中间件 - Stripe Node(
stripe):基于资源、类型化错误、优秀开发者体验 - OpenAI Node(
openai):流式、异步迭代器、现代TypeScript
Python:
- Boto3(
boto3):资源与客户端模式、分页器 - Stripe Python(
stripe):同步/异步双模式、上下文管理器
Go:
- AWS SDK Go v2(
github.com/aws/aws-sdk-go-v2):上下文、中间件
常见陷阱
避免这些错误:
- 无重试逻辑 - 所有SDK都需要自动重试处理暂时性错误
- 错误消息不佳 - 包含请求ID、状态码、错误类型
- 无分页 - 实现带异步迭代器的自动分页
- 硬编码凭据 - 使用环境变量或配置文件
- 缺少幂等性 - 添加幂等键防止重复操作
- 忽略速率限制 - 尊重429响应的
Retry-After头 - 破坏性更改 - 使用SemVer、弃用前移除
与其他技能的集成
- api-design-principles:API设计与SDK设计互补(错误码 → 错误类)
- building-clis:CLI包装SDK用于命令行访问
- testing-strategies:用模拟HTTP、重试场景测试SDK
下一步
查看语言特定示例以了解实现细节。研究参考文档以深入了解特定模式。检查最佳实践SDK(Stripe、AWS、OpenAI)以获取灵感。