Help us improve
Share bugs, ideas, or general feedback.
From dex-skill-dotnet-resilience
Resilience для HTTP клиентов — Polly, retry, timeout, circuit breaker. Активируется при resilience, polly, retry, circuit breaker, timeout, idempotency, flaky, HttpClient, Refit, StandardResilienceHandler, AddResilience
How this skill is triggered — by the user, by Claude, or both
Slash command
/dex-skill-dotnet-resilience:dotnet-resilienceThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Плохо: retry / circuit breaker навешены на промежуточный вторичный клиент (`OrdersApiClient`), при этом реальный источник нестабильности — VCS / auth / upstream-сервис на уровень ниже
Share bugs, ideas, or general feedback.
Плохо: retry / circuit breaker навешены на промежуточный вторичный клиент (OrdersApiClient), при этом реальный источник нестабильности — VCS / auth / upstream-сервис на уровень ниже
Правильно: resilience policy ставится на границе с нестабильной системой — на том клиенте, который реально падает; листовой получает чистый контракт
Почему: если листовой клиент ретраит вместо корневого, каждый внешний сбой превращается в каскад ретраев выше по стеку (N × M запросов). Ретрай на листовом скрывает root cause и маскирует метрики корневого сервиса. Правило: retry-policy ставится на том HTTP-клиенте, который первым видит транспортный сбой
Плохо: retry в HttpClient + retry в handler'е вокруг него + retry в pipeline MediatR
Правильно: одна точка retry на маршруте «наш-процесс → внешняя система»
Почему: вложенные ретраи умножаются (3 × 3 × 3 = 27 попыток). Внешняя система получает бомбу запросов, latency взрывается, circuit breaker верхнего слоя никогда не срабатывает. Retry — один уровень, ближайший к транспорту
Плохо: retry на POST /orders без заголовка Idempotency-Key (или бизнес-ключа в теле)
Правильно: retry только на идемпотентных операциях (GET, PUT, DELETE с идемпотентными семантиками); для POST — либо Idempotency-Key, либо отказ от retry
Почему: повторный POST создаёт дубликат. При сетевой ошибке мы не знаем, дошёл запрос или нет — без Idempotency-Key ретрай = двойное списание / двойная запись
Плохо: policy ретраит любой non-2xx, включая 400 Bad Request и 403 Forbidden
Правильно: retry только на transient errors — 5xx (кроме 501), 408 Request Timeout, 429 Too Many Requests (с учётом Retry-After), и network-level exceptions (socket / timeout)
Почему: 4xx — это контракт «запрос не пройдёт, сколько ни повторяй». Ретраим 400 / 401 / 403 — бьём внешний сервис без шанса на успех, жжём quota, в auth-случаях блокируем аккаунт
Плохо: фиксированный RetryDelay = TimeSpan.FromSeconds(1)
Правильно: exponential backoff с jitter (DelayBackoffType = Exponential, UseJitter = true)
Почему: одновременный всплеск у 1000 клиентов → все ретраят через ровно 1 секунду → thundering herd валит сервис повторно. Jitter размазывает ретраи во времени и даёт сервису шанс восстановиться
Плохо: AttemptTimeout = 10s, retry 3 раза → суммарно запрос может жить 30+s и задержит вызывающий поток
Правильно: per-attempt + total. Per-attempt — защита от зависания одной попытки; total — защита от накопления latency через retry
Почему: per-attempt не ограничивает общее время. Под retry policy запрос может идти минуты, ASP.NET request будет жить пока не дойдёт до request-timeout. Total-timeout — явная граница пользовательского SLA
Плохо: внутренний HTTP client с Timeout = 60s вызывается из ASP.NET-контроллера с дефолтным request-timeout 30s
Правильно: внутренний timeout строго меньше timeout'а вызывающего, с запасом на serialize / deserialize
Почему: если inner-timeout > outer-timeout, клиент (или ASP.NET) обрывает соединение раньше, чем наш код узнает о сбое. Потеря управления над failure path, запрос повисает в InProgress без финализации
Плохо: retry policy есть, circuit breaker — нет; при падении внешней системы все pods её бомбят в retry-цикле
Правильно: добавить circuit breaker с MinimumThroughput, FailureRatio, BreakDuration
Почему: retry без circuit breaker = DoS на восстанавливающийся сервис. Breaker быстро отказывает при серии сбоев, даёт внешней системе время на recovery и возвращает быстрый ответ пользователю вместо длинного timeout
Плохо: catch (Exception ex) { /* retry / ignore */ } — скрывает OperationCanceledException, OutOfMemoryException, bugs
Правильно: типизированные предикаты в policy — HandleResult<HttpResponseMessage> + Handle<HttpRequestException> + Handle<TimeoutRejectedException>
Почему: catch (Exception) превращает resilience-слой в швабру — ловит отмену, критические ошибки процесса, NullReferenceException из бага. Retry начинает маскировать реальные баги
Плохо: policy настроена, но телеметрия отсутствует — нельзя увидеть, сколько ретраев идёт в рантайме
Правильно: подписка на ResilienceEvents / OpenTelemetry-интеграция: счётчики retry_attempts, circuit_breaker_state, timeout_rejections
Почему: без метрик resilience — чёрный ящик. Нельзя калибровать лимиты, нельзя поймать момент, когда retry стал маскировать системную проблему (рост retry rate без роста ошибок в downstream — сигнал о выгорании SLO)
Плохо: FallbackAction = _ => ValueTask.FromResult(new Response()) — возвращаем пустышку без лога и без метрики
Правильно: fallback возвращает осмысленную деградацию (кэш / default) + логирует / инкрементирует метрику «fallback triggered»
Почему: молчаливый fallback скрывает проблему от оператора. Пользователь видит «всё ок», система видит «всё ок», а реально внешний сервис лежит неделю. Fallback — это видимая деградация, не костыль «чтобы не падало»
Exceptionnpx claudepluginhub dex-it/claude-code-marketplace --plugin dex-skill-dotnet-resilienceProvides UI/UX resources: 50+ styles, color palettes, font pairings, guidelines, charts for web/mobile across React, Next.js, Vue, Svelte, Tailwind, React Native, Flutter. Aids planning, building, reviewing interfaces.
Fetches up-to-date documentation from Context7 for libraries and frameworks like React, Next.js, Prisma. Use for setup questions, API references, and code examples.