resilience-engineering

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Resilience Engineering for Shopify Apps

Shopify应用的韧性工程

Shopify's API limit is a "Leaky Bucket". If you pour too much too fast, it overflows (429 Too Many Requests). Your app must handle this gracefully.
Shopify的API限制采用的是「漏桶算法」。如果请求速度过快、量过大,桶就会溢出,返回429 Too Many Requests错误。你的应用必须优雅地处理这种情况。

1. Handling Rate Limits (429)

1. 处理速率限制(429)

The "Retry-After" Header

"Retry-After"响应头

When Shopify returns a 429, they include a
Retry-After
header (seconds to wait).
Implementation (using
bottleneck
or custom delay)
:
typescript
async function fetchWithRetry(url, options, retries = 3) {
  try {
    const res = await fetch(url, options);
    if (res.status === 429) {
      const wait = parseFloat(res.headers.get("Retry-After") || "1.0");
      if (retries > 0) {
        await new Promise(r => setTimeout(r, wait * 1000));
        return fetchWithRetry(url, options, retries - 1);
      }
    }
    return res;
  } catch (err) {
    // network error handling
  }
}
Note: The official
@shopify/shopify-api
client handles retries automatically if configured.
当Shopify返回429错误时,响应会附带
Retry-After
头,值为需要等待的秒数。
实现方案(使用
bottleneck
或自定义延迟)
:
typescript
async function fetchWithRetry(url, options, retries = 3) {
  try {
    const res = await fetch(url, options);
    if (res.status === 429) {
      const wait = parseFloat(res.headers.get("Retry-After") || "1.0");
      if (retries > 0) {
        await new Promise(r => setTimeout(r, wait * 1000));
        return fetchWithRetry(url, options, retries - 1);
      }
    }
    return res;
  } catch (err) {
    // network error handling
  }
}
注意:官方的
@shopify/shopify-api
客户端如果完成相关配置,会自动处理重试逻辑。

2. Queues & Throttling

2. 队列与限流

For bulk operations (e.g., syncing 10,000 products), you cannot just loop and await.
对于批量操作(比如同步10000个商品),不能简单地循环执行await请求。

Using
bottleneck

使用
bottleneck

bash
npm install bottleneck
typescript
import Bottleneck from "bottleneck";

const limiter = new Bottleneck({
  minTime: 500, // wait 500ms between requests (2 req/sec)
  maxConcurrent: 5,
});

const products = await limiter.schedule(() => shopify.rest.Product.list({ ... }));
bash
npm install bottleneck
typescript
import Bottleneck from "bottleneck";

const limiter = new Bottleneck({
  minTime: 500, // wait 500ms between requests (2 req/sec)
  maxConcurrent: 5,
});

const products = await limiter.schedule(() => shopify.rest.Product.list({ ... }));

Background Jobs (BullMQ)

后台任务(BullMQ)

Move heavy lifting to a background worker. (See
redis-bullmq
skill - to be added if needed, but conceptually here).
将 heavy 任务转移到后台worker执行。(可参考
redis-bullmq
技能——如有需要后续会添加,此处先介绍概念)。

3. Circuit Breaker

3. 熔断机制

If an external service (e.g., your own backend API or a shipping carrier) goes down, stop calling it to prevent cascading failures.
如果外部服务(比如你自己的后端API或者物流商服务)宕机,要停止调用该服务,避免发生级联故障。

Using
cockatiel

使用
cockatiel

bash
npm install cockatiel
typescript
import { CircuitBreaker, handleAll, retry } from 'cockatiel';

// Create a Retry Policy
const retryPolicy = retry(handleAll, { maxAttempts: 3, backoff: new ExponentialBackoff() });

// Create a Circuit Breaker (open after 5 failures, reset after 10s)
const circuitBreaker = new CircuitBreaker(handleAll, {
  halfOpenAfter: 10 * 1000,
  breaker: new ConsecutiveBreaker(5),
});

// Execute
const result = await retryPolicy.execute(() => 
  circuitBreaker.execute(() => fetchMyService())
);
bash
npm install cockatiel
typescript
import { CircuitBreaker, handleAll, retry } from 'cockatiel';

// Create a Retry Policy
const retryPolicy = retry(handleAll, { maxAttempts: 3, backoff: new ExponentialBackoff() });

// Create a Circuit Breaker (open after 5 failures, reset after 10s)
const circuitBreaker = new CircuitBreaker(handleAll, {
  halfOpenAfter: 10 * 1000,
  breaker: new ConsecutiveBreaker(5),
});

// Execute
const result = await retryPolicy.execute(() => 
  circuitBreaker.execute(() => fetchMyService())
);

4. Webhook Idempotency

4. Webhook幂等性

Shopify guarantees "at least once" delivery. You might receive the same
orders/create
webhook twice. Fix: Store
X-Shopify-Webhook-Id
in Redis/DB with a short TTL (e.g., 24h). If it exists, ignore the request.
Shopify保证Webhook「至少投递一次」,你可能会两次收到同一个
orders/create
webhook请求。 解决方案: 将
X-Shopify-Webhook-Id
存储在Redis/数据库中,设置较短的TTL(比如24小时)。如果请求携带的ID已经存在,直接忽略该请求。