ADR-0068: Бизнес-параметры передаём явно, не через context.Context

Status: accepted Date: 2026-07-08 Deciders: Maxim Belkanov

Контекст

context.Context в ingestion используется не только по назначению. Через app.WithSchedule в ctx кладётся доменная структура IngestionSchedule (job kind, целевой набор SKU, бюджет тика, credential scope, customer/credential ref), а три разных потребителя достают её из ctx и МЕНЯЮТ ПОВЕДЕНИЕ по ней:

  • bulk_snapshot_pipeline.go (planFromSchedule) — какую ось тянуть, каким бюджетом, какой срез;
  • ingestion/di.go:393 (credential provider) — выбрать customer-scoped кред вместо system-fallback;
  • ingestion/di.go:589 (rotator factory) — schedule-aware пул-ротатор.

Это скрытая («серая») передача параметров: значимый доменный вход не виден в сигнатурах (RunTick(ctx, supplierRef), ListActive(ctx, supplier)), а протаскивается неявно. Причина — так не пришлось менять сигнатуры интерфейсов/портов на всю глубину вызова. Цена — код нельзя прочитать по сигнатурам, тесты вынуждены собирать ctx через WithSchedule, а поведение зависит от невидимого значения.

Нюанс. WithSchedule делает две разные вещи: (1) прячет расписание ради ПОВЕДЕНИЯ — анти-паттерн; (2) ставит platformerrors.JobScope (schedule_id/job_kind/customer_ref…) для лог-корреляции — это ЛЕГИТИМНОЕ, кросс-срезовое использование ctx. Запрет касается только (1).

Решение

  1. Доменные/бизнес-параметры передаются ЯВНО — аргументом функции или типизированным request-структом (напр. «что тянуть в этот тик»), а не через ctx.Value.
  2. В context.Context допустимо только: отмена/дедлайн; кросс-срезовые данные наблюдаемости и трассировки (correlation/JobScope для логов), не влияющие на выбор ветви логики; request-scoped идентификаторы, которые НЕ являются доменным входом.
  3. Правило проверки: если по значению из ctx код выбирает, ЧТО и КАК делать (ось, бюджет, срез, scope креда) — это бизнес-параметр, его место в сигнатуре.
  4. Новый код обязан передавать такие параметры явно. Расширять WithSchedule/ ScheduleFromContext-паттерн (новые доменные поля в ctx, новые читатели) — нельзя.

Последствия

  • Существующий долг (размотать): IngestionSchedule в ctx (WithSchedule в scheduler_service.go:198; читатели — pipeline :533, credential provider di.go:393, rotator factory di.go:589; + тесты и internal/tests/russvet_contour используют WithSchedule за неимением явного пути). Целевая размотка: провести явный tick-request (schedule/job kind/бюджет/target/scope) через PipelineRouter.RunTick → конвейеры → RunOnce, и через порты CredentialProvider/RotatorFactory; убрать behavioural-чтение из ctx, оставив только JobScope для логов. Прод-критично (от scope зависит выбор customer-кредов) — делать отдельным аккуратным проходом с тестами, не «наскоком».
  • Пока путь не размотан, contour-тест сознательно использует WithSchedule для commerce-тика — это помечено как долг, а не образец.

Связано

  • ADR-0066 (не кодировать условия строками), ADR-0067 (читаемость оркестраторов) — тот же принцип: логика выражается явными сигнатурами/поведением, а не спрятанным состоянием.
  • ADR-0030 (композиция/инъекция в DI-корне): зависимости и их параметры — явные.