Multi-source routing 2026: маршрутизация заказов между поставщиками
Production B2B-фулфилмент цифровых товаров не зависит от одного поставщика. Эксклюзивный supplier — это single point of failure: один даунтайм, и все ваши заказы fail. Правильный архитектурный паттерн — multi-source routing с автоматическим выбором лучшего варианта на каждый запрос.
1. Latency-weighted routing
Самый простой вариант — выбирать поставщика с минимальной p95 delivery latency. Хранить historical-метрики в Redis sliding window:
type SupplierStats = { p95Ms: number; failureRate: number; stock: number };
async function pickSupplier(productId: string): Promise<string> {
const candidates = await getSuppliersForProduct(productId);
const stats = await Promise.all(
candidates.map(s => redis.hgetall(`sup:${s.id}:stats`))
);
const scored = candidates.map((s, i) => ({
id: s.id,
score: 1 / (parseFloat(stats[i].p95Ms) + 1),
}));
return scored.sort((a, b) => b.score - a.score)[0].id;
}
Этого достаточно для 80% сценариев. Latency-метрики обновляются каждые 30 секунд из реальных тиймингов опроса статуса.
2. Stock-aware routing
Если у поставщика inventory низкий — лучше не использовать его как primary, даже если latency хорошая. Stock buffer защищает от race condition «4 одновременных заказа на 3 кода».
function isViableSupplier(s: SupplierStats, qty: number): boolean {
const buffer = Math.max(50, qty * 3); // 3x safety margin
return s.stock >= buffer && s.failureRate < 0.05;
}
Для FoxReload наличие товара отражается в каталоге — запрашивайте GET /api/products/{id_or_slug} для проверки актуальной доступности.
3. Cost-optimised routing
Если SLA позволяет (например, fulfilment до 5 минут — норма для B2B), оптимизируйте по wholesale-cost. Формула:
score = (1 / wholesale_cost) * sla_multiplier
where sla_multiplier = 1 if p95 < target else 0
Это даёт жёсткий SLA-гарантия + минимальный cost. На больших объёмах (>10k заказов/день) экономия 1.5–3% к marginal cost.
4. Интеграция с FoxReload: создание и опрос заказа
Создание заказа через FoxReload:
async function placeOrderOnFoxReload(
itemId: string,
quantity: number,
note?: Record<string, string>
): Promise<Order> {
const resp = await fetch('https://public-api.foxreload.com/api/orders', {
method: 'POST',
headers: {
'X-API-Key': process.env.FOXRELOAD_KEY!,
'Content-Type': 'application/json',
},
body: JSON.stringify({
items: [{ itemId, quantity, ...(note && { note }) }],
isMock: false,
}),
});
if (!resp.ok) throw new Error(`FoxReload error: ${resp.status}`);
const order = await resp.json();
// FoxReload не поддерживает вебхуки — опрашиваем статус
return pollOrderStatus(order.id);
}
async function pollOrderStatus(orderId: string): Promise<Order> {
for (let i = 0; i < 20; i++) {
await sleep(Math.min(1000 * Math.pow(2, i), 15000));
const r = await fetch(`https://public-api.foxreload.com/api/orders/${orderId}`, {
headers: { 'X-API-Key': process.env.FOXRELOAD_KEY! },
});
const o = await r.json();
if (['completed', 'cancelled', 'failed'].includes(o.status)) return o;
}
throw new Error('poll timeout');
}
5. Failover и circuit breaker
Health-check pattern:
| Сигнал | Threshold | Action |
|---|---|---|
| 5xx rate за 5 мин | >5% | Mark DEGRADED |
| Timeout rate | >2% | Mark DEGRADED |
| Heartbeat fail | 3 × подряд | Mark DOWN |
| Recovery | 5 × успешных | Mark HEALTHY |
Circuit breaker pattern (Polly/resilience4j-style):
import CircuitBreaker from 'opossum';
const breaker = new CircuitBreaker(placeOrderOnFoxReload, {
timeout: 8000,
errorThresholdPercentage: 50,
resetTimeout: 120000, // 2 min
});
breaker.fallback(() => fallbackSupplier());
При open-state 100% traffic уходит на fallback — 2 минуты cooldown, потом half-open canary check, потом recovery.
CTA
FoxReload предоставляет доступ к широкому ассортименту цифровых товаров через единый API. Получите доступ и переведите фулфилмент на надёжную основу.
