АрхитектураПроектирование системАрхитектор серверных систем

Команда публикует внутреннюю модель хранения напрямую в публичном API. Какой главный системный риск это соз...

Команда публикует внутреннюю модель хранения напрямую в публичном API. Какой главный системный риск это создаёт?

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

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

Главный риск — жёсткая связанность публичного контракта с внутренней схемой хранения. Любое изменение таблиц, полей или способа представления данных начинает угрожать совместимости клиентов, поэтому развитие хранилища замедляется и требует координации с потребителями API.

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

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

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

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

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

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

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

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

При проектировании контракта нужно определить:

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

Разделение не означает, что для каждого endpoint обязательно нужна полностью независимая модель. Если внутренняя и внешняя структуры временно совпадают, их можно реализовать одинаково, но контракт всё равно должен считаться отдельным слоем. Иначе случайное изменение схемы хранения автоматически становится изменением API.

У такого подхода есть стоимость: требуется маппинг, дополнительные тесты контрактов и явное сопровождение преобразований. Однако эта стоимость оправдана, когда API имеет внешних потребителей, хранилище активно меняется или сервис должен развиваться независимо от клиентов.

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

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

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

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

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

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

  1. Достаточно ли назвать отдельный класс или DTO, чтобы устранить связанность?

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

  1. Можно ли безопасно добавлять новые поля в публичный ответ?

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

  1. Нужно ли версионировать API при каждом изменении внутренней схемы?

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