designing-sdks について
クライアントライブラリを開発するエンジニア向けの設計ガイドです。API構成、認証、エラー処理、バージョン管理を扱い、TypeScript、Python、Goのパターンと例を掲載しています。
主な機能
- 01リソース型・コマンド型の構成
- 02言語別クライアントパターン
- 03認証とトークン更新
- 04リトライ、バックオフ、エラー処理
- 05自動・手動ページネーション
- 06ストリーミングAPIとバージョン管理
活用シーン
- 01REST API用クライアントライブラリの設計
- 02一時的なエラーの再試行とレート制限対応
- 03ページネーション、認証、ストリーミングの実装
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ ancoleman/ ai-design-components --skill designing-sdksインストール後、Claude Code で/designing-sdksを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
outputs.yamlexamples/go/basic-client.gochannel-pagination.gocontext-client.goexamples/python/async-client.pydual-client.pysync-client.pyexamples/typescript/advanced-client.tsbasic-client.tsresource-based.tsreferences/architecture-patterns.mdauthentication.mderror-handling.mdpagination.mdretry-backoff.mdtesting-sdks.mdversioning.md詳細
SDK の設計
直感的な API、堅牢なエラー処理、自動再試行、プログラミング言語間で一貫したパターンを通じて、優れた開発者体験を実現するクライアントライブラリ(SDK)を設計します。
このスキルを使用する場面
REST API のクライアントライブラリを構築する場合、内部サービスの SDK を作成する場合、指数バックオフによる再試行ロジックを実装する場合、認証パターンを処理する場合、型付きエラー階層を作成する場合、非同期イテレーターによるページネーションを実装する場合、またはリアルタイムデータ向けのストリーミング API を設計する場合に使用します。
中核となるアーキテクチャパターン
クライアント → リソース → メソッド
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 スタイル):
const client = new APIClient({ apiKey: 'sk_test_...' })
const user = await client.users.create({ email: '[email protected]' })
メソッド数が 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: '[email protected]' })
すべてのメソッドは Promise を返します。コールバックは避けてください。
Python:同期/非同期の両方
# Sync
client = APIClient(api_key='sk_test_...')
user = client.users.create(email='[email protected]')
# Async
async_client = AsyncAPIClient(api_key='sk_test_...')
user = await async_client.users.create(email='[email protected]')
両方のクライアントを提供します。ユーザーはアーキテクチャに応じて選択できます。
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 は有効期限が切れる前にトークンを自動更新します。
リクエストごとのベアラートークン
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('\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)
}
冪等性キー
再試行時の操作の重複を防ぎます。
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' }
)
サーバーはキーを使ってリクエストの重複を排除します。
バージョン管理
セマンティックバージョニング
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 更新 | ユーザーベースの認証 |
| リクエストごとのベアラー | マルチテナント |
再試行戦略
| 戦略 | 用途 |
|---|---|
| 指数バックオフ | デフォルトの再試行 |
| レート制限 | 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- asyncio を使用した非同期クライアントexamples/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):リソースベース、型付きエラー、優れた DX - 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)を参考にしてください。