designing-sdks
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSDK Design
SDK设计
Design client libraries (SDKs) with excellent developer experience through intuitive APIs, robust error handling, automatic retries, and consistent patterns across programming languages.
通过直观的API、健壮的错误处理、自动重试机制以及跨编程语言的一致模式,设计具备卓越开发者体验的客户端库(SDK)。
When to Use This Skill
适用场景
Use when building a client library for a REST API, creating internal service SDKs, implementing retry logic with exponential backoff, handling authentication patterns, creating typed error hierarchies, implementing pagination with async iterators, or designing streaming APIs for real-time data.
适用于为REST API构建客户端库、创建内部服务SDK、实现带指数退避的重试逻辑、处理认证模式、创建类型化错误层级、通过异步迭代器实现分页,或为实时数据设计流式API等场景。
Core Architecture Patterns
核心架构模式
Client → Resources → Methods
客户端 → 资源 → 方法
Organize SDK code hierarchically:
Client (config: API key, base URL, retries, timeout)
├─ Resources (users, payments, posts)
│ ├─ create(), retrieve(), update(), delete()
│ └─ list() (with pagination)
└─ Top-Level Methods (convenience)Resource-Based (Stripe style):
typescript
const client = new APIClient({ apiKey: 'sk_test_...' })
const user = await client.users.create({ email: 'user@example.com' })Use for APIs <100 methods. Prioritizes developer experience.
Command-Based (AWS SDK v3):
typescript
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'
await client.send(new PutObjectCommand({ Bucket: '...' }))Use for APIs >100 methods. Prioritizes bundle size and tree-shaking.
For detailed architectural guidance, see .
references/architecture-patterns.md按层级组织SDK代码:
Client (config: API key, base URL, retries, timeout)
├─ Resources (users, payments, posts)
│ ├─ create(), retrieve(), update(), delete()
│ └─ list() (with pagination)
└─ Top-Level Methods (convenience)基于资源的模式(Stripe风格):
typescript
const client = new APIClient({ apiKey: 'sk_test_...' })
const user = await client.users.create({ email: 'user@example.com' })适用于方法数少于100个的API,优先保障开发者体验。
基于命令的模式(AWS SDK v3风格):
typescript
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'
await client.send(new PutObjectCommand({ Bucket: '...' }))适用于方法数多于100个的API,优先考虑包体积和摇树优化。
如需详细架构指导,请参阅。
references/architecture-patterns.mdLanguage-Specific Patterns
语言特定模式
TypeScript: Async-Only
TypeScript:仅异步
typescript
const user = await client.users.create({ email: 'user@example.com' })All methods return Promises. Avoid callbacks.
typescript
const user = await client.users.create({ email: 'user@example.com' })所有方法均返回Promises,避免使用回调函数。
Python: Dual Sync/Async
Python:同步/异步双模式
python
undefinedpython
undefinedSync
同步
client = APIClient(api_key='sk_test_...')
user = client.users.create(email='user@example.com')
client = APIClient(api_key='sk_test_...')
user = client.users.create(email='user@example.com')
Async
异步
async_client = AsyncAPIClient(api_key='sk_test_...')
user = await async_client.users.create(email='user@example.com')
Provide both clients. Users choose based on architecture.async_client = AsyncAPIClient(api_key='sk_test_...')
user = await async_client.users.create(email='user@example.com')
同时提供两种客户端,用户可根据架构选择使用。Go: Sync with Context
Go:带Context的同步模式
go
client := apiclient.New("api_key")
user, err := client.Users().Create(ctx, req)Use context.Context for timeout and cancellation.
go
client := apiclient.New("api_key")
user, err := client.Users().Create(ctx, req)使用context.Context实现超时和取消功能。
Authentication
认证机制
API Key (Most Common)
API密钥(最常用)
typescript
const client = new APIClient({ apiKey: process.env.API_KEY })Store keys in environment variables, never hardcode.
typescript
const client = new APIClient({ apiKey: process.env.API_KEY })将密钥存储在环境变量中,切勿硬编码。
OAuth Token Refresh
OAuth令牌刷新
typescript
const client = new APIClient({
clientId: 'id',
clientSecret: 'secret',
refreshToken: 'token',
onTokenRefresh: (newToken) => saveToken(newToken)
})SDK automatically refreshes tokens before expiry.
typescript
const client = new APIClient({
clientId: 'id',
clientSecret: 'secret',
refreshToken: 'token',
onTokenRefresh: (newToken) => saveToken(newToken)
})SDK会在令牌过期前自动刷新。
Bearer Token Per-Request
逐请求Bearer令牌
typescript
await client.users.list({
headers: { Authorization: `Bearer ${userToken}` }
})Use for multi-tenant applications.
See for OAuth flows, JWT handling, and credential providers.
references/authentication.mdtypescript
await client.users.list({
headers: { Authorization: `Bearer ${userToken}` }
})适用于多租户应用。
如需了解OAuth流程、JWT处理和凭证提供器,请参阅。
references/authentication.mdRetry and Backoff
重试与退避
Exponential Backoff with Jitter
带抖动的指数退避
typescript
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
)
}Retry Decision Matrix:
| Error Type | Retry? | Rationale |
|---|---|---|
| 5xx, 429, Network Timeout | ✅ Yes | Transient errors |
| 4xx, 401, 403, 404 | ❌ No | Client errors won't fix themselves |
typescript
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 | ❌ 否 | 客户端错误无法通过重试解决 |
Rate Limit Handling
速率限制处理
typescript
if (error.status === 429) {
const retryAfter = parseInt(error.headers['retry-after'] || '60')
await sleep(retryAfter * 1000)
}Respect header on 429 responses.
Retry-AfterSee for jitter strategies, circuit breakers, and idempotency keys.
references/retry-backoff.mdtypescript
if (error.status === 429) {
const retryAfter = parseInt(error.headers['retry-after'] || '60')
await sleep(retryAfter * 1000)
}在429响应时遵循头信息。
Retry-After如需了解抖动策略、断路器和幂等键,请参阅。
references/retry-backoff.mdError Handling
错误处理
Typed Error Hierarchy
类型化错误层级
typescript
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)
}
}typescript
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)
}
}Error Handling in Practice
实际错误处理示例
typescript
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})`)
}
}Include request ID in all errors for debugging.
See for user-friendly messages, validation errors, and debugging support.
references/error-handling.mdtypescript
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('无效API密钥')
} else if (error instanceof APIError) {
console.error(`${error.message} (请求ID: ${error.requestId})`)
}
}在所有错误中包含请求ID以方便调试。
如需了解用户友好型消息、验证错误和调试支持,请参阅。
references/error-handling.mdPagination
分页
Async Iterators (Recommended)
异步迭代器(推荐)
TypeScript:
typescript
for await (const user of client.users.list({ limit: 100 })) {
console.log(user.id, user.email)
}Python:
python
async for user in client.users.list(limit=100):
print(user.id, user.email)SDK automatically fetches next page.
TypeScript:
typescript
for await (const user of client.users.list({ limit: 100 })) {
console.log(user.id, user.email)
}Python:
python
async for user in client.users.list(limit=100):
print(user.id, user.email)SDK会自动获取下一页数据。
Implementation
实现方式
typescript
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
}
}
}typescript
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
}
}
}Manual Pagination
手动分页
typescript
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
}Provide both automatic and manual options.
See for cursor vs. offset pagination and Go channel patterns.
references/pagination.mdtypescript
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.mdStreaming
流式传输
Server-Sent Events
Server-Sent Events
typescript
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('\n')) {
if (line.startsWith('data: ')) {
const data = line.slice(6)
if (data === '[DONE]') return
yield JSON.parse(data)
}
}
}
}
// Usage
for await (const chunk of client.posts.stream({ prompt: 'Write a story' })) {
process.stdout.write(chunk.content)
}typescript
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('\n')) {
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)
}Idempotency Keys
幂等键
Prevent duplicate operations during retries:
typescript
import { randomUUID } from 'crypto'
if (['POST', 'PATCH', 'PUT'].includes(method)) {
headers['Idempotency-Key'] = options?.idempotencyKey || randomUUID()
}
// Usage
await client.charges.create(
{ amount: 1000 },
{ idempotencyKey: 'charge_unique_123' }
)Server deduplicates requests by key.
在重试期间防止重复操作:
typescript
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' }
)服务器会根据键对请求进行去重。
Versioning
版本控制
Semantic Versioning
语义化版本控制
- →
1.0.0: New features (safe)1.1.0 - →
1.1.0: Breaking changes (review)2.0.0 - →
1.0.0: Bug fixes (safe)1.0.1
- →
1.0.0:新增功能(兼容升级)1.1.0 - →
1.1.0:破坏性变更(需谨慎升级)2.0.0 - →
1.0.0:Bug修复(兼容升级)1.0.1
Deprecation Warnings
弃用警告
typescript
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() }typescript
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(`[已弃用] ${propertyKey} 自 ${since} 起弃用。 ${message}`)
return originalMethod.apply(this, args)
}
return descriptor
}
}
@deprecated('请改用users.list()', 'v2.0.0')
async getAll() { return this.list() }API Version Pinning
API版本固定
typescript
const client = new APIClient({
apiKey: 'sk_test_...',
apiVersion: '2025-01-01'
})See for migration strategies.
references/versioning.mdtypescript
const client = new APIClient({
apiKey: 'sk_test_...',
apiVersion: '2025-01-01'
})如需了解迁移策略,请参阅。
references/versioning.mdConfiguration Best Practices
配置最佳实践
typescript
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
}
}Provide sensible defaults, require only apiKey.
typescript
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。
Quick Reference Tables
快速参考表格
Authentication Patterns
认证模式
| Pattern | Use Case |
|---|---|
| API Key | Service-to-service |
| OAuth Refresh | User-based auth |
| Bearer Per-Request | Multi-tenant |
| 模式 | 适用场景 |
|---|---|
| API密钥 | 服务间通信 |
| OAuth刷新 | 基于用户的认证 |
| 逐请求Bearer令牌 | 多租户应用 |
Retry Strategies
重试策略
| Strategy | Use Case |
|---|---|
| Exponential Backoff | Default retry |
| Rate Limit | 429 responses |
| Max Retries | Avoid infinite loops (3-5) |
| 策略 | 适用场景 |
|---|---|
| 指数退避 | 默认重试方案 |
| 速率限制处理 | 429响应场景 |
| 最大重试次数 | 避免无限循环(3-5次) |
Pagination Options
分页选项
| Pattern | Language | Use Case |
|---|---|---|
| Async Iterator | TypeScript, Python | Automatic pagination |
| Generator | Python | Sync pagination |
| Channels | Go | Concurrent iteration |
| Manual | All | Explicit control |
| 模式 | 语言 | 适用场景 |
|---|---|---|
| 异步迭代器 | TypeScript、Python | 自动分页 |
| 生成器 | Python | 同步分页 |
| 通道 | Go | 并发迭代 |
| 手动分页 | 所有语言 | 显式控制分页 |
Reference Documentation
参考文档
Architecture:
- - Resource vs. command organization
references/architecture-patterns.md
Core Patterns:
- - OAuth, token refresh, credential providers
references/authentication.md - - Exponential backoff, jitter, circuit breakers
references/retry-backoff.md - - Error hierarchies, debugging support
references/error-handling.md - - Cursor vs. offset, async iterators
references/pagination.md - - SemVer, deprecation strategies
references/versioning.md - - Unit testing, mocking, integration tests
references/testing-sdks.md
架构:
- - 资源型与命令型组织方式对比
references/architecture-patterns.md
核心模式:
- - OAuth、令牌刷新、凭证提供器
references/authentication.md - - 指数退避、抖动、断路器
references/retry-backoff.md - - 错误层级、调试支持
references/error-handling.md - - 游标与偏移分页、异步迭代器
references/pagination.md - - 语义化版本、弃用策略
references/versioning.md - - 单元测试、Mock、集成测试
references/testing-sdks.md
Code Examples
代码示例
TypeScript:
- - Simple async SDK
examples/typescript/basic-client.ts - - Retry, errors, streaming
examples/typescript/advanced-client.ts - - Stripe-style organization
examples/typescript/resource-based.ts
Python:
- - Synchronous client
examples/python/sync-client.py - - Async client with asyncio
examples/python/async-client.py - - Both sync and async
examples/python/dual-client.py
Go:
- - Simple Go client
examples/go/basic-client.go - - Context patterns
examples/go/context-client.go - - Channel-based pagination
examples/go/channel-pagination.go
TypeScript:
- - 简单异步SDK
examples/typescript/basic-client.ts - - 包含重试、错误处理、流式传输的高级SDK
examples/typescript/advanced-client.ts - - Stripe风格的资源型组织
examples/typescript/resource-based.ts
Python:
- - 同步客户端
examples/python/sync-client.py - - 基于asyncio的异步客户端
examples/python/async-client.py - - 同步异步双模式客户端
examples/python/dual-client.py
Go:
- - 简单Go客户端
examples/go/basic-client.go - - Context模式示例
examples/go/context-client.go - - 基于通道的分页实现
examples/go/channel-pagination.go
Best-in-Class SDK Examples
业界优秀SDK示例
Study these production SDKs:
TypeScript/JavaScript:
- AWS SDK v3 (): Modular, tree-shakeable, middleware
@aws-sdk/client-* - Stripe Node (): Resource-based, typed errors, excellent DX
stripe - OpenAI Node (): Streaming, async iterators, modern TypeScript
openai
Python:
- Boto3 (): Resource vs. client patterns, paginators
boto3 - Stripe Python (): Dual sync/async, context managers
stripe
Go:
- AWS SDK Go v2 (): Context, middleware
github.com/aws/aws-sdk-go-v2
参考以下生产级SDK:
TypeScript/JavaScript:
- AWS SDK v3 (): 模块化、支持摇树优化、中间件架构
@aws-sdk/client-* - Stripe Node (): 资源型设计、类型化错误、卓越开发者体验
stripe - OpenAI Node (): 流式传输、异步迭代器、现代TypeScript实现
openai
Python:
- Boto3 (): 资源型与客户端型模式、分页器
boto3 - Stripe Python (): 同步异步双模式、上下文管理器
stripe
Go:
- AWS SDK Go v2 (): Context支持、中间件架构
github.com/aws/aws-sdk-go-v2
Common Pitfalls
常见陷阱
Avoid these mistakes:
- No Retry Logic - All SDKs need automatic retries for transient errors
- Poor Error Messages - Include request ID, status code, error type
- No Pagination - Implement automatic pagination with async iterators
- Hardcoded Credentials - Use environment variables or config files
- Missing Idempotency - Add idempotency keys to prevent duplicate operations
- Ignoring Rate Limits - Respect header on 429 responses
Retry-After - Breaking Changes - Use SemVer, deprecate before removing
避免以下错误:
- 无重试逻辑 - 所有SDK都需要针对临时错误的自动重试机制
- 错误信息质量差 - 需包含请求ID、状态码、错误类型
- 未实现分页 - 需通过异步迭代器实现自动分页
- 硬编码凭证 - 使用环境变量或配置文件存储凭证
- 缺少幂等性 - 添加幂等键以防止重复操作
- 忽略速率限制 - 在429响应时遵循头信息
Retry-After - 破坏性变更未处理 - 使用语义化版本控制,先弃用再移除功能
Integration with Other Skills
与其他技能的集成
- api-design-principles: API design complements SDK design (error codes → error classes)
- building-clis: CLIs wrap SDKs for command-line access
- testing-strategies: Test SDKs with mocked HTTP, retry scenarios
- api-design-principles: API设计与SDK设计相辅相成(错误码→错误类)
- building-clis: CLI可基于SDK封装以提供命令行访问能力
- testing-strategies: 使用Mock HTTP、重试场景测试SDK
Next Steps
下一步行动
Review language-specific examples for implementation details. Study references for deep dives on specific patterns. Examine best-in-class SDKs (Stripe, AWS, OpenAI) for inspiration.
查看语言特定示例获取实现细节。阅读参考文档深入了解特定模式。研究业界优秀SDK(Stripe、AWS、OpenAI)获取灵感。