1 min read
KI-EntwicklungAPI Integration Patterns für Automation
Robuste API Integrationen. Retry Logic, Rate Limiting, Webhooks und Error Handling für zuverlässige Automatisierung.
API IntegrationRetry LogicRate LimitingWebhooksError HandlingREST API

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
import axios, { AxiosInstance, AxiosRequestConfig, AxiosError } from 'axios';
interface ApiClientConfig {
baseURL: string;
timeout?: number;
headers?: Record<string, string>;
retries?: number;
retryDelay?: number;
rateLimit?: {
maxRequests: number;
windowMs: number;
};
}
class ApiClient {
private client: AxiosInstance;
private config: Required<ApiClientConfig>;
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<void> {
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<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Public Methods
async get<T>(url: string, config?: AxiosRequestConfig): Promise<T> {
const response = await this.client.get<T>(url, config);
return response.data;
}
async post<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
const response = await this.client.post<T>(url, data, config);
return response.data;
}
async put<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
const response = await this.client.put<T>(url, data, config);
return response.data;
}
async delete<T>(url: string, config?: AxiosRequestConfig): Promise<T> {
const response = await this.client.delete<T>(url, config);
return response.data;
}
}Circuit Breaker Pattern
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<CircuitBreakerConfig> = {}) {
this.config = {
failureThreshold: 5,
successThreshold: 2,
timeout: 30000,
...config
};
}
async execute<T>(fn: () => Promise<T>): Promise<T> {
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
import { createHmac, timingSafeEqual } from 'crypto';
interface WebhookConfig {
secret: string;
tolerance: number; // Sekunden
}
class WebhookHandler {
private config: WebhookConfig;
private handlers: Map<string, (data: any) => Promise<void>> = new Map();
constructor(config: WebhookConfig) {
this.config = config;
}
register(event: string, handler: (data: any) => Promise<void>) {
this.handlers.set(event, handler);
}
async handle(
payload: string,
signature: string,
timestamp: string
): Promise<void> {
// 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
interface PaginatedResponse<T> {
data: T[];
pagination: {
page: number;
pageSize: number;
total: number;
hasMore: boolean;
};
}
async function* paginatedFetch<T>(
apiClient: ApiClient,
endpoint: string,
pageSize: number = 100
): AsyncGenerator<T[], void, unknown> {
let page = 1;
let hasMore = true;
while (hasMore) {
const response = await apiClient.get<PaginatedResponse<T>>(
`${endpoint}?page=${page}&pageSize=${pageSize}`
);
yield response.data;
hasMore = response.pagination.hasMore;
page++;
}
}
// Cursor-based Pagination
async function* cursorPaginatedFetch<T>(
apiClient: ApiClient,
endpoint: string,
limit: number = 100
): AsyncGenerator<T[], void, unknown> {
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<Product>(apiClient, '/products')) {
allProducts.push(...products);
console.log(`Fetched ${allProducts.length} products so far`);
}
return allProducts;
}Response Caching
interface CacheEntry<T> {
data: T;
expiresAt: number;
etag?: string;
}
class ResponseCache {
private cache: Map<string, CacheEntry<any>> = new Map();
private defaultTTL: number;
constructor(defaultTTL: number = 60000) {
this.defaultTTL = defaultTTL;
}
set<T>(key: string, data: T, ttl?: number, etag?: string) {
this.cache.set(key, {
data,
expiresAt: Date.now() + (ttl || this.defaultTTL),
etag
});
}
get<T>(key: string): CacheEntry<T> | 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<T>(
apiClient: ApiClient,
url: string,
cache: ResponseCache
): Promise<T> {
const cacheKey = url;
const cached = cache.get<T>(cacheKey);
const headers: Record<string, string> = {};
if (cached?.etag) {
headers['If-None-Match'] = cached.etag;
}
try {
const response = await apiClient.get<T>(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:
- Retry Logic: Exponential Backoff mit Jitter
- Rate Limiting: Client-seitige Throttling
- Circuit Breaker: Fail Fast bei Ausfällen
- Caching: Reduziert Last und Latenz
Production-ready von Anfang an.
Bildprompts
- "API calls with retry arrows showing resilience, error recovery"
- "Circuit breaker switch between services, fail fast concept"
- "Rate limiter queue with request tickets, throttling visualization"