Сборка у продюсера зелёная: тесты прошли, данные легли в таблицу. Отчёт падает назавтра — колонку переименовали, и первым об этом узнал дашборд, а не человек. Никто не ошибся: каждый сделал свою работу. Сломано не поведение людей, а устройство связки — между изменением схемы и тем, кто от него зависит, нет ничего, что могло бы остановить деплой.
Контракт данных — попытка поставить на это место остановку. Сразу отграничим тему: контракт не про качество данных вообще и не про то, как узнать о поломке раньше всех — про обнаружение у нас есть отдельный разбор, «Схема поехала: пять способов узнать об этом первым». Контракт про интерфейс: что продюсер обязуется не менять молча.
Контракт — это не документ
У работающего контракта четыре части. Схема: поля, типы, обязательность. Семантика: то, что тип не выражает, — сумма в копейках или в рублях, метка времени в каком часовом поясе, что считается дублем. Политика совместимости: какие изменения безопасны, а какие ломают потребителя. И точка контроля: место, где нарушение что-то останавливает.
Четвёртая часть — определяющая. Пока нарушение контракта ничего не останавливает, контракта нет — есть описание таблицы на день подписания. Отсюда тест, который стоит применять к любому «у нас есть контракты»: что происходит в момент нарушения? «Падает сборка продюсера» или «батч уходит в карантин» — контракт есть. «Кому-то нужно вспомнить, что в вики была страница» — нет.
И ещё одно отграничение, без которого контракты умирают под собственным весом: контракт — не инвентаризация. Он покрывает не таблицу целиком, а то, что потребитель читает. Поля, которые никто не читает, продюсер должен иметь право менять свободно — иначе контракт превращается в смирительную рубашку, и продюсер начнёт его обходить, потому что обход дешевле переговоров.
Тупик: первым делом пишут в вики
Первый ход почти всегда один: описать поля в корпоративной вики, отправить потребителю ссылку, поставить галочку. Понятно почему — дёшево, быстро, выглядит как договор. И это не работает, причём по устройству, а не по старанию.
Документ не может остановить деплой — это свойство носителя. Документ не участвует в цепочке поставки данных: сборка его не читает, пайплайн его не читает. Тот, кто меняет таблицу, в момент изменения смотрит в код и в задачу; страница вики физически не попадает в поле зрения. Поэтому документ стареет мгновенно: он верен на день подписания и с каждым деплоем расходится с реальностью всё сильнее. Подпись под таким документом создаёт чувство закрытой задачи — и это худшая часть: проблема не решена, но сигнал о ней больше не подаётся.
Рядом с вики лежат ещё два хода, назовём их честно. Валидация на стороне потребителя после посадки данных — это обнаружение, а не предотвращение: о нарушении вы узнаёте так же поздно, только теперь в системе лишний компонент. Обнаружение нужно, без него никуда, но это другой инструмент, и путать их дорого. А реестр схем, навешанный поверх процесса, в котором продюсер с ним не сверяется, — склад правильных намерений. Хранилище схемы не становится контролем, пока схема автоматически не сравнивается с чем-то.
Что делать руками
Начинать надо не с каталога контрактов, а с одного отчёта. Дальше — шаги; у каждого есть условие применимости, и его лучше проверять до, а не после.
Шаг 1. Возьмите отчёт, который ломают чаще прочих. Если ломают всё равномерно — тот, при поломке которого звонят вам. Если не ломается ничего, контракты вам пока не нужны; вернитесь к тексту, когда понадобится.
Шаг 2. Выпишите, что потребитель читает на самом деле. Не таблицу — используемое. Источники: код трансформации, текст запросов, список колонок дашборда. Если потребитель — человек с SQL-запросом, спросите его; если код — выписывайте из кода, а не из его слов о коде. Это самое трудоёмкое место всей затеи и самое важное: получившееся множество полей и есть покрытие контракта. Отдельный случай — потребитель с «звёздочкой»: он читает всё, и это не повод покрывать контрактом всю таблицу, а повод для разговора о том, зачем ему всё. Если поле никто не читает, но вы не уверены — это не кандидат в контракт, это кандидат на удаление.
Шаг 3. Запишите контракт машиночитаемо. Формат берите тот, что уже живёт в вашем стеке: JSON Schema, Avro, Protobuf, режим контрактов в средстве трансформации, если он там есть. Формат — наименее важная часть решения; важнее семантика, которую тип не выражает: единицы, часовой пояс, допустимая пустота поля, уникальность ключа. Она записывается рядом со схемой в виде проверок. И решите, где контракт живёт. Наш выбор — отдельный репозиторий с PR-процессом: контракт — это переговоры двух сторон, и место переговоров должно быть общим. Контракт, лежащий в коде продюсера, потребитель обнаруживает в момент удара — то есть тогда, когда читать его уже поздно.
Шаг 4. Встройте проверку в CI продюсера. Механика: шаг сборки сравнивает изменившуюся схему с контрактом. Добавление необязательного поля — проходит. Переименование, смена типа, перенос смысла в другое поле — сборка падает. Падение — это не отказ, а начало переговоров: продюсер либо откатывает изменение, либо запускает окно устаревания из шага 6. Вот в этот момент контракт начинает существовать: нарушение перестаёт быть невидимым и становится красной сборкой с фамилией. Условие применимости жёсткое: если у продюсера нет управляемого процесса изменений — CI нет или изменения ходят мимо него, — контракты в этой форме не работают. Сначала процесс, потом контракт; в обратном порядке не получается.
Шаг 5. Проверяйте на записи. CI ловит нарушение в коде, но данные нарушают контракт и без изменения кода: источник прислал мусор, поменялся upstream. Вторая точка контроля — вход пайплайна: батч валидируется до посадки, нарушивший уходит в карантин, а не в отчёт. Про карантин скажем отдельно: у него должны быть владелец и алерт. Батч в карантине, на который никто не смотрит, — это та же поломка отчёта, только с задержкой и лишним местом в системе. И здесь есть компромисс, о котором надо сказать честно: полная проверка каждого батча стоит вычислений. Можно проверять выборочно, но тогда контракт перестаёт быть гарантией и становится вероятностью. Иногда это осознанный размен, иногда самообман — различие в том, записан ли размен в политике.
Шаг 6. Определите окно устаревания. Ломающие изменения бывают нужны — тогда они не запрещены, а спланированы: новая версия схемы, старая живёт переходный период, потребитель мигрирует. Длину окна считайте не от среднего потребителя, а от самого медленного: возьмите время его последней миграции и добавьте запас. Если такой истории у вас нет — начните собирать её сейчас, иначе окно всегда будет выставляться на глаз.
Шаг 7. Имена с обеих сторон. У контракта есть подписант от продюсера и подписант от потребителя. Без имён контракт через месяц окажется ничьим — и его нельзя будет ни изменить, ни защитить.
Позиция редакции
Мы считаем, что контракт данных — это проверка, которая падает в сборке продюсера. Схема в вики, письмо о договорённостях, валидация на своей стороне — эскизы контракта, а не он сам. И мы против широких контрактов: покрытие — только то, что потребитель читает; старт — один отчёт, а не каталог.
Условие, при котором мы неправы. Если продюсер внешний и его изменения не проходят и не могут проходить через ваш CI — вендорская система, сторонний источник, — техническая часть схемы не работает: остаётся организационная договорённость и детекция на своей стороне. Тогда честнее не называть это контрактом и вкладываться в раннее обнаружение. Второе условие: если таблицу читает один потребитель и он сидит за соседним столом, стоимость формального контракта выше пользы — договоритесь голосом. Форма контракта должна соответствовать расстоянию между сторонами.
Что дальше
Когда контракт падает, встаёт следующий вопрос: где именно нарушено — какой батч, какой upstream, на каком стыке. Для ответа в пайплайне нужна трассировка, и что в неё писать, а что не писать, — тема отдельного нашего материала.
И открытый вопрос, на который у нас нет готового ответа: что делать, когда у одной таблицы много потребителей и их множества читаемых полей пересекаются. Композиция контрактов — место, где практика расходится с теорией. Если вы это уже решали — расскажите.