
Интеграционная граница должна поглощать различия провайдеров
Provider feed — внешний язык. Внутренний продукт не должен зависеть от того, как поставщик именует status, period, competitor или market. На ingress сохраняется raw envelope, затем данные переводятся в каноническую модель, а неизвестные значения не замалчиваются.
| Вопрос команды | Что фиксируем | Что считаем ошибкой |
|---|---|---|
| Что видит пользователь? | Явные состояния и переходы | Необъяснимый скачок или «вечный loading» |
| Кто источник истины? | Контракт клиента, API и внешнего источника | Два слоя считают себя финальными |
| Как деградируем? | Fallback, stale policy, retry и stop condition | Скрытый partial сбой |
идентификатор провайдера не является продуктовым идентификатор
При нескольких источниках нужен внутренний entity id и отдельный mapping registry. Сопоставление может использовать участников, время, турнир и другие признаки, но пограничные случаи должны иметь confidence и путь ручной проверки. Автоматическая «уверенность 100%» опаснее честного unresolved.
Событие в реальном времени лучше моделировать переходами
Счёт, фаза и timer связаны. Обновление независимых полей создаёт невозможные комбинации. State machine задаёт разрешённые переходы и приоритеты источников, а позднее сообщение не должно откатывать событие из final обратно в live без явной reconciliation-процедуры.
Разветвление потока отделяет скорость приёма данных от клиентской частоты
Провайдер может присылать несколько изменений в секунду, но клиенту нужен последовательно версионированный state. Snapshot + delta, backpressure и granular subscription позволяют не превращать каждый provider tick в отдельный API request или UI commit.
Деградация должна быть сегментной
Провайдер редко падает «целиком». Можно потерять конкретный спорт, competition или тип market. Health-модель по сегментам ограничивает blast radius: один раздел suspend, другой остаётся live. Stale policy должна быть явной и отличать информационные данные от интерактивных действий.
Минимальная форма канонического контракта
Каноническая модель полезна только когда переносит не просто поля, но и семантику актуальности. Для live-сущности обычно нужны внутренний id, version или monotonic sequence, occurred/received timestamps, status, source diagnostics и признак качества. Эти поля позволяют downstream-слоям принять решение, а не гадать по времени последнего HTTP-ответа.
| Слой | Что хранит | Чего не должен знать |
|---|---|---|
| Raw envelope | Оригинальное сообщение, provider id, receive time | UX-правила продукта |
| Normalizer | Канонические сущности, mapping diagnostics | React/Vue component state |
| Product API | Версию, freshness, разрешённые product states | Специфичные названия поля поставщика |
| UI | Отображаемое состояние и пользовательский intent | Какой provider прислал исходный status code |
Если новый поставщик заставляет менять десятки UI-компонентов, anti-corruption граница ответственности фактически отсутствует, даже если формально есть сервис «normalizer».
Досье интеграции для каждого внешнего источника
Для provider полезно вести компактное dossier: coverage, update cadence, semantic exceptions, идентификаторы, ограничения replay и известные gaps. Этот документ живёт рядом с contract tests и mapping registry, а не только в onboarding-презентации.
- Fixture set: реальные формы raw messages для ключевых статусов и пограничных значений.
- Mapping table: provider term → canonical term → unresolved policy.
- Freshness rule: когда данные ещё отображаются, когда помечаются stale, когда интерактивный слой suspend.
- Reconciliation path: кто и как исправляет конфликт двух источников.
Так смена поставщика становится контролируемой миграцией контракта, а не серией неожиданных изменений во frontend.
Контракт стоит проверять на «плохих» сообщениях, а не только на эталонном потоке
Хороший integration test включает повтор, запоздавшее сообщение, неизвестный status, пропущенный sequence и событие, которое один поставщик уже завершил, а второй ещё считает live. На таких данных становится понятно, где заканчивается парсинг и начинается продуктовая семантика.
Сохраняйте исходное сообщение рядом с нормализованной сущностью хотя бы на диагностическом пути. Это позволяет объяснить расхождение без попыток восстановить историю по логам. Практическая точка входа — EventNormalizer, а типовые маршруты можно сверить в provider ingest flow.
Что стоит согласовать до первого обратного вызова
Интеграция почти никогда не падает в тот момент, когда провайдер отдаёт первый тестовый payload. Проблемы приходят позже: при смене фазы матча, коррекции счёта, частичном suspend рынков или конфликте идентификаторов. Поэтому ещё до разработки команды полезно провести короткий contract review и зафиксировать, где заканчивается нормализация и начинается продуктовая модель. Хорошая отправная точка — разбор нормализации событий от разных поставщиков.
После этого уже можно честно проверять fan-out и SLA для пользовательского экрана. Если команда не знает, какие сообщения считаются обязательными, а какие можно буферизовать, она быстро приходит к либо перегруженному API, либо к stale-состоянию на front-end. Именно поэтому мы почти всегда связываем руководство по интеграции с практическим разбором пути live-коэффициента от фида до интерфейса.
Поддерживающие материалы
Ниже — подробные разборы отдельных решений: от состояния интерфейса и границ компонента до интеграции и поведения при отказе.