Анализ интеграций и API: контракты, форматы, сценарии обмена, ошибки
Требования почти никогда не теряются внутри системы. Их теряют на границе — там, где наш
сервис зовёт чужой, где приходит файл от склада, где партнёр присылает вебхук. Внутри одной
команды недосказанность лечится репликой в чате. На границе недосказанность становится инцидентом:
деньги списались дважды, заявка «повисла» в статусе, ночная выгрузка положила партнёра, а поле
amount пришло в рублях, хотя все три месяца обсуждали копейки.
Это статья про работу аналитика в точке стыка. Не про то, как выбрать между REST и gRPC — этот выбор делает архитектор, и подробно он разобран в стилях API. Наша тема другая: как превратить «мы интегрируемся с платёжкой» в описание, по которому можно писать код, писать тесты и спорить с партнёром, ссылаясь на документ, а не на память.
Предыдущие статьи трека дали инструменты: BPMN показал потоки сообщений между пулами, UML — sequence-диаграммы взаимодействия, моделирование данных — сущности и словарь, документирование — форму спецификации. Здесь всё это собирается в один артефакт и проверяется на прочность.
1. Почему интеграции — работа аналитика
Распространённое возражение: «контракт API пишут разработчики». Пишут — синтаксис. Но контракт состоит не только из синтаксиса, и всё остальное в нём — предметное:
- Что означает «возврат оформлен» с точки зрения бухгалтерии — деньги ушли или заявка принята?
- Какой срок ожидания приемлем для клиента в кабинете, а какой уже требует показать «в обработке»?
- Что делать, если склад подтвердил приёмку, а платёжка отказала: заказ закрыт или открыт?
- Какие поля обязательны юридически, а какие «желательно бы»?
Разработчик не знает ответов, а партнёр не обязан их угадывать. Аналитик — единственная роль, которая одновременно понимает бизнес-смысл операции и способна прочитать схему сообщения. Пять задач, которые на границе делает именно он:
- Инвентаризация: кто с кем обменивается, чем, как часто и кто владеет данными.
- Семантика: что значит каждое поле и каждый вызов на языке предметной области.
- Сценарии: не только happy path, а полный набор ветвей, включая «результат неизвестен».
- Контракт на ошибки: какой отказ что означает и что видит человек.
- Проверяемость: формулировки, которые можно проверить на приёмке, а не «должно работать надёжно».
Чего аналитик не решает: транспорт, формат сериализации, схему БД партнёра, стратегию масштабирования. Но он обязан задать по каждому из этих пунктов вопрос и записать ответ. Неспрошенное превращается в допущение, а допущение — в дефект.
2. Карта интеграций: с чего начинается анализ
Первый артефакт — не спецификация эндпоинта, а карта. Пока не видно всего ландшафта, любой контракт пишется в вакууме.
владелец: заявка"] OMS["OMS
владелец: заказ"] end subgraph Ext["Внешние системы"] PSP["Платёжный провайдер
владелец: транзакция"] WMS["Склад 3PL
владелец: приёмка"] CRM["CRM SaaS
владелец: обращение"] DWH["Хранилище
только чтение"] end UI -->|"REST, синхронно, 200 rps"| RS RS -->|"REST + Idempotency-Key
синхронно, 5 rps"| PSP PSP -.->|"webhook refund.*
асинхронно, at-least-once"| RS RS -->|"событие return.accepted
брокер, асинхронно"| OMS WMS -->|"CSV по SFTP
раз в час, окно 10 минут"| RS CRM -->|"чтение статуса
polling 1 раз в минуту"| RS RS -->|"CDC-поток"| DWH
На карте сразу видно то, что в тексте незаметно: у нас три разных способа узнать судьбу возврата (вебхук, поллинг из CRM, часовой CSV со склада), и они рассинхронизированы по времени. Это будущий баг «в кабинете возврат готов, а в CRM ещё нет» — и он найден до разработки.
2.1. Паспорт интеграции
Под каждую стрелку на карте заводится одна табличка — минимальный переиспользуемый шаблон:
| Поле паспорта | Пример значения |
|---|---|
| Имя и код | INT-04 «Возврат средств в PSP» |
| Стороны и роли | consumer: сервис возвратов; provider: PSP |
| Владелец данных | транзакция — PSP; заявка — мы |
| Направление и режим | исходящий вызов, синхронный запрос + асинхронное уведомление |
| Стиль и транспорт | HTTPS/REST, JSON; вебхук с подписью HMAC |
| Триггер | оператор подтвердил возврат либо автоправило |
| Объёмы | 3 000 вызовов в сутки, пик 5 rps, разовая догрузка до 20 000 |
| Тайминги | ответ ≤ 3 с (p99), финальный статус ≤ 24 ч |
| Идентификаторы | наш return_id, их psp_refund_id, ключ идемпотентности |
| Ошибки | каталог ERR-PSP-01…09, см. раздел 6 |
| Среды и доступ | test/stage/prod, mTLS, ключи в Vault, whitelist IP |
| Версия и владелец контракта | PSP API v3, изменения — за 90 дней, контакт: их интеграционный менеджер |
| Что при недоступности | заявка в статусе «ожидает», повтор по расписанию, ручной разбор через 24 ч |
Паспорт занимает половину страницы и снимает 80 % будущих вопросов. Его ведут не «для галочки»: именно он превращается в раздел спецификации и в чек-лист приёмки (приёмка).
2.2. Правило одного владельца
Самая дорогая ошибка на карте — два владельца одной сущности. Если статус возврата может менять и наш сервис, и оператор в CRM, вы гарантированно получите расхождение и неразрешимый спор «чей статус правильный». Правило: у каждого атрибута ровно один источник истины, все остальные — реплики, и это фиксируется в паспорте. Из репликации сразу вырастают вопросы про допустимое отставание — это уже нефункциональные требования.
3. Контракт — это обещание, а не yaml-файл
Файл openapi.yaml — форма записи. Контракт — набор обещаний, которые стороны дают друг другу.
Обещаний ровно восемь слоёв, и пропуск любого стоит денег.
Слои полезно проходить именно в этом порядке: адрес и доступ (слои 1–2) блокируют разработку организационно, синтаксис и семантика (3–4) — содержательно, а слои 5–8 определяют, сколько вы будете страдать в проде.
Отдельно про асимметрию сторон. Кто диктует контракт, зависит от расстановки сил:
| Ситуация | Кто определяет контракт | Что делает аналитик |
|---|---|---|
| Внешний вендор, платформа-гигант | provider, менять нельзя | читает их доку, ищет ограничения, проектирует обходы |
| Партнёр сопоставимого размера | договариваются | ведёт согласование, фиксирует протокол встреч |
| Наш внутренний сервис | consumer сильно влияет | пишет требования к API как обычные требования |
| Мы — provider для многих | мы, но с обязательствами | защищает стабильность контракта от «удобных» правок |
Практический вывод: до чтения документации партнёра нельзя обещать бизнесу срок. Сначала выясняется, что вообще возможно в чужом API, потом обсуждается процесс. Обратный порядок — классический источник «мы пообещали синхронный возврат, а у них только асинхронный».
4. Стили обмена и вопросы к каждому
| Стиль | Когда естественен | Что обязан спросить аналитик |
|---|---|---|
| REST/HTTP | CRUD-подобные операции, внешние партнёры | какие коды на что, идемпотентность, пагинация, фильтры, лимиты |
| gRPC | внутренние сервисы, много вызовов, строгая схема | эволюция proto, дефолты полей, дедлайны, кто владеет схемой |
| GraphQL | много разных клиентов, гибкие выборки | лимит сложности запроса, N+1 на бэкенде, кэширование, права на поля |
| SOAP/XML | госсистемы, банки, легаси | WSDL и его версия, XSD-валидация, подпись, кодировки |
| Вебхуки | уведомления от внешней системы | подпись, повторы, порядок, дедупликация, что если наш endpoint лежал |
| Брокер сообщений | развязка сервисов, пики, события | семантика доставки, порядок, DLQ, схема события, ретеншен |
| Файлы (SFTP/S3) | выгрузки, реестры, банк-клиент, 3PL | окно, имя файла, кодировка, порядок, повторная выгрузка, признак конца |
| Прямой доступ к БД | почти никогда | как узнаем об изменении схемы (ответ: никак) — искать альтернативу |
4.1. Главный водораздел: синхронно или асинхронно
Вопрос звучит технически, а решается предметно. Синхронный вызов возможен, только если бизнес готов ждать ответа в пределах пользовательского терпения и партнёр гарантирует такой отклик. Как только между запросом и результатом появляется человек, батч или внешняя система с SLA в часы — обмен асинхронный, и это меняет весь UX: нужен статус «в обработке», нужно уведомление, нужен предел ожидания и действие по его истечении.
Признаки, что «синхронно» не получится, даже если очень хочется: в процессе есть ручная проверка; партнёр отвечает «примем в обработку»; операция затрагивает внешнюю платёжную инфраструктуру; в SLA партнёра фигурируют часы или рабочие дни. Асинхронность — не техническая деталь, а требование к интерфейсу и к процессу; подробнее про событийную модель — в event-driven архитектуре.
4.2. Файловый обмен: не устаревший, а специфический
Файлы никуда не делись: реестры платежей, выгрузки 3PL, обмен с госсистемами. Для них формулируются свои вопросы, которых нет в REST:
- Имя файла: шаблон, дата в имени, часовой пояс даты, уникальность.
- Окно доставки: во сколько появляется, до какого времени ждём, что если не пришёл.
- Признак завершённости: флаг-файл
*.ok, переименование, контрольная сумма — иначе прочитаете половину файла, который ещё пишется. - Порядок и повторы: файл за прошлый час прислали дважды — это дубль или корректировка?
- Кодировка и разделитель:
windows-1251и;— норма для российского финтеха, а не экзотика. - Что делать со «строкой-ошибкой»: отбраковать строку или весь файл?
4.3. Антипаттерн: интеграция через чужую БД
«Дайте нам read-only доступ к их базе» выглядит быстрым решением и создаёт связанность, которую невозможно контролировать: у чужой схемы нет контракта, нет версий и нет обязательств. Партнёр переименует колонку в пятницу — вы узнаете в субботу. Если такой обмен уже есть, задача аналитика — описать его как интеграцию (какие таблицы, какие поля, кто уведомляет об изменениях) и поставить в план замену на нормальный контракт.
5. Форматы и поля: где ломается на самом деле
5.1. Форматы
| Формат | Схема | Сильная сторона | Что важно аналитику |
|---|---|---|---|
| JSON | JSON Schema (опционально) | читаем, универсален | нет типов дат и decimal — договариваться словами |
| XML | XSD (обычно есть) | строгая валидация, подписи | namespace, атрибут vs элемент, пустой элемент ≠ null |
| Protobuf | .proto (обязательна) | компактность, строгая эволюция | номера полей нельзя переиспользовать, дефолты вместо null |
| Avro | схема в реестре | эволюция схем в потоках | правила совместимости в Schema Registry |
| CSV | нет | простота, объёмы | кодировка, разделители, кавычки, экранирование, десятичный знак |
5.2. Каталог полевых ловушек
Это самый прикладной список в статье: он ловит больше дефектов, чем любая другая техника.
- Дата и время. RFC 3339 с зоной или «10:00 по Москве»? Что значит «дата операции» — момент запроса, момент проводки или банковский день? Как передаётся дата без времени?
- Деньги. Минорные единицы (
amount_minor: 149900) или дробь?decimal, а неfloat(0.1 + 0.2 ≠ 0.3). Валюта по ISO 4217 в каждом сообщении, правило округления, кто считает НДС. - Отсутствие значения. Поля нет, поле
null, пустая строка — три разных состояния. В PATCH разница критична:nullможет означать «удалить», а отсутствие — «не менять». - Перечисления. Список закрытый или расширяемый? Что делает клиент с неизвестным значением — падает или обрабатывает как «прочее» (принцип tolerant reader)?
- Идентификаторы. Тип, длина, регистр;
int64в JSON ломается в JavaScript после 2^53 — значит, строка. Кто генерирует ID и в какой момент он появляется? - Строки и локали. Длина в символах или байтах, юникод и эмодзи, телефон в E.164, транслитерация ФИО для банковских реестров.
- Массивы, числа, единицы. Предел размера, значимость порядка, смысл пустого массива; единица измерения и точность — «вес 0» это ошибка или «не измерено»?
- Размеры и пагинация. Лимит тела запроса, размер страницы по умолчанию и максимум, стабильность сортировки (иначе клиент увидит одну запись дважды).
Тот же контракт в двух видах — «как обычно приходит» и «как надо договориться»:
// ПЛОХО: три интерпретации на четыре поля
{
"id": 90071992547409931, // int64 — потеряет точность в JS
"amount": 1499.9, // рубли? с копейками? float?
"date": "16.07.2026 10:00", // чей часовой пояс, какой день
"status": "OK" // OK — принято или уже исполнено?
}
// ХОРОШО: каждое поле имеет одно чтение
{
"refund_id": "rf_90071992547409931",
"amount_minor": 149990,
"currency": "RUB",
"requested_at": "2026-07-16T10:00:00+03:00",
"status": "processing",
"status_final": false,
"settlement_date": "2026-07-17"
}
5.3. Таблица маппинга полей
Рабочий артефакт аналитика на интеграции — не проза, а таблица соответствия. Её формат:
| Наше поле | Тип | Поле партнёра | Тип | Правило преобразования | Обязательность | Что если пусто |
|---|---|---|---|---|---|---|
return.id |
string(36) | merchant_reference |
string(50) | без изменений | да | ошибка валидации |
return.amount |
decimal(12,2) | amount_minor |
int64 | ×100, округление HALF_UP | да | ошибка валидации |
return.currency |
char(3) | currency |
string | ISO 4217, uppercase | да | подставить RUB |
order.paid_at |
timestamptz | original_payment_date |
date | взять дату в TZ Europe/Moscow | да | искать по payment_id |
customer.phone |
string | customer_contact |
string | привести к E.164 | нет | не передавать поле |
reason_code |
enum(8) | reason |
string | по словарю MAP-RSN-01 | да | other + текст в comment |
Три колонки, которые чаще всего забывают: правило преобразования, обязательность и что делать при пустом значении. Именно они превращают маппинг из картинки в проверяемое требование. Про качество данных на входе — data quality.
5.4. Соответствие идентификаторов: модель, которую забывают
Как только появляется внешняя система, появляется задача «связать наш объект с их объектом» — и её нужно смоделировать явно, отдельной сущностью, а не полем «на всякий случай».
Что читается из модели: ключ идемпотентности — наш атрибут, он существует до первого успешного
ответа; psp_refund_id появляется позже и может не появиться никогда; event_id нужен, чтобы
повторный вебхук не создал вторую запись; WMS_INTAKE необязателен (o|), значит бывают возвраты
без подтверждённой приёмки — и надо описать, что с ними делать. Подробнее про мощности связей —
моделирование данных.
6. Сценарии обмена: happy path — это 20 % работы
6.1. Обязательный набор сценариев
Для каждой интеграции описываются не «основной поток», а список ветвей. Минимальный набор:
- Успех синхронный.
- Успех асинхронный: приняли в обработку, финальный статус пришёл позже.
- Валидационный отказ партнёра (наши данные плохие).
- Бизнес-отказ партнёра (данные хорошие, операция невозможна).
- Таймаут: результат неизвестен.
- Повтор после таймаута: результат тот же, деньги не списаны дважды.
- Партнёр недоступен полностью: очередь, деградация, что видит пользователь.
- Дубль уведомления: тот же вебхук пришёл два раза.
- Уведомление не пришло вообще: срок ожидания и переход к сверке.
- Уведомления пришли не по порядку:
failedпослеsucceeded. - Частичный успех: деньги вернули, склад не подтвердил.
- Компенсация: операцию нужно откатить, а откатить нечем.
- Сверка: ежедневное сопоставление наших записей с их реестром.
Первые четыре обычно есть в любой спецификации. Пункты 5–13 — то, из чего состоит эксплуатация. Если их нет в документе, они всё равно произойдут, просто в виде инцидентов.
6.2. Сценарий с таймаутом, идемпотентностью и вебхуком
GET /refunds?merchant_reference=...
Диаграмма — не украшение: она фиксирует четыре решения, которые в тексте обычно теряются. Ключ
идемпотентности генерируем мы и до первого вызова. Ответ 202 пользователю означает, что в
UI обязателен промежуточный статус. Дедупликация вебхука идёт по event_id, а не по «телу
сообщения». И есть предел ожидания с явным действием после него. Механику гарантий доставки —
в идемпотентности и семантике доставки.
6.3. Жизненный цикл с точки зрения обеих систем
Состояния — самое недооценённое место интеграционного анализа. Обычно рисуют наши статусы и забывают, что у партнёра свои, и что бывает состояние «мы не знаем».
Состояния UNKNOWN и RECON — те самые «неявные требования». Их никто не заказывает, потому что
никто о них не думает, но без них система живёт на ручном труде поддержки. Появление этих
состояний в модели немедленно рождает требования: экран разбора, отчёт расхождений, роль,
которая этим занимается, срок реакции.
6.4. Сверка как обязательная часть контракта
Любой асинхронный обмен деньгами или обязательствами требует сверки. Запрос, который аналитик может написать сам и приложить к спецификации как критерий:
-- Расхождения между нашими заявками и реестром PSP за сутки: пять классов,
-- у каждого своё действие. FULL JOIN, потому что «нет записи» — тоже расхождение.
WITH ours AS (
SELECT r.return_id, r.status, r.amount_minor, l.psp_refund_id
FROM returns r LEFT JOIN refund_link l ON l.return_id = r.return_id
WHERE r.created_at >= current_date - INTERVAL '1 day'
), theirs AS (
SELECT psp_refund_id, psp_status, amount_minor
FROM psp_settlement_report WHERE report_date = current_date - 1
)
SELECT o.return_id, o.status AS our_status, t.psp_status AS their_status,
CASE
WHEN t.psp_refund_id IS NULL THEN 'нет в реестре партнёра'
WHEN o.return_id IS NULL THEN 'списание без нашей заявки'
WHEN o.amount_minor <> t.amount_minor THEN 'расходятся суммы'
WHEN o.status = 'DONE' THEN 'мы закрыли, партнёр нет'
ELSE 'партнёр исполнил, мы не знаем'
END AS problem
FROM ours o FULL JOIN theirs t ON t.psp_refund_id = o.psp_refund_id
WHERE t.psp_refund_id IS NULL OR o.return_id IS NULL
OR o.amount_minor <> t.amount_minor
OR (o.status = 'DONE') <> (t.psp_status = 'succeeded');
Требование к сверке формулируется измеримо: «расхождений класса „партнёр исполнил, мы не знаем“ не более 0,1 % операций в сутки, все они закрываются в течение одного рабочего дня». Это уже проверяемое НФТ, и оно ведёт к следующей статье.
7. Ошибки: контракт на неуспех
7.1. Три уровня, которые нельзя смешивать
| Уровень | Примеры | Кто обрабатывает | Ответ пользователю |
|---|---|---|---|
| Транспорт | DNS, TLS, connection refused, таймаут | инфраструктура + ретраи | «попробуйте позже», операция может быть выполнена |
| Протокол | 400, 401, 404, 409, 422, 429, 503 | код клиента по правилам | зависит от кода, часто «мы починим» |
| Бизнес | «карта закрыта», «лимит возврата исчерпан» | продуктовая логика и UX | понятный человеку текст и действие |
Смешение уровней — типовой дефект. Бизнес-отказ, отданный как 500, заставляет клиента ретраить
то, что никогда не выполнится. Валидационная ошибка, отданная как 200 с флагом в теле, ломает
мониторинг: графики зелёные, деньги не возвращаются.
7.2. Тело ошибки
Единый формат ошибки — часть контракта. Стандарт есть: RFC 9457 Problem Details.
{
"type": "https://api.psp.example/problems/refund-limit-exceeded",
"title": "Refund limit exceeded",
"status": 409,
"detail": "Сумма возврата превышает остаток по исходному платежу",
"instance": "/refunds/rf_91",
"code": "ERR-PSP-07",
"retryable": false,
"remaining_minor": 32000,
"trace_id": "01J8Z4K0T6"
}
Что здесь важно именно аналитику: стабильный code, по которому пишутся правила и тексты; флаг
retryable, снимающий спор «повторять или нет»; trace_id для разбора инцидентов
(наблюдаемость); поле
remaining_minor, позволяющее показать человеку осмысленное сообщение вместо «ошибка».
7.3. Матрица ошибок — главный артефакт раздела
| Код | Что случилось | Повтор | Действие системы | Текст пользователю |
|---|---|---|---|---|
| ERR-PSP-01 | невалидный запрос | нет | лог + алерт разработчикам | «Не удалось оформить, мы уже разбираемся» |
| ERR-PSP-03 | нет прав / ключ истёк | нет | алерт дежурному, стоп интеграции | «Возврат оформим вручную до 18:00» |
| ERR-PSP-05 | карта закрыта | нет | предложить возврат на счёт | «Карта недоступна, выберите другой способ» |
| ERR-PSP-07 | лимит возврата исчерпан | нет | показать остаток | «Доступно к возврату 320 ₽» |
| ERR-PSP-08 | 429, лимит запросов | да, с backoff | замедлить, поставить в очередь | статус «в обработке» |
| ERR-PSP-09 | 503 / таймаут | да, ≤ 2 раза, тот же ключ | после исчерпания — в сверку | статус «в обработке» |
Матрица заполняется совместно с UX и поддержкой. Пустая клетка «текст пользователю» — это незакрытое требование, а не косметика: именно из этих клеток потом вырастает половина обращений в поддержку. Про формулировки в интерфейсе — UX и требования.
7.4. Бюджет таймаутов и повторов
Ретраи без согласованного бюджета создают ровно ту проблему, от которой должны спасать.
Три правила, которые аналитик обязан зафиксировать в спецификации:
- Таймаут снаружи больше суммы таймаутов и пауз внутри. Иначе внешний уровень оборвёт вызов посреди повтора и потеряет результат, который у партнёра уже есть.
- Ретраит ровно один уровень. Если ретраят три уровня по три раза, партнёр получит 27 запросов на одну операцию — это уже не отказоустойчивость, а самодиагностируемый DDoS.
- Повторяются только идемпотентные операции. Для остальных повтор запрещён, и вместо него — переход в состояние «результат неизвестен» с последующей сверкой.
Паттерны, стоящие за этими правилами (retry с экспоненциальной задержкой и джиттером, circuit breaker, bulkhead), разобраны в resilience-паттернах. Аналитик не выбирает библиотеку, но именно он приносит цифры: сколько ждёт пользователь, сколько стоит лишний вызов, что важнее — быстрый отказ или отложенное исполнение.
8. Что чаще всего ломается в анализе интеграций
8.1. Неявные требования
Неявное требование — то, которое все считают очевидным, поэтому никто не произносит. На границе систем их концентрация максимальна. Работающий приём — набор вопросов-детекторов, которые задаются к любой интеграции:
- А если ответа нет — сколько ждём и что делаем потом?
- А если то же сообщение придёт дважды?
- А если сообщения придут не по порядку?
- А если партнёр пришлёт новое значение статуса, которого нет в нашем списке?
- А кто узнаёт, что интеграция сломалась, и через сколько минут?
- А что происходит с уже начатыми операциями во время нашего релиза?
- А как выглядит первый запуск: нужно ли грузить историю и за какой период?
- А как это выключить, если партнёр начнёт присылать мусор?
- А кто платит за лишние вызовы и есть ли лимит по деньгам?
- А что мы обязаны залогировать, и можно ли писать в лог номер карты (нет)?
Последний пункт — про безопасность API и PII: маскирование, срок хранения, состав аудита. Это тоже требования, и их формулирует аналитик.
8.2. Противоречия между стейкхолдерами
Интеграция — место, где интересы сталкиваются лбами, потому что за каждой стороной стоит своя метрика. Живой пример из процесса возврата:
| Стейкхолдер | Что хочет | Почему | С чем конфликтует |
|---|---|---|---|
| Финансы | возврат только после физической приёмки товара | потери от мошенничества | скоростью возврата для клиента |
| Клиентский сервис | мгновенный возврат по нажатию | NPS и нагрузка на поддержку | требованием финансов |
| Платформенная команда | только асинхронный вызов | SLA партнёра — часы | обещанием «мгновенно» в UI |
| Юристы | письменное согласие на передачу данных в 3PL | требования по ПД | простотой сценария |
Такое противоречие не решается голосованием и тем более не решается тем, что аналитик выберет сам. Рабочая последовательность: сделать конфликт видимым (эта таблица уже половина решения), свести к количественной постановке (возврат до приёмки для сумм до 3 000 ₽ даёт X потерь и Y экономии на поддержке), найти решение, устраняющее сам конфликт (мгновенный возврат в лимите суммы, полный — после приёмки), зафиксировать решение с автором и датой. Техники согласования — работа со стейкхолдерами.
Отдельный вид противоречия — между документом и реальностью. Партнёр присылает спецификацию v3, а прод отвечает как v2. Правило: контракт проверяется на реальном трафике. Пятнадцать сохранённых ответов из тестовой среды стоят дороже сорока страниц документации.
8.3. «Хотелки» без задачи
Три формулировки, которые звучат как требования, но требованиями не являются:
- «Хотим real-time». Вопрос: какое решение принимается на основании этих данных и как быстро? Часто выясняется, что отчёт смотрят утром, и обновления раз в час достаточно. Разница в цене — порядок.
- «Отдайте нам всё через один эндпоинт». Вопрос: какие поля используются в каких экранах? Обычно нужны шесть полей из восьмидесяти, а «всё» появилось из страха что-то не получить — и создаёт нагрузку, риск утечки PII и невозможность эволюции контракта.
- «Пусть будет как в предыдущей системе». Вопрос: какую задачу решала та схема? Часто она решала задачу, которой уже нет, и переносится вместе с легаси-ограничениями.
Приём один и тот же: подняться от решения к задаче. Не «зачем вам real-time», а «какое решение вы принимаете и что произойдёт, если данные будут на час старше». Ответ либо даёт настоящее требование, либо снимает хотелку. Подробнее про эту лестницу — в видах требований.
8.4. Требования, которые невозможно проверить
Непроверяемая формулировка — это отложенный спор на приёмке. Переписывать нужно до разработки:
| Непроверяемо | Проверяемо |
|---|---|
| «Интеграция должна быть надёжной» | «При недоступности PSP заявки не теряются: 100 % попадают в очередь и исполняются в течение 30 минут после восстановления» |
| «Ответ должен приходить быстро» | «p95 ответа POST /refunds ≤ 800 мс, p99 ≤ 3 с при 5 rps» |
| «Данные должны быть актуальными» | «Статус в CRM отстаёт от источника не более чем на 60 с в 99 % случаев» |
| «Ошибки должны обрабатываться корректно» | «Для каждого кода из ERR-PSP-01…09 есть определённое действие системы и текст пользователю (матрица в разделе 7.3)» |
| «Система должна быть защищена» | «Все вызовы по mTLS, вебхуки с подписью HMAC-SHA256, отклонение неподписанных с 401, ключи ротируются раз в 90 дней» |
| «Нужна поддержка больших объёмов» | «Разовая догрузка 20 000 заявок за окно 2 ч без превышения лимита партнёра 10 rps» |
Тест на проверяемость простой: можно ли придумать процедуру, которая даст ответ «да» или «нет»? Если нет — это не требование, а пожелание, и на приёмке оно превратится в конфликт (приёмка).
9. Формальный документ или схема на доске
Формальность стоит денег: её пишут, согласовывают, поддерживают в актуальном состоянии. Платить эту цену нужно там, где она возвращается.
или юрлиц?"} B -->|да| F["Формальный контракт:
OpenAPI/AsyncAPI в репозитории,
версия, SLA, регламент изменений"] B -->|нет| C{"Деньги, персональные данные
или необратимые действия?"} C -->|да| F C -->|нет| D{"Больше двух команд
или срок жизни больше квартала?"} D -->|да| E["Лёгкий контракт:
схема в репозитории провайдера
+ contract-тесты"] D -->|нет| G{"Решение откатывается
за один спринт?"} G -->|да| H["Доска и разговор:
фото схемы в задаче,
примеры запросов в тестах"] G -->|нет| E
| Достаточно доски и разговора | Обязателен формальный документ |
|---|---|
| два разработчика одной команды и общий репозиторий | обмен между компаниями, тем более с деньгами |
| внутренний сервис, который можно поменять за день | госсистемы, банки, регулируемые данные |
| эксперимент, который выключается флагом | контракт, на который завязаны несколько потребителей |
| уточнение существующего поля | всё, что попадает в договор или SLA |
| прототип для проверки гипотезы (прототипирование) | интеграция, которую будут поддерживать другие люди через год |
Важная тонкость: «доска» — не отсутствие артефакта. Минимум, который остаётся после разговора: фотография схемы в задаче, два-три примера реальных запросов и ответов в тестах, запись решения в один абзац. Это дёшево и переживает отпуск автора. А вот обратная ошибка дороже: сорок страниц спецификации на интеграцию из двух вызовов между своими сервисами — документ, который устареет к первому релизу и которому перестанут верить.
Минимальный формальный набор, если решение «нужен документ»: машиночитаемая схема (OpenAPI/AsyncAPI) в репозитории провайдера как источник истины; паспорт интеграции; матрица ошибок; список сценариев из раздела 6.1 с ожидаемым поведением; таблица маппинга полей. Проза вокруг — только там, где она добавляет смысл, которого нет в схеме.
Схему аналитик вполне может писать сам — это уже норма профессии, и она резко сокращает цикл согласования. Фрагмент, где важна именно аналитическая часть, — ошибки и идемпотентность:
# openapi.yaml — фрагмент. Аналитическая часть: идемпотентность и контракт на ошибки.
paths:
/v1/refunds:
post:
summary: Создать возврат средств по исходному платежу
parameters:
- name: Idempotency-Key # обязателен: повтор не должен вернуть деньги дважды
in: header
required: true
schema: { type: string, maxLength: 64 }
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [merchant_reference, amount_minor, currency, reason_code]
properties:
merchant_reference: { type: string, maxLength: 50 }
amount_minor: { type: integer, format: int64, minimum: 1 }
currency: { type: string, pattern: "^[A-Z]{3}$" }
reason_code: { type: string, enum: [defect, wrong_item, refused, other] }
responses:
"202":
description: Принято, финальный статус придёт вебхуком в течение 24 ч
"409":
description: Тот же ключ с другим телом либо лимит возврата исчерпан
content:
application/problem+json: # RFC 9457
schema: { $ref: "#/components/schemas/Problem" }
"429":
description: Лимит запросов; повтор после Retry-After с тем же ключом
headers:
Retry-After: { schema: { type: integer } }
Для событий и вебхуков роль OpenAPI играет AsyncAPI — тот же приём, другая спецификация:
# asyncapi.yaml — фрагмент. Событие о результате возврата.
channels:
return.refunded:
description: Публикуется один раз на возврат; получатель обязан дедуплицировать по event_id
subscribe:
message:
headers:
type: object
required: [event_id, occurred_at, schema_version] # ключ дедупликации и версия схемы
properties:
event_id: { type: string }
occurred_at: { type: string, format: date-time }
schema_version: { type: integer, minimum: 1 }
payload:
type: object
required: [return_id, order_id, amount_minor, currency]
Дальше схема оживает: линтер стиля (Spectral), мок для проверки сценария до разработки (Prism), контрактные тесты между потребителем и поставщиком (Pact). Аналитику достаточно уметь поднять мок и прогнать по нему сценарии — этого хватает, чтобы найти дефекты контракта за час вместо спринта. Про проверку контрактов подробно — тестирование API и интеграционное тестирование.
Полезная привычка: проверять реальные ответы партнёра на соответствие заявленной схеме, а не верить документации.
"""Сверка 20-30 сохранённых ответов партнёра с заявленной JSON Schema.
Запускает аналитик до начала разработки: находит расхождения документации и поведения."""
import json
from pathlib import Path
from jsonschema import Draft202012Validator
validator = Draft202012Validator(
json.loads(Path("schemas/refund_response.json").read_text(encoding="utf-8")))
samples = sorted(Path("samples").glob("*.json"))
for sample in samples:
payload = json.loads(sample.read_text(encoding="utf-8"))
# Собираем ВСЕ нарушения, а не первое: важна картина в целом.
errors = [f"{'.'.join(map(str, e.path)) or '<root>'} — {e.message}"
for e in validator.iter_errors(payload)]
if errors:
print(sample.name, *(f" - {e}" for e in errors), sep="\n")
Типичный результат первого запуска: поле, объявленное обязательным, отсутствует в трети ответов;
status содержит значение, которого нет в документации; сумма приходит строкой. Каждая находка —
вопрос партнёру до кода, а не багрепорт после релиза.
10. Чек-лист интеграции
Перед тем как отдать интеграцию в разработку, пройдите по списку. Каждый пункт — либо ответ в спецификации, либо явно записанное допущение с владельцем.
Контекст. Зачем эта интеграция бизнесу · кто владелец данных · что будет, если её не делать · кто со стороны партнёра принимает решения · есть ли договор и SLA.
Доступ. Адреса всех сред · кто заказывает доступы и за сколько дней · схема аутентификации · где хранятся секреты · ротация ключей · ограничения по IP.
Данные. Схема сообщений · маппинг полей с правилами преобразования · форматы даты, денег, телефонов · словари и что делать с неизвестным значением · обязательность и поведение при пустом · PII и маскирование в логах.
Семантика. Что именно делает каждый вызов · какие побочные эффекты · единица работы · идемпотентность и её ключ · кто генерирует идентификаторы.
Сценарии. Все 13 ветвей из раздела 6.1 · первичная загрузка истории · поведение во время нашего релиза и релиза партнёра · как выключить интеграцию.
Ошибки. Матрица кодов с действием и текстом · что повторяемо · бюджет таймаутов и повторов · кто узнаёт об аварии и через сколько · процедура сверки и допустимый уровень расхождений.
Качество. Объёмы и пики · лимиты партнёра · требования к задержке и доступности · окна регламентных работ · нефункциональные требования.
Эволюция. Версионирование · правила обратной совместимости · срок депрекейта · как партнёр уведомляет об изменениях · как мы уведомляем своих потребителей.
Мини-итог
- Граница систем — место с максимальной плотностью потерянных требований; работать там обязан аналитик, а не только разработчик.
- Анализ начинается с карты интеграций и паспорта на каждую стрелку, а не со спецификации метода.
- Контракт — восемь слоёв: адрес, доступ, синтаксис, семантика, ошибки, сценарии, качество, эволюция. Незакрытый слой всегда всплывает в проде.
- Больше всего дефектов дают не «сложные» вещи, а даты, деньги,
null, перечисления и идентификаторы. Таблица маппинга с правилами преобразования — самый дешёвый способ их поймать. - Happy path — пятая часть работы. Таймаут с неизвестным результатом, дубли, нарушенный порядок, частичный успех и сверка описываются наравне с успехом.
- Ошибка — часть контракта: стабильный код, признак повторяемости, действие системы и текст пользователю. Ретраи без согласованного бюджета таймаутов делают хуже, чем их отсутствие.
- Формальный документ нужен там, где высока цена ошибки и длинный срок жизни: другая организация, деньги, ПД, много потребителей. Внутри команды достаточно схемы на доске, примеров в тестах и абзаца с решением — но не «ничего».
- Непроверяемое требование на интеграции — гарантированный спор на приёмке. Переписывайте до разработки.
Источники
- OpenAPI Specification и AsyncAPI — машиночитаемые описания синхронных и событийных API.
- RFC 9457, Problem Details for HTTP APIs — стандартный формат тела ошибки; RFC 9110 — семантика HTTP и коды состояний; RFC 3339 — даты.
- Stripe: Idempotent requests — эталонный разбор идемпотентности в платёжном API.
- Zalando RESTful API Guidelines и Google API Improvement Proposals — готовые своды правил, из которых удобно собирать собственный стандарт.
- Gregor Hohpe, Bobby Woolf, Enterprise Integration Patterns — язык паттернов обмена сообщениями.
- Michael Nygard, Release It! — про таймауты, каскадные отказы и жизнь интеграций в проде.
- Sam Newman, Building Microservices — главы про контракты и границы сервисов.
- Martin Fowler, Tolerant Reader — как не падать от чужих изменений.
- JSON Schema, Protocol Buffers, Pact, Prism, Spectral — инструменты, которыми аналитик проверяет контракт собственными руками.
- BABOK v3, IIBA — раздел про анализ интерфейсов в общей системе техник.
Что дальше
Разбирая интеграции, мы всё время натыкались на цифры: таймауты, объёмы, допустимое отставание реплики, доля расхождений в сверке, срок депрекейта. Это уже не функциональные требования — это требования к качеству, и у них своя техника: выбор характеристик, метрик, целевых значений и, главное, способов измерения.
Нефункциональные требования: производительность, надёжность, безопасность, как их измерять