Команда решила заменить поле статуса заказа новым форматом. В тестовой среде достаточно переименовать колонку и поправить запросы. В production всё сложнее: часть приложений ещё работает со старым полем, обновление раскатывается постепенно, а фоновые задачи могут жить дольше одного релиза.
Если сначала удалить старое поле, старый код перестанет работать. Если оставить только старую модель, новая функциональность не сможет развиваться. Expand–Contract позволяет провести изменение по этапам так, чтобы соседние версии приложения некоторое время оставались совместимыми.
Сначала расширяем, не ломая прежний путь
На этапе expand добавляют новый способ хранения или передачи данных, не удаляя старый. Например, рядом с прежним статусом появляется новый. Приложение получает возможность понимать оба формата, а команда начинает обновлять потребителей.
Часто приходится перенести исторические записи. Этот backfill нужно выполнять с учётом объёма данных и новых изменений, которые продолжают приходить во время переноса. Иначе к концу миграции окажется, что часть записей успела измениться лишь в старом поле.
Можно временно поддерживать двойную запись или другой механизм синхронизации, но нужен ясный источник истины. Без него два значения начинают расходиться, а команда не понимает, какое исправлять.
Переходный период — отдельное состояние продукта
Некоторое время будут существовать старые и новые читатели и писатели. Важно проверить не только новую версию приложения, но и их сочетания. Особенно опасна ситуация, когда новая версия записывает данные, которые старый код ещё должен читать.
Я бы отдельно проверил, как поступает система при возврате старой версии, что происходит с изменёнными записями и можно ли восстановить данные после неудачного переноса. Команда должна понимать, где откат ещё безопасен, а где потребуется новая корректирующая миграция.
Этот подход нужен не только для таблиц. Он помогает менять форматы API и событий, когда у них есть независимые потребители. Но в каждом случае должны быть определены совместимость и срок жизни переходного контракта.
Удаление старого — обязательная часть решения
Contract наступает только после подтверждения, что старую форму больше никто не использует. Недостаточно поверить сообщению «все команды обновились»: полезнее наблюдать реальные вызовы, версии приложений и обращения к устаревшим полям.
После этого можно удалить временную синхронизацию, старые поля и код совместимости. Если последний шаг постоянно откладывать, система накапливает два формата и все их комбинации, а временная миграция превращается в постоянную стоимость.
Где нужна простота
Для локального изменения, которое обслуживает одна команда и один одновременно обновляемый процесс, многоэтапная схема может быть избыточна. Но при независимых релизах и критичных данных цена одного большого переключения часто выше временной сложности.
Expand–Contract не делает миграцию бесплатной. Он позволяет оплатить управляемый переход вместо рискованного дня, когда все потребители обязаны измениться одновременно.
Практический пример изменения колонки разобран в руководстве Prisma по Expand–Contract.