技能 编程开发 HubSpot可靠性设计模式

HubSpot可靠性设计模式

v20260423
hubspot-reliability-patterns
本文档详细介绍了如何在生产环境中构建高可靠性的HubSpot CRM集成系统。内容涵盖了熔断器(Circuit Breaker)、重试机制、优雅降级和死信队列(DLQ)等关键设计模式,帮助开发者确保系统在API服务故障或不稳定时依然能够稳定运行,实现高可用性。
获取技能
127 次下载
概览

HubSpot Reliability Patterns

Overview

Production-grade reliability patterns for HubSpot CRM integrations: circuit breaker, retry with Retry-After, graceful degradation, and dead letter queues.

Prerequisites

  • @hubspot/api-client installed (has built-in retry)
  • Optional: opossum for circuit breaker
  • Optional: Redis or database for dead letter queue

Instructions

Step 1: SDK Built-in Retry (First Line of Defense)

import * as hubspot from '@hubspot/api-client';

// The SDK automatically retries 429 and 5xx errors
const client = new hubspot.Client({
  accessToken: process.env.HUBSPOT_ACCESS_TOKEN!,
  numberOfApiCallRetries: 3,  // retries with exponential backoff
});
// This handles most transient failures automatically

Step 2: Circuit Breaker for HubSpot API

import CircuitBreaker from 'opossum';

// Circuit breaker wrapping all HubSpot API calls
const hubspotBreaker = new CircuitBreaker(
  async <T>(operation: () => Promise<T>): Promise<T> => operation(),
  {
    timeout: 15000,                // 15s timeout per call
    errorThresholdPercentage: 50,  // open after 50% failure rate
    resetTimeout: 30000,           // try again after 30s
    volumeThreshold: 5,            // need 5+ calls before evaluating
    rollingCountTimeout: 60000,    // 60s rolling window
  }
);

// Monitor circuit state
hubspotBreaker.on('open', () => {
  console.warn('[HubSpot] Circuit OPEN -- requests failing fast');
  // Alert team: HubSpot integration degraded
});

hubspotBreaker.on('halfOpen', () => {
  console.info('[HubSpot] Circuit HALF-OPEN -- testing recovery');
});

hubspotBreaker.on('close', () => {
  console.info('[HubSpot] Circuit CLOSED -- normal operation');
});

// Usage
async function resilientHubSpotCall<T>(operation: () => Promise<T>): Promise<T> {
  return hubspotBreaker.fire(operation) as Promise<T>;
}

// Example
const contacts = await resilientHubSpotCall(() =>
  client.crm.contacts.basicApi.getPage(10, undefined, ['email'])
);

Step 3: Graceful Degradation

// Serve cached/fallback data when HubSpot is unavailable
import { LRUCache } from 'lru-cache';

const fallbackCache = new LRUCache<string, any>({
  max: 10000,
  ttl: 30 * 60 * 1000, // 30 minutes
});

async function withFallback<T>(
  cacheKey: string,
  operation: () => Promise<T>,
  fallback?: T
): Promise<{ data: T; source: 'live' | 'cache' | 'fallback' }> {
  try {
    const data = await resilientHubSpotCall(operation);
    fallbackCache.set(cacheKey, data);
    return { data, source: 'live' };
  } catch (error) {
    // Try cache first
    const cached = fallbackCache.get(cacheKey);
    if (cached) {
      console.warn(`[HubSpot] Serving cached data for ${cacheKey}`);
      return { data: cached as T, source: 'cache' };
    }

    // Use static fallback if provided
    if (fallback !== undefined) {
      console.warn(`[HubSpot] Serving fallback for ${cacheKey}`);
      return { data: fallback, source: 'fallback' };
    }

    throw error;
  }
}

// Usage
const { data: contacts, source } = await withFallback(
  'recent-contacts',
  () => client.crm.contacts.basicApi.getPage(10, undefined, ['email', 'firstname']),
  { results: [], paging: undefined } // empty fallback
);

if (source !== 'live') {
  console.warn(`Serving ${source} data -- HubSpot may be degraded`);
}

Step 4: Dead Letter Queue

interface FailedOperation {
  id: string;
  operation: string;
  payload: any;
  error: string;
  correlationId?: string;
  attempts: number;
  firstAttempt: string;
  lastAttempt: string;
}

class HubSpotDeadLetterQueue {
  private queue: FailedOperation[] = [];

  add(operation: string, payload: any, error: any): void {
    const entry: FailedOperation = {
      id: `dlq-${Date.now()}-${Math.random().toString(36).slice(2)}`,
      operation,
      payload,
      error: error?.body?.message || error.message,
      correlationId: error?.body?.correlationId,
      attempts: 1,
      firstAttempt: new Date().toISOString(),
      lastAttempt: new Date().toISOString(),
    };
    this.queue.push(entry);
    console.warn(`[DLQ] Enqueued ${operation}: ${entry.error}`);
  }

  async retryAll(): Promise<{ succeeded: number; failed: number }> {
    let succeeded = 0, failed = 0;

    for (const entry of [...this.queue]) {
      try {
        // Retry the operation
        await this.executeOperation(entry);
        this.queue = this.queue.filter(e => e.id !== entry.id);
        succeeded++;
      } catch (error) {
        entry.attempts++;
        entry.lastAttempt = new Date().toISOString();
        failed++;

        if (entry.attempts > 5) {
          console.error(`[DLQ] Giving up on ${entry.id} after ${entry.attempts} attempts`);
          // Move to permanent failure storage
        }
      }
    }

    return { succeeded, failed };
  }

  private async executeOperation(entry: FailedOperation): Promise<void> {
    switch (entry.operation) {
      case 'createContact':
        await client.crm.contacts.basicApi.create({
          properties: entry.payload,
          associations: [],
        });
        break;
      case 'updateContact':
        await client.crm.contacts.basicApi.update(
          entry.payload.id, { properties: entry.payload.properties }
        );
        break;
      // Add more operation types as needed
    }
  }

  getStats(): { pending: number; oldestAge: string } {
    return {
      pending: this.queue.length,
      oldestAge: this.queue.length > 0
        ? this.queue[0].firstAttempt
        : 'none',
    };
  }
}

const dlq = new HubSpotDeadLetterQueue();

// Usage
async function createContactWithDLQ(properties: Record<string, string>) {
  try {
    return await resilientHubSpotCall(() =>
      client.crm.contacts.basicApi.create({ properties, associations: [] })
    );
  } catch (error) {
    dlq.add('createContact', properties, error);
    throw error;
  }
}

// Retry DLQ periodically
setInterval(async () => {
  const stats = dlq.getStats();
  if (stats.pending > 0) {
    console.log(`[DLQ] Retrying ${stats.pending} failed operations...`);
    const result = await dlq.retryAll();
    console.log(`[DLQ] Results: ${result.succeeded} succeeded, ${result.failed} failed`);
  }
}, 5 * 60 * 1000); // every 5 minutes

Step 5: Health Check with Degraded State

type HealthStatus = 'healthy' | 'degraded' | 'unhealthy';

async function hubspotHealthCheck(): Promise<{
  status: HealthStatus;
  circuitState: string;
  dlqPending: number;
  apiLatencyMs: number;
}> {
  const dlqStats = dlq.getStats();
  const start = Date.now();

  try {
    await client.crm.contacts.basicApi.getPage(1);
    const latency = Date.now() - start;

    const status: HealthStatus =
      hubspotBreaker.stats().state === 'open' ? 'degraded' :
      dlqStats.pending > 50 ? 'degraded' :
      latency > 5000 ? 'degraded' :
      'healthy';

    return {
      status,
      circuitState: hubspotBreaker.stats().state,
      dlqPending: dlqStats.pending,
      apiLatencyMs: latency,
    };
  } catch {
    return {
      status: 'unhealthy',
      circuitState: hubspotBreaker.stats().state,
      dlqPending: dlqStats.pending,
      apiLatencyMs: Date.now() - start,
    };
  }
}

Output

  • SDK built-in retry handling transient 429/5xx errors
  • Circuit breaker preventing cascade failures
  • Graceful degradation with cached/fallback data
  • Dead letter queue for failed operations with automatic retry
  • Health check reporting degraded state

Error Handling

Issue Cause Solution
Circuit stays open Threshold too low Increase errorThresholdPercentage
Stale cache served HubSpot down for long time Alert when cache age > TTL
DLQ growing Persistent failures Investigate root cause, not symptoms
Fallback data confusing users No degradation indicator Show "data may be stale" in UI

Resources

Next Steps

For policy enforcement, see hubspot-policy-guardrails.

信息
Category 编程开发
Name hubspot-reliability-patterns
版本 v20260423
大小 8.65KB
更新时间 2026-04-28
语言