АналитикаСистемный анализСистемный аналитик

В модели заказа статус хранится свободной строкой. Какой механизм на уровне БД не позволит сохранить значен...

В модели заказа статус хранится свободной строкой. Какой механизм на уровне БД не позволит сохранить значение вне согласованного набора?

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

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

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

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

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

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

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

Свободная строка допускает значения с опечатками, разным регистром и различной семантикой: например, paid, оплачен и payd. Из-за этого фильтры, отчёты, интеграции и переходы между состояниями начинают трактовать один и тот же статус по-разному.

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

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

Для фиксированного набора значений задают ограничение CHECK. База принимает строку только тогда, когда она входит в явно определённый набор; условие проверяется при вставке и изменении записи.

CREATE TABLE orders ( id BIGINT PRIMARY KEY, status VARCHAR(20) NOT NULL, CONSTRAINT order_status_allowed CHECK (status IN ('new', 'paid', 'cancelled')) );

Приложение всё равно должно валидировать статус заранее, чтобы вернуть клиенту понятную ошибку. Однако приложение отвечает за удобство и контракт, а ограничение БД — за окончательную защиту целостности.

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

Ограничение допустимых значений не регулирует порядок переходов. CHECK может разрешить и new, и paid, но сам по себе не доказывает, что переход cancelled в paid допустим. Правила переходов реализуют в транзакционной бизнес-операции, отдельной модели состояний или другом явно выбранном механизме.

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

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

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

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

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

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

  1. Гарантирует ли CHECK корректный жизненный цикл заказа?

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

  1. Когда вместо CHECK нужна справочная таблица?

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

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

  1. Почему нельзя полностью полагаться на прикладную валидацию, если все записи сейчас делает один сервис?

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

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