# API Integration Patterns für Automation **Meta-Description:** Robuste API Integrationen. Retry Logic, Rate Limiting, Webhooks und Error Handling für zuverlässige Automatisierung. **Keywords:** API Integration, Retry Logic, Rate Limiting, Webhooks, Error Handling, REST API, HTTP Client --- ## Einführung Zuverlässige API-Integrationen sind das Fundament jeder Automation. **Retry Logic, Rate Limiting, Circuit Breaker** – diese Patterns machen den Unterschied zwischen fragilen und robusten Systemen. --- ## API Client Architecture ``` ┌─────────────────────────────────────────────────────────────┐ │ ROBUST API CLIENT │ ├─────────────────────────────────────────────────────────────┤ │ │ │ Request Flow: │ │ │ │ Application │ │ ↓ │ │ Rate Limiter (Throttle Requests) │ │ ↓ │ │ Retry Handler (Exponential Backoff) │ │ ↓ │ │ Circuit Breaker (Fail Fast) │ │ ↓ │ │ HTTP Client (Axios/Fetch) │ │ ↓ │ │ Response Handler (Parse/Validate) │ │ ↓ │ │ Application │ │ │ │ Cross-Cutting Concerns: │ │ ├── Logging │ │ ├── Metrics │ │ └── Caching │ │ │ └─────────────────────────────────────────────────────────────┘ ``` --- ## Base API Client ```typescript import axios, { AxiosInstance, AxiosRequestConfig, AxiosError } from 'axios'; interface ApiClientConfig { baseURL: string; timeout?: number; headers?: Record; retries?: number; retryDelay?: number; rateLimit?: { maxRequests: number; windowMs: number; }; } class ApiClient { private client: AxiosInstance; private config: Required; private requestTimestamps: number[] = []; constructor(config: ApiClientConfig) { this.config = { timeout: 30000, headers: {}, retries: 3, retryDelay: 1000, rateLimit: { maxRequests: 100, windowMs: 60000 }, ...config }; this.client = axios.create({ baseURL: this.config.baseURL, timeout: this.config.timeout, headers: this.config.headers }); this.setupInterceptors(); } private setupInterceptors() { // Request Interceptor this.client.interceptors.request.use( async (config) => { await this.waitForRateLimit(); this.recordRequest(); console.log(`[API] ${config.method?.toUpperCase()} ${config.url}`); return config; }, (error) => Promise.reject(error) ); // Response Interceptor this.client.interceptors.response.use( (response) => { console.log(`[API] Response: ${response.status}`); return response; }, async (error: AxiosError) => { const config = error.config as AxiosRequestConfig & { _retryCount?: number }; if (!config || !this.shouldRetry(error, config._retryCount || 0)) { return Promise.reject(this.formatError(error)); } config._retryCount = (config._retryCount || 0) + 1; const delay = this.calculateDelay(config._retryCount); console.log(`[API] Retry ${config._retryCount}/${this.config.retries} after ${delay}ms`); await this.sleep(delay); return this.client.request(config); } ); } private shouldRetry(error: AxiosError, retryCount: number): boolean { if (retryCount >= this.config.retries) return false; // Retry bei Netzwerkfehlern if (!error.response) return true; // Retry bei bestimmten Status Codes const retryableStatuses = [408, 429, 500, 502, 503, 504]; return retryableStatuses.includes(error.response.status); } private calculateDelay(retryCount: number): number { // Exponential Backoff mit Jitter const baseDelay = this.config.retryDelay; const exponentialDelay = baseDelay * Math.pow(2, retryCount - 1); const jitter = Math.random() * 1000; return exponentialDelay + jitter; } private async waitForRateLimit(): Promise { const now = Date.now(); const windowStart = now - this.config.rateLimit.windowMs; // Alte Timestamps entfernen this.requestTimestamps = this.requestTimestamps.filter(t => t > windowStart); if (this.requestTimestamps.length >= this.config.rateLimit.maxRequests) { const oldestRequest = this.requestTimestamps[0]; const waitTime = oldestRequest + this.config.rateLimit.windowMs - now; if (waitTime > 0) { console.log(`[API] Rate limit reached, waiting ${waitTime}ms`); await this.sleep(waitTime); } } } private recordRequest() { this.requestTimestamps.push(Date.now()); } private formatError(error: AxiosError): Error { if (error.response) { return new Error( `API Error: ${error.response.status} - ${JSON.stringify(error.response.data)}` ); } if (error.request) { return new Error(`Network Error: ${error.message}`); } return error; } private sleep(ms: number): Promise { return new Promise(resolve => setTimeout(resolve, ms)); } // Public Methods async get(url: string, config?: AxiosRequestConfig): Promise { const response = await this.client.get(url, config); return response.data; } async post(url: string, data?: any, config?: AxiosRequestConfig): Promise { const response = await this.client.post(url, data, config); return response.data; } async put(url: string, data?: any, config?: AxiosRequestConfig): Promise { const response = await this.client.put(url, data, config); return response.data; } async delete(url: string, config?: AxiosRequestConfig): Promise { const response = await this.client.delete(url, config); return response.data; } } ``` --- ## Circuit Breaker Pattern ```typescript enum CircuitState { CLOSED = 'CLOSED', // Normal Operation OPEN = 'OPEN', // Failing, block requests HALF_OPEN = 'HALF_OPEN' // Testing recovery } interface CircuitBreakerConfig { failureThreshold: number; successThreshold: number; timeout: number; } class CircuitBreaker { private state: CircuitState = CircuitState.CLOSED; private failureCount = 0; private successCount = 0; private lastFailureTime?: number; private config: CircuitBreakerConfig; constructor(config: Partial = {}) { this.config = { failureThreshold: 5, successThreshold: 2, timeout: 30000, ...config }; } async execute(fn: () => Promise): Promise { if (this.state === CircuitState.OPEN) { if (this.shouldAttemptReset()) { this.state = CircuitState.HALF_OPEN; } else { throw new Error('Circuit breaker is OPEN'); } } try { const result = await fn(); this.onSuccess(); return result; } catch (error) { this.onFailure(); throw error; } } private shouldAttemptReset(): boolean { return ( this.lastFailureTime !== undefined && Date.now() - this.lastFailureTime >= this.config.timeout ); } private onSuccess() { if (this.state === CircuitState.HALF_OPEN) { this.successCount++; if (this.successCount >= this.config.successThreshold) { this.reset(); } } this.failureCount = 0; } private onFailure() { this.failureCount++; this.lastFailureTime = Date.now(); if (this.state === CircuitState.HALF_OPEN) { this.state = CircuitState.OPEN; this.successCount = 0; } else if (this.failureCount >= this.config.failureThreshold) { this.state = CircuitState.OPEN; } } private reset() { this.state = CircuitState.CLOSED; this.failureCount = 0; this.successCount = 0; this.lastFailureTime = undefined; } getState(): CircuitState { return this.state; } } // Verwendung const circuitBreaker = new CircuitBreaker({ failureThreshold: 3 }); async function callExternalApi() { return circuitBreaker.execute(async () => { return apiClient.get('/data'); }); } ``` --- ## Webhook Handler ```typescript import { createHmac, timingSafeEqual } from 'crypto'; interface WebhookConfig { secret: string; tolerance: number; // Sekunden } class WebhookHandler { private config: WebhookConfig; private handlers: Map Promise> = new Map(); constructor(config: WebhookConfig) { this.config = config; } register(event: string, handler: (data: any) => Promise) { this.handlers.set(event, handler); } async handle( payload: string, signature: string, timestamp: string ): Promise { // 1. Signature validieren if (!this.verifySignature(payload, signature, timestamp)) { throw new Error('Invalid webhook signature'); } // 2. Timestamp validieren (Replay Attack Prevention) if (!this.verifyTimestamp(timestamp)) { throw new Error('Webhook timestamp too old'); } // 3. Event verarbeiten const event = JSON.parse(payload); const handler = this.handlers.get(event.type); if (handler) { await handler(event.data); } else { console.warn(`No handler for event type: ${event.type}`); } } private verifySignature( payload: string, signature: string, timestamp: string ): boolean { const signedPayload = `${timestamp}.${payload}`; const expectedSignature = createHmac('sha256', this.config.secret) .update(signedPayload) .digest('hex'); const sigBuffer = Buffer.from(signature); const expectedBuffer = Buffer.from(`sha256=${expectedSignature}`); return sigBuffer.length === expectedBuffer.length && timingSafeEqual(sigBuffer, expectedBuffer); } private verifyTimestamp(timestamp: string): boolean { const webhookTime = parseInt(timestamp, 10) * 1000; const now = Date.now(); const tolerance = this.config.tolerance * 1000; return Math.abs(now - webhookTime) <= tolerance; } } // Express Route import express from 'express'; const app = express(); const webhookHandler = new WebhookHandler({ secret: process.env.WEBHOOK_SECRET!, tolerance: 300 // 5 Minuten }); // Webhook Events registrieren webhookHandler.register('order.created', async (data) => { await processNewOrder(data); }); webhookHandler.register('payment.completed', async (data) => { await completePayment(data); }); // Webhook Endpoint app.post('/webhooks', express.raw({ type: 'application/json' }), async (req, res) => { const signature = req.headers['x-webhook-signature'] as string; const timestamp = req.headers['x-webhook-timestamp'] as string; try { await webhookHandler.handle( req.body.toString(), signature, timestamp ); res.status(200).send('OK'); } catch (error) { console.error('Webhook error:', error); res.status(400).send('Invalid webhook'); } } ); ``` --- ## Pagination Handler ```typescript interface PaginatedResponse { data: T[]; pagination: { page: number; pageSize: number; total: number; hasMore: boolean; }; } async function* paginatedFetch( apiClient: ApiClient, endpoint: string, pageSize: number = 100 ): AsyncGenerator { let page = 1; let hasMore = true; while (hasMore) { const response = await apiClient.get>( `${endpoint}?page=${page}&pageSize=${pageSize}` ); yield response.data; hasMore = response.pagination.hasMore; page++; } } // Cursor-based Pagination async function* cursorPaginatedFetch( apiClient: ApiClient, endpoint: string, limit: number = 100 ): AsyncGenerator { let cursor: string | null = null; while (true) { const url = cursor ? `${endpoint}?cursor=${cursor}&limit=${limit}` : `${endpoint}?limit=${limit}`; const response = await apiClient.get<{ data: T[]; nextCursor: string | null; }>(url); yield response.data; if (!response.nextCursor) break; cursor = response.nextCursor; } } // Verwendung async function fetchAllProducts() { const allProducts: Product[] = []; for await (const products of paginatedFetch(apiClient, '/products')) { allProducts.push(...products); console.log(`Fetched ${allProducts.length} products so far`); } return allProducts; } ``` --- ## Response Caching ```typescript interface CacheEntry { data: T; expiresAt: number; etag?: string; } class ResponseCache { private cache: Map> = new Map(); private defaultTTL: number; constructor(defaultTTL: number = 60000) { this.defaultTTL = defaultTTL; } set(key: string, data: T, ttl?: number, etag?: string) { this.cache.set(key, { data, expiresAt: Date.now() + (ttl || this.defaultTTL), etag }); } get(key: string): CacheEntry | null { const entry = this.cache.get(key); if (!entry) return null; if (Date.now() > entry.expiresAt) { this.cache.delete(key); return null; } return entry; } invalidate(pattern: string | RegExp) { for (const key of this.cache.keys()) { if (typeof pattern === 'string' ? key.includes(pattern) : pattern.test(key)) { this.cache.delete(key); } } } } // Mit ETag Support async function fetchWithCache( apiClient: ApiClient, url: string, cache: ResponseCache ): Promise { const cacheKey = url; const cached = cache.get(cacheKey); const headers: Record = {}; if (cached?.etag) { headers['If-None-Match'] = cached.etag; } try { const response = await apiClient.get(url, { headers }); const etag = (response as any).headers?.etag; cache.set(cacheKey, response, undefined, etag); return response; } catch (error: any) { if (error.response?.status === 304 && cached) { return cached.data; } throw error; } } ``` --- ## Fazit Robuste API Integrationen brauchen: 1. **Retry Logic**: Exponential Backoff mit Jitter 2. **Rate Limiting**: Client-seitige Throttling 3. **Circuit Breaker**: Fail Fast bei Ausfällen 4. **Caching**: Reduziert Last und Latenz Production-ready von Anfang an. --- ## Bildprompts 1. "API calls with retry arrows showing resilience, error recovery" 2. "Circuit breaker switch between services, fail fast concept" 3. "Rate limiter queue with request tickets, throttling visualization" --- ## Quellen - [Axios Documentation](https://axios-http.com/) - [Circuit Breaker Pattern](https://martinfowler.com/bliki/CircuitBreaker.html) - [API Rate Limiting Best Practices](https://cloud.google.com/architecture/rate-limiting-strategies) - [Webhook Security](https://webhooks.fyi/security/hmac)