Перейти к содержанию

Развитие схемы без обрыва клиентов

API Академии уже имеет чтение, mutation, права и ограничение структуры документа. Теперь добавим новое поле displayTitle, которое объединяет название и понятную подпись уровня. Старый title останется доступным, но получит deprecated. Задача — развить договор без немедленного обрыва прежних клиентов. Изменение схемы нельзя оценивать только по тому, компилируется ли сервер: действительные документы интерфейсов тоже являются частью совместимости.

Предметное поле title в исходных данных сохраняется. DisplayTitle вычисляется в resolver и не переписывает название хранилища. Для курса c01 ожидается «Статический сайт · начальный». Это представление для карточки, а не новый внутренний ключ. Позже локализация могла бы сделать такое поле зависимым от контекста языка; сейчас русский текст зафиксирован выбранным интерфейсом курса и не объявляется универсальным мультиязычным договором.

Добавление рядом со старым полем

В Course появляется displayTitle: String!. Поле title получает директиву deprecated с причиной использования нового варианта. Клиент, который продолжает запрашивать title, всё ещё получает прежнее название. Директива не выключает resolver и не превращает старый документ в ошибку. Это сигнал для перехода, который помогает инструментам и разработчикам заметить новое предпочтение без насильственного изменения каждого клиента в один момент.

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

type Course { id: ID! title: String! @deprecated(reason:"Use displayTitle; title remains available") level: Level! author: Author displayTitle: String! }

Этот блок показывает изменённый тип Course из schema.graphql состояния lesson-11. Полный файл сохраняет Query, Mutation, Enrollment и connection. Значение displayTitle вычисляется в fields по типу Course и имени поля. Остальные поля продолжают использовать прежний механизм. Новая удобная подпись не должна случайно отключить связь автора или изменить id, на который опираются курсоры.

Семантическая совместимость

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

Смена типа тоже важна. Замена String на enum или nullable на non-null влияет на проверку и распространение ошибок. Иногда расширение допустимых выходных значений выглядит совместимым для сервера, но клиент с закрытым переключателем не умеет показать новый enum. Схема не знает всех предположений интерфейса. Поэтому совместимость включает чтение документов и поведения клиентов, а не только автоматическое сравнение текстовых определений.

Файл operations.graphql сохраняет старые выборки для учебной истории. Этот набор известных намерений полезен при планировании перехода, но не является отчётом выполненных проверок: пользователь просил не запускать тесты. Мы не называем файлы подтверждением совместимости конкретных библиотек или машин. По исходникам можно проверить, что старые поля остаются в схеме и соответствующие resolvers продолжают возвращать прежние значения.

Deprecated и процедура перехода

Причина устаревания должна объяснять действие, а не только факт «старое поле». В нашем договоре она направляет к displayTitle и сообщает, что title пока остаётся доступным. Если есть срок удаления, его следует согласовать с продуктом и реальными потребителями. В учебном курсе такой срок не назначаем, потому что публичной платформы с зарегистрированными клиентами нет. Придуманная дата создала бы ложное обязательство без основания.

Для управляемого API полезно знать, какие операции ещё используют поле. Это можно получить наблюдением именованных документов или реестром известных запросов, учитывая политику приватности. Простое число HTTP-запросов к одному endpoint не показывает, какое поле нужно клиенту. У GraphQL единый адрес, но множество проекций. Поэтому сопровождение схемы требует более предметного понимания используемого договора, чем список URL доступа.

Удаление title сразу после добавления displayTitle обрывает прежние документы. Они получат ошибку проверки до выполнения, даже если resolver мог бы вычислить похожее значение. Нельзя решить такой отказ только перенаправлением HTTP-адреса: путь сервера остаётся тем же, изменился язык допустимого документа. Совместимый переход сохраняет оба поля, обновляет клиентов и лишь затем рассматривает удаление на подтверждённом основании.

Клиентская проекция нового поля

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

Рассмотрим одновременный запрос title и displayTitle. Ожидается исходное название в первом и дополненная подпись во втором. Такой документ помогает увидеть, что новое поле не переписало данные. Неизвестный автор c03 по-прежнему влияет только на связь author, а не на displayTitle. Выбор проекции позволяет получить полезный список даже при недоступной метаинформации, сохраняя причины частичного ответа из предыдущих глав.

Можно также мысленно добавить обязательное поле в EnrollInput без значения по умолчанию. Старый клиент записи окажется несовместимым, хотя новое выходное поле не ломало его. Этот пример показывает, почему словосочетание «добавили поле» недостаточно для оценки изменения. Нужно назвать его позицию, обязательность, значения и влияющие документы. Такой язык полезен при обсуждении схемы между авторами клиента и серверной части.

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

Исходники урока

Оглавление серии · Скачать исходники. Полное самостоятельное состояние находится в lesson-11. Учебные приложения, зависимости, база и проверки при подготовке не запускались; описанный результат ожидается по исходникам. Материалы пока находятся в локальной редакционной версии.

Первичные источники: документация 1.