АрхитектураМикросервисы и интеграцииИнженер по интеграционной архитектуре

Сервис публикует наружу идентификаторы записей из своей базы, а после миграции хранилища старые ссылки пере...

Сервис публикует наружу идентификаторы записей из своей базы, а после миграции хранилища старые ссылки перестают работать. Какой принцип должен определить внешний идентификатор ресурса?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Внешний идентификатор должен быть стабильным публичным идентификатором, независимым от внутреннего первичного ключа и конкретной технологии хранения. Миграция базы, смена схемы или объединение таблиц не должны менять уже опубликованные ссылки на ресурс.

Исторический контекст

Внутренние идентификаторы удобны для работы базы данных, но интеграционный контракт живёт дольше конкретной реализации сервиса. По мере развития систем хранилища меняют, данные переносят между базами, а несколько внутренних сущностей могут объединяться или разделяться.

Поэтому возникло разделение между идентичностью ресурса в предметной области и его адресом или ключом внутри хранилища. Внешний контракт должен опираться на первую, а не на вторую.

Постановка проблемы

Если сервис публикует наружу автоинкрементный ключ таблицы или другой внутренний идентификатор, потребители начинают считать его частью контракта. После миграции такой ключ может измениться, повторно использоваться, стать недоступным или получить другой смысл.

Последствиями становятся неработающие ссылки, невозможность корректно сопоставить старые события с новыми данными и скрытая связанность потребителей со схемой базы. Простая замена идентификатора также опасна: потребители могут хранить его локально и не иметь способа автоматически восстановить соответствие.

Подробное решение

Сервис должен выбрать идентификатор, чья семантика принадлежит его публичной модели. Такой идентификатор создаётся при появлении ресурса, не переиспользуется для другого ресурса и сохраняется при изменении внутренних структур. Он может быть непрозрачным для потребителя, например случайно сгенерированным значением, но важна не форма, а стабильность и область уникальности.

Внешний идентификатор не обязан совпадать с первичным ключом базы. Сервис может хранить отдельное отображение между публичным идентификатором и внутренним ключом либо использовать публичный идентификатор внутри хранилища, если это не создаёт нежелательных ограничений.

Нужно заранее определить область уникальности, правила жизненного цикла и поведение после удаления ресурса. Идентификатор удалённого ресурса обычно не следует переиспользовать: иначе старые сообщения или ссылки могут ошибочно начать указывать на новый объект.

Важно не путать стабильность идентификатора с неизменностью самого ресурса. Ресурс может менять атрибуты, владельца или внутреннее представление, сохраняя тот же публичный идентификатор. Если ресурс действительно разделяется на несколько самостоятельных объектов, это уже изменение модели, и для новых объектов должны появиться новые идентификаторы с явным правилом совместимости.

Компромисс состоит в дополнительном хранении и необходимости мигрировать отображение идентификаторов. Непрозрачные идентификаторы также хуже подходят для сортировки и иногда увеличивают размер данных, но зато не раскрывают внутреннюю структуру и не привязывают контракт к реализации.

Ситуация из практики

Сервис заказов передавал партнёрам числовой ключ строки из своей базы. При переходе на распределённое хранилище записи были перенесены с другими ключами, поэтому ссылки из писем и внешней системы перестали находить заказы.

Рассматривались три варианта. Сохранить старые ключи было быстро, но это продолжало связывать контракт с базой и усложняло будущие миграции. Добавить таблицу перенаправлений решало текущую проблему, но требовало постоянного обслуживания и не устраняло ошибочную модель для новых заказов. Перевести контракт на отдельный стабильный идентификатор с временным отображением старых ключей потребовало миграции потребителей, зато разорвало зависимость от хранилища.

Выбрали третий вариант: новые ссылки и события стали использовать стабильный идентификатор заказа, а старые ключи некоторое время разрешались через таблицу совместимости. После переходного периода таблицу оставили только для исторических ссылок и аудита. В результате последующие изменения схемы базы не требовали менять внешний контракт.

Что кандидаты часто упускают

  1. Достаточно ли сделать внутренний идентификатор глобально уникальным?

Нет. Глобальная уникальность не делает идентификатор подходящим для внешнего контракта. Он может оставаться связанным с конкретной таблицей, раскладкой данных или жизненным циклом записи. Нужно гарантировать также стабильность, отсутствие переиспользования и понятную ответственность сервиса за этот идентификатор.

  1. Можно ли кодировать в публичном идентификаторе дату, тип или регион ресурса?

Технически можно, если эти свойства являются устойчивой частью публичной модели. Но это создаёт риск: потребители начнут извлекать смысл из формата, а последующее изменение правил генерации станет несовместимым. Если потребителю не нужна такая семантика, безопаснее использовать непрозрачный идентификатор и передавать отдельные явно определённые поля.

  1. Что делать при объединении двух ресурсов с разными идентификаторами?

Нельзя молча переиспользовать один из старых идентификаторов, если оба могли быть известны потребителям. Следует определить новую модель ресурса, выдать ему новый стабильный идентификатор и опубликовать явное соответствие или событие миграции. Старые идентификаторы должны либо продолжать разрешаться как прежние ресурсы, либо возвращать однозначный результат о закрытии или объединении, но не указывать случайно на другой объект.