Adobe Reliability Patterns
Overview
Production-grade reliability patterns for Adobe API integrations. Adobe APIs present unique challenges: IMS tokens expire after 24h, Firefly/Photoshop jobs are async with variable completion times, and rate limits vary by API. These patterns address each failure mode.
Prerequisites
- Understanding of circuit breaker pattern
opossum installed for circuit breaker (npm install opossum)
- Queue infrastructure (BullMQ/Redis) for dead letter queue
- Caching layer for fallback data
Instructions
Pattern 1: Circuit Breaker per Adobe API
Different Adobe APIs fail independently — use separate circuit breakers:
import CircuitBreaker from 'opossum';
const imsBreaker = new CircuitBreaker(
async () => {
const res = await fetch('https://ims-na1.adobelogin.com/ims/token/v3', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
client_id: process.env.ADOBE_CLIENT_ID!,
client_secret: process.env.ADOBE_CLIENT_SECRET!,
grant_type: 'client_credentials',
scope: process.env.ADOBE_SCOPES!,
}),
});
if (!res.ok) throw new Error(`IMS ${res.status}`);
return res.json();
},
{
timeout: 10_000,
errorThresholdPercentage: 30,
resetTimeout: 60_000,
volumeThreshold: 3,
}
);
const fireflyBreaker = new CircuitBreaker(
async (fn: () => Promise<any>) => fn(),
{
timeout: 60_000,
errorThresholdPercentage: 50,
resetTimeout: 30_000,
volumeThreshold: 5,
}
);
const pdfBreaker = new CircuitBreaker(
async (fn: () => Promise<any>) => fn(),
{
timeout: 30_000,
errorThresholdPercentage: 40,
resetTimeout: 30_000,
volumeThreshold: 5,
}
);
for (const [name, breaker] of [['ims', imsBreaker], ['firefly', fireflyBreaker], ['pdf', pdfBreaker]] as const) {
breaker.on('open', () => console.warn(`Circuit ${name} OPEN — failing fast`));
breaker.on('halfOpen', () => console.info(`Circuit ${name} HALF-OPEN — testing recovery`));
breaker.on('close', () => console.info(`Circuit ${name} CLOSED — normal`));
}
Pattern 2: Graceful Degradation with Fallback
interface FallbackResult<T> {
data: T;
source: 'live' | 'cached' | 'default';
staleness?: string;
}
async function withAdobeFallback<T>(
liveFn: () => Promise<T>,
cacheKey: string,
defaultValue: T
): Promise<FallbackResult<T>> {
try {
const data = await liveFn();
await cache.set(cacheKey, JSON.stringify(data), 'EX', 3600);
return { data, source: 'live' };
} catch (error: any) {
console.warn(`Adobe API failed (${error.message}), trying fallback`);
}
const cached = await cache.get(cacheKey);
if (cached) {
const ttl = await cache.ttl(cacheKey);
{
: .(cached),
: ,
: ,
};
}
{ : defaultValue, : };
}
result = (
({ : }),
,
{ : [{ : { : } }] }
);
(result. !== ) {
.();
}
Pattern 3: Dead Letter Queue for Failed Jobs
import { Queue, Worker } from 'bullmq';
import { Redis } from 'ioredis';
const redis = new Redis(process.env.REDIS_URL);
const adobeDlq = new Queue('adobe-dlq', { connection: redis });
const adobeQueue = new Queue('adobe-jobs', { connection: redis });
const worker = new Worker('adobe-jobs', async (job) => {
try {
switch (job.data.operation) {
case 'firefly-generate':
return await generateImage(job.data.params);
case 'pdf-extract':
return await extractPdfContent(job.data.params.pdfPath);
case 'photoshop-cutout':
(job..);
:
();
}
} (: ) {
(job. >= ) {
adobeDlq.(, {
: job.,
: error.,
: job.,
: ().(),
});
.();
;
}
error;
}
}, {
: redis,
: ,
: {
: ,
: ,
},
});
Pattern 4: Timeout Hierarchy for Adobe APIs
const ADOBE_TIMEOUTS = {
ims_token: 10_000,
firefly_sync: 30_000,
firefly_async: 5_000,
firefly_poll: 120_000,
pdf_extract: 30_000,
pdf_create: 20_000,
photoshop_submit: 5_000,
photoshop_poll: 120_000,
};
async function timedAdobeCall<T>(
operation: keyof typeof ADOBE_TIMEOUTS,
fn: () => Promise<T>
): Promise<T> {
const timeout = ADOBE_TIMEOUTS[operation];
return Promise.race([
fn(),
new Promise<never>((_, reject) =>
setTimeout(() => reject( ()), timeout)
),
]);
}
Pattern 5: Health Check with Degraded State
type ServiceHealth = 'healthy' | 'degraded' | 'unhealthy';
async function adobeHealthCheck(): Promise<{
status: ServiceHealth;
services: Record<string, any>;
}> {
const checks = {
ims: {
status: imsBreaker.stats().state === 'closed' ? 'healthy' : 'unhealthy',
circuitState: imsBreaker.stats().state,
},
firefly: {
status: fireflyBreaker.stats().state === 'closed' ? 'healthy' :
fireflyBreaker.stats().state === 'halfOpen' ? 'degraded' : 'unhealthy',
circuitState: fireflyBreaker.stats().state,
},
pdf: {
status: pdfBreaker.stats().state === 'closed' ? 'healthy' : 'degraded',
circuitState: pdfBreaker.stats().state,
},
: {
: adobeDlq.(),
: ( adobeDlq.()) > ? : ,
},
};
: =
checks.. === ? :
.(checks).( c. === ) ? :
;
{ : overall, : checks };
}
Output
- Per-API circuit breakers (IMS, Firefly, PDF Services)
- Graceful degradation with cached/default fallback
- Dead letter queue for failed async jobs
- Timeout hierarchy matching Adobe API latency profiles
- Health check with degraded state detection
Error Handling
| Issue | Cause | Solution |
|---|
| IMS circuit stays open | Credentials rotated | Update secret and restart |
| Firefly circuit flapping | Intermittent 500s | Increase resetTimeout |
| DLQ growing | Persistent failures | Investigate root cause; process DLQ |
| Fallback data too stale | Long outage | Increase cache TTL; notify users |
Resources
Next Steps
For policy enforcement, see adobe-policy-guardrails.