shopify-reliability-patterns
Implement Shopify reliability patterns including circuit breakers, idempotency, and graceful degradation. Use when building fault-tolerant Shopify integrations, implementing retry strategies, or adding resilience to production Shopify services. Trigger with phrases like "shopify reliability", "shopify circuit breaker", "shopify idempotent", "shopify resilience", "shopify fallback", "shopify bulkhead".
Allowed Tools
Provided by Plugin
shopify-pack
Claude Code skill pack for Shopify (30 skills)
Installation
This skill is included in the shopify-pack plugin:
/plugin install shopify-pack@claude-code-plugins-plus
Click to copy
Instructions
Shopify Reliability Patterns
Overview
Production-grade reliability patterns for Shopify integrations.
Prerequisites
- Understanding of circuit breaker pattern
- opossum or similar library installed
- Queue infrastructure for DLQ
- Caching layer for fallbacks
Circuit Breaker
import CircuitBreaker from 'opossum';
const shopifyBreaker = new CircuitBreaker(
async (operation: () => Promise<any>) => operation(),
{
timeout: 30000,
errorThresholdPercentage: 50,
resetTimeout: 30000,
volumeThreshold: 10,
}
);
// Events
shopifyBreaker.on('open', () => {
console.warn('Shopify circuit OPEN - requests failing fast');
alertOps('Shopify circuit breaker opened');
});
shopifyBreaker.on('halfOpen', () => {
console.info('Shopify circuit HALF-OPEN - testing recovery');
});
shopifyBreaker.on('close', () => {
console.info('Shopify circuit CLOSED - normal operation');
});
// Usage
async function safeShopifyCall<T>(fn: () => Promise<T>): Promise<T> {
return shopifyBreaker.fire(fn);
}
Idempotency Keys
import { v4 as uuidv4 } from 'uuid';
import crypto from 'crypto';
// Generate deterministic idempotency key from input
function generateIdempotencyKey(
operation: string,
params: Record<string, any>
): string {
const data = JSON.stringify({ operation, params });
return crypto.createHash('sha256').update(data).digest('hex');
}
// Or use random key with storage
class IdempotencyManager {
private store: Map<string, { key: string; expiresAt: Date }> = new Map();
getOrCreate(operationId: string): string {
const existing = this.store.get(operationId);
if (existing && existing.expiresAt > new Date()) {
return existing.key;
}
const key = uuidv4();
this.store.set(operationId, {
key,
expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000),
});
return key;
}
}
Bulkhead Pattern
import PQueue from 'p-queue';
// Separate queues for different operations
const shopifyQueues = {
critical: new PQueue({ concurrency: 10 }),
normal: new PQueue({ concurrency: 5 }),
bulk: new PQueue({ concurrency: 2 }),
};
async function prioritizedShopifyCall<T>(
priority: 'critical' | 'normal' | 'bulk',
fn: () => Promise<T>
): Promise<T> {
return shopifyQueues[priority].add(fn);
}
// Usage
await prioritizedShopifyCall('critical', () =>
shopifyClient.processPayment(order)
);
await prioritizedShopifyCall('bulk', () =>
shopifyClient.syncCatalog(products)
);
Timeout Hierarchy
const TIMEOUT_CONFIG = {
connect: 5000, // Initial connection
request: 30000, // Standard requests
upload: 120000, // File uploads
longPoll: 300000, // Webhook long-polling
};
async function timedoutShopifyCall<T>(
operation: 'connect' | 'request' | 'upload' | 'longPoll',
fn: () => Promise<T>
): Promise<T> {
const timeout = TIMEOUT_CONFIG[operation];
return Promise.race([
fn(),
new Promise<never>((_, reject) =>
setTimeout(() => reject(new Error(`Shopify ${operation} timeout`)), timeout)
),
]);
}
Graceful Degradation
interface ShopifyFallback {
enabled: boolean;
data: any;
staleness: 'fresh' | 'stale' | 'very_stale';
}
async function withShopifyFallback<T>(
fn: () => Promise<T>,
fallbackFn: () => Promise<T>
): Promise<{ data: T; fallback: boolean }> {
try {
const data = await fn();
// Update cache for future fallback
await updateFallbackCache(data);
return { data, fallback: false };
} catch (error) {
console.warn('Shopify failed, using fallback:', error.message);
const data = await fallbackFn();
return { data, fallback: true };
}
}
Dead Letter Queue
interface DeadLetterEntry {
id: string;
operation: string;
payload: any;
error: string;
attempts: number;
lastAttempt: Date;
}
class ShopifyDeadLetterQueue {
private queue: DeadLetterEntry[] = [];
add(entry: Omit<DeadLetterEntry, 'id' | 'lastAttempt'>): void {
this.queue.push({
...entry,
id: uuidv4(),
lastAttempt: new Date(),
});
}
async processOne(): Promise<boolean> {
const entry = this.queue.shift();
if (!entry) return false;
try {
await shopifyClient[entry.operation](entry.payload);
console.log(`DLQ: Successfully reprocessed ${entry.id}`);
return true;
} catch (error) {
entry.attempts++;
entry.lastAttempt = new Date();
if (entry.attempts < 5) {
this.queue.push(entry);
} else {
console.error(`DLQ: Giving up on ${entry.id} after 5 attempts`);
await alertOnPermanentFailure(entry);
}
return false;
}
}
}
Health Check with Degraded State
type HealthStatus = 'healthy' | 'degraded' | 'unhealthy';
async function shopifyHealthCheck(): Promise<{
status: HealthStatus;
details: Record<string, any>;
}> {
const checks = {
api: await checkApiConnectivity(),
circuitBreaker: shopifyBreaker.stats(),
dlqSize: deadLetterQueue.size(),
};
const status: HealthStatus =
!checks.api.connected ? 'unhealthy' :
checks.circuitBreaker.state === 'open' ? 'degraded' :
checks.dlqSize > 100 ? 'degraded' :
'healthy';
return { status, details: checks };
}
Instructions
Step 1: Implement Circuit Breaker
Wrap Shopify calls with circuit breaker.
Step 2: Add Idempotency Keys
Generate deterministic keys for operations.
Step 3: Configure Bulkheads
Separate queues for different priorities.
Step 4: Set Up Dead Letter Queue
Handle permanent failures gracefully.
Output
- Circuit breaker protecting Shopify calls
- Idempotency preventing duplicates
- Bulkhead isolation implemented
- DLQ for failed operations
Error Handling
| Issue | Cause | Solution |
|---|---|---|
| Circuit stays open | Threshold too low | Adjust error percentage |
| Duplicate operations | Missing idempotency | Add idempotency key |
| Queue full | Rate too high | Increase concurrency |
| DLQ growing | Persistent failures | Investigate root cause |
Examples
Quick Circuit Check
const state = shopifyBreaker.stats().state;
console.log('Shopify circuit:', state);
Resources
Next Steps
For policy enforcement, see shopify-policy-guardrails.