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

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

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

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

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

Итак, какие перед нами стоят задачи?

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

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

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

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

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

Вы также увидите, почему мы собираемся использовать GraphQL в качестве основной спецификации API. Несмотря на то, что OpenAPI Specification имеет больше принятия, мы увидим, почему GraphQL будет доминировать на рынке интеграции в ближайшие годы.

Вы, вероятно, читали о "преимуществах" GraphQL над REST. Большинство этих блог-постов просто пытаются поймать волну хайпа. В этом блог-посте я представлю вам реальное преимущество, а не обычную моду на недостаточную или избыточную выборку, мы также не будем "генерировать" API сегодня, хотя это дает вам много дофамина в первые 5 минут (и много стресса, когда вам приходится добавлять пользовательскую бизнес-логику).

Я надеюсь, что "энтузиасты REST" все еще на борту. Вы узнаете что-то крутое сегодня, я обещаю.

API без версий

Концепцию, которую я объясняю сегодня, я называю API без версий. Без версий не означает, что версий нет. API без версий подразумевается так же, как и Serverless.

Serverless не означает "нет серверов". Serverless означает, что вам не нужно иметь дело с серверами.

Без версий означает, что вам не нужно иметь дело с версиями.

Заблуждения о версионировании GraphQL и REST API

Я уже говорил о версионировании, но с удовольствием повторюсь.

Когда вы читаете о преимуществах GraphQL над REST API, вы довольно часто слышите, что GraphQL лучше, потому что вам не "нужно версионировать ваш API".

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

GraphQL просто не предлагает решения для версионирования, хотя это не совсем верно. Вы могли бы добавить новое поле и дать ему суффикс версии, затем пометить устаршим старое с помощью директивы @deprecated.

Вот пример, Версия 1:

type Query {
  hello: String
}

Версия 2:

type Query {
  hello: String @deprecated(reason: "please use helloV2 instead")
  helloV2(arg: String!): String
}

В чем разница между приведенным выше примером и добавлением нового эндпоинта к вашему REST API, с тегом версии в URL, как параметр запроса или, возможно, заголовок?

Для обоих REST и GraphQL вам придется либо поддерживать две реализации, одну для hello и одну для helloV2.

Также есть проект IETF от Эрика Вильде Deprecation HTTP Header Field, который делает в сущности то же самое, что и директива @deprecated. Еще один проект, опять же от Эрика Вильде, Sunset HTTP Header, который помогает разработчикам понять, когда API выходит из строя. Похоже, Эрик заботится о жизненном цикле API. Спасибо, Эрик!

С учетом всего этого, есть ли действительно какая-либо разница между REST и GraphQL, когда речь идет о версионировании? Если вы не хотите версионировать ваши API, вы могли бы просто не нарушать их.

Кроме того, у вас также могло бы быть несколько версий вашего GraphQL API. Кто сказал, что example.com/graphql/v2 не подходит? Это может быть сложно поддерживать, потому что мало инструментов поддерживают этот сценарий использования, но это могло бы быть возможно, хотя я не думаю, что это хорошая идея.

Чтобы закончить этот экскурс о заблуждениях, я хотел бы сделать замечание, что я не считаю GraphQL сам по себе "без версий". Я обсужу позже, что именно подразумевается под "без версий".

Сначала давайте поговорим о том, почему GraphQL - такой замечательный язык для интеграции API.

Почему GraphQL собирается захватить рынок интеграции API

Это раздел, которого вы, вероятно, ждали. Я очень рад поделиться с вами этой концепцией сегодня.

Хорошо, в чем же GraphQL на самом деле лучше, по сравнению с REST. На самом деле, это не только GraphQL. GraphQL недостаточно, речь идет о Федерации.

Федерация позволяет вам расширять типы другого API GraphQL. Другая функция, которая нам поможет, это Интерфейсы, редко используемые, но чрезвычайно мощные.

Давайте посмотрим на пример. Представьте, что у нас есть две компании в нашей вселенной, первая предоставляет API для получения широты и долготы по заданному адресу, вторая предлагает API для получения текущей погоды для пары Широта-Долгота.

Как бы мог выглядеть наш вселенский API?

Сначала давайте посмотрим на компанию Geocoder. Что мы могли бы сделать, чтобы сделать ее максимально простой для принятия?

Вместо того, чтобы заставлять компанию вступать в зависимость от поставщика, могли бы мы разработать абстрактный API? Да, абсолютно!

interface IGeoCoder {
  geoCode(address: String!): ILatLng
}
interface ILatLng {
  latitude: Float
  longitude: Float
}

Эта абстрактная спецификация GeoCoder может находиться в репозитории git, например, github.com/graphql-schemas/geocoder, но это просто деталь реализации. Давайте пока оставим это на высоком уровне.

Хорошо, как компания GeoCoder может реализовать этот абстрактный GeoCoder?

type Query implements IGeoCoder {
  geoCode(address: String!): LatLng
}
type LatLng implements ILatLng @key(fields: "latitude longitude") {
  latitude: Float
  longitude: Float
}
interface IGeoCoder @specifiedBy(git: "github.com/graphql-schemas/geocoder") {
  geoCode(address: String!): ILatLng
}
interface ILatLng @specifiedBy(git: "github.com/graphql-schemas/geocoder") {
  latitude: Float
  longitude: Float
}

С этой схемой компания GeoCoder сделала свой API соответствующим официальному стандарту GeoCoder.

Примечание для людей, не так знакомых со спецификацией Федерации. Директива @key(fields: "latitude longitude") определяет, что LatLng становится сущностью согласно спецификации Федерации. Это означает, что любой другой сервис может найти объект LatLng, используя поля latitude и longitude.

В чем преимущество этого?

Это не только то, что мы решили проблему зависимости от поставщика. Мы также сделали очень легким принятие API компанией. Как человек, который хочет решить проблему через API, ищите открытый стандарт, например, Open Banking, FHIR, или более простые, как GeoCoder выше, ищите компании, которые реализуют спецификацию, и интегрируйтесь с ними.

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

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

Хорошо, мы закончили с GeoCoder. Если вам понравилась анти-зависимость от поставщика и открытый рынок для API, вы будете удивлены, что будет дальше, потому что это следующее - это настоящее сотрудничество API.

Давайте поговорим о поставщике API погоды. Как они могут убедиться, что получат максимальное внимание? Как они могут быть совместимы с как можно большим количеством других API?

Вот черновик того, как мог бы выглядеть "контракт" API погоды:

interface IWeatherApi extends ILatLng
    @specifiedBy(git: "github.com/graphql-schemas/weather-api")
    @key(fields: "latitude longitude") {
        latitude: Float @external
        longitude: Float @external
        weatherInfo: IWeatherInfo
}
interface IWeatherInfo @specifiedBy(git: "github.com/graphql-schemas/weather-api") {
    temperature: ITemperature!
    summary: String!
}
interface ITemperature @specifiedBy(git: "github.com/graphql-schemas/weather-api") {
    Celsius: Float
    Farenheit: Float
}
interface ILatLng @specifiedBy(git: "github.com/graphql-schemas/geocoder") {
    latitude: Float
    longitude: Float
}

Допустим, мы храним эту спецификацию для простого API погоды в репозитории git: github.com/graphql-schemas/weather-api

Теперь поставщик WeatherAPI может реализовать следующую схему:

type LatLng implements IWeatherApi @key(fields: "latitude longitude") {
    latitude: Float @external
    longitude: Float @external
    weatherInfo: WeatherInfo
}
type WeatherInfo implements IWeatherInfo {
    temperature: Temperature!
    summary: String!
}
type Temperature implements ITemperature {
    Celsius: Float
    Farenheit: Float
}
interface IWeatherApi extends ILatLng
    @specifiedBy(git: "github.com/graphql-schemas/weather-api")
    @key(fields: "latitude longitude") {
        latitude: Float @external
        longitude: Float @external
        weatherInfo: IWeatherInfo
}
interface IWeatherInfo @specifiedBy(git: "github.com/graphql-schemas/weather-api") {
    temperature: ITemperature!
    summary: String!
}
interface ITemperature @specifiedBy(git: "github.com/graphql-schemas/weather-api") {
    Celsius: Float
    Farenheit: Float
}
interface ILatLng @specifiedBy(git: "github.com/graphql-schemas/geocoder") {
    latitude: Float
    longitude: Float
}

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

interface IWeatherApi extends ILatLng
    @specifiedBy(git: "github.com/graphql-schemas/weather-api")
    @key(fields: "latitude longitude") {
        latitude: Float @external
        longitude: Float @external
        weatherInfo: IWeatherInfo
}

Мы определяем новый контракт, IWeatherApi, который, как и все другие контракты, является просто абстрактным определением и, следовательно, интерфейсом. Этот интерфейс расширяет интерфейс ILatLng, который, как мы видим ниже, определен спецификацией в вымышленном репозитории git (github.com/graphql-schemas/weather-api). Директива @key(fields: "latitude longitude") определяет два внешних ключа для интерфейса ILatLng: latitude, longitude. Кроме того, директивы @external помечают эти два поля как внешние, то есть они исходят из внешнего сервиса. Поле weatherInfo не имеет прикрепленной директивы, что означает, что наш собственный сервис будет его предоставлять.

interface ILatLng @specifiedBy(git: "github.com/graphql-schemas/geocoder") {
  latitude: Float
  longitude: Float
}

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

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

type LatLng implements IWeatherApi @key(fields: "latitude longitude") {
  latitude: Float @external
  longitude: Float @external
  weatherInfo: WeatherInfo
}

Наконец, мы реализуем контракт IWeatherApi с помощью неабстрактного, конкретного определения типа.

До сих пор это должно иметь хотя бы некоторый смысл с технической точки зрения. Но что все это значит с бизнес-точки зрения?

Оба провайдера, GeoCoder Api и WeatherApi, реализуют открытые стандарты, мы уже затрагивали тему противодействия блокировке поставщиков. Но Weather API - это особый случай, потому что он не реализует тип Query. Вместо этого он расширяет интерфейс ILatLng, указанный в другом открытом стандарте.

Построение связей между открытыми стандартами спецификаций API - это будущее экономики API.

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

API Mesh - создание связей между стандартизированными API, определенными с использованием открытых стандартов

Представьте себе мир, который не просто "API first", мир, где мы не просто рассматриваем API как продукты. Представьте себе мир, где мы стандартизируем конкретные сценарии использования, такие как GeoCoding, перевод денег, отправка SMS, и определяем их как открытые стандарты.

Представьте себе мир, где мы не просто определяем эти открытые стандарты, но также добавляем связи между ними, сеть API или API mesh.

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

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

Безверсионные API - Почему обратно совместимые API так важны

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

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

Так что, я думаю, можно с уверенностью сказать, что без 100% гарантий обратной совместимости невозможно воплотить это в реальность.

Как добавить нарушающие изменения в ваш GraphQL API без нарушения клиентов

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

Если вы прочитали несколько других сообщений в этом блоге, например, этот очень популярный пост о безопасности GraphQL, вы, вероятно, знакомы с концепцией того, как WunderGraph использует JSON-RPC перед виртуальным GraphQL API.

Для тех, кто еще не знаком с концепцией, вот краткое изложение.

WunderGraph берет все ваши REST- и GraphQL API, а также сгенерированные API из вашей базы данных и объединяет их в одну единую схему GraphQL. Эта схема GraphQL никогда не открывается для публики, поэтому я называю ее "виртуальной схемой" или "виртуальным API". Вместо того, чтобы напрямую предоставлять GraphQL API, мы используем подход, который используют такие компании, как Facebook, Twitter и Co., с одной небольшой корректировкой, мы превратили их индивидуальные решения в готовый к использованию продукт.

Во время разработки разработчики определяют операции GraphQL, которые они хотели бы использовать в своем приложении. Эти операции будут скомпилированы в нечто похожее на "подготовленные выражения", по сути, удаляя GraphQL из времени выполнения и заменяя его на JSON-RPC.

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

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

Кроме того, мы можем извлечь определения JSON-схемы для каждой "сохраненной" операции, позволяя как серверу, так и клиенту легко проверять пользовательские вводы.

Но есть еще один фантастический побочный эффект этой архитектуры фасада JSON-RPC GraphQL, который очень пригодится, когда речь идет о создании API без версий.
Возвращаясь к простому примеру из начала:

type Query {
  hello: String
}

Если клиент использовал бы этот API, он, вероятно, выглядел бы так. Клиент создаст конечную точку RPC, которая хранит запрос с полем hello, ожидая ответа, выглядящего так (в формате JSON Schema):

{
  "type": "object",
  "properties": {
    "data": {
      "type": "object",
      "properties": {
        "hello": {
          "type": "string"
        },
        "additionalProperties": false
      }
    }
  },
  "additionalProperties": false,
  "required": ["data"]
}

Вот сохраненный запрос:

{
  hello
}

Помните, этот клиент и вся сеть API зависят от этого API. Теперь давайте внесем нарушающее изменение. Мы переименуем поле hello в helloV2, без устаревания, просто переименуем и развернем.

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

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

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

Так что, каждый раз, когда вы намерены нарушить API, мы предотвратим нарушение клиентов, автоматически останавливая развертывание. Затем мы позволим вам написать скрипт "миграции", чтобы мигрировать старых клиентов на новую схему, чтобы снова сделать их совместимыми.
Как бы выглядела миграция в нашем сценарии?

Во-первых, вместо запроса поля hello, мы должны переписать запрос, чтобы использовать поле helloV2. Это, очевидно, все еще нарушит клиента, потому что мы теперь больше не соответствуем JSON-схеме. Так что на втором шаге нам придется переименовать поле data.helloV2 в data.hello. В качестве альтернативы мы также могли бы переписать запрос с псевдонимом:

{
  hello: helloV2
}

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

Затем вы можете посмотреть свою аналитику и решить, сколько старых версий клиентов вы хотели бы поддерживать.

Что это значит для провайдера API с бизнес-точки зрения?

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

Что это значит для разработчиков?

У них есть простой инструмент для миграции старых клиентов. Благодаря аналитике, они могут с уверенностью выпускать обновления, зная, что не сломают ни одного клиента. Это будет настоящим прорывом для тех, кто должен поддерживать мобильные клиенты. Мобильные приложения не будут немедленно загружать и устанавливать ваше обновленное приложение. Возможно, вам придется поддерживать старые версии вашего API в течение нескольких месяцев или даже лет. С этим подходом одно большое препятствие устранено. Вы можете использовать все преимущества GraphQL, отделив клиента (которым вы не можете напрямую управлять) от схемы GraphQL.

Вы даже можете полностью заменить схему, сохраняя совместимость со всеми клиентами, переводя их.

Хотите перейти с FaunaDB на dgraph или наоборот? Мы вас поддерживаем!

Что это значит для API Mesh в целом?

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

Без версионных API сеть API действительно невозможна.

Альтернативные решения для обеспечения обратной совместимости вашего GraphQL API

Я хотел бы выделить одно открытое решение, которое пытается решить ту же проблему с другим подходом, библиотека называется graphql-query-rewriter и делает именно то, что предполагает название, это совместимое с NodeJS промежуточное ПО, которое позволяет вам переписывать запросы GraphQL.

Не иронично ли, что некоторые люди в сообществе GraphQL утверждают, что отсутствие "функций версионирования" в спецификации GraphQL является функцией, в то время как почти 400 звезд для этой библиотеки указывают на необходимость версионирования?

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

  • FieldArgTypeRewriter

  • FieldArgNameRewriter

  • FieldArgsToInputTypeRewriter

  • ScalarFieldToObjectFieldRewriter

  • JsonToTypedObjectRewriter

  • NestFieldOutputsRewriter

Он работает так, что проверяет AST операции GraphQL, чтобы найти соответствующие правила переписывания, и применяет их.

Как мы видим из списка выше, есть довольно много вариантов на выбор, но всегда будут случаи, когда переписывание может быть невозможно.

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

Вот простой пример того, как настроить переписыватель:

app.use(
  '/graphql',
  graphqlRewriterMiddleware({
    rewriters: [
      new FieldArgTypeRewriter({
        fieldName: 'userById',
        argName: 'id',
        oldType: 'String!',
        newType: 'ID!',
      }),
    ],
  })
)

Что мне нравится в этой библиотеке:

Если вы уже используете сервер Node-JS GraphQL, это решение может довольно далеко зайти без больших усилий. Конфигурация правил кажется прямолинейной.

Несколько вещей, о которых стоит подумать:

Мне кажется, что правила переписывания не полностью типобезопасны. Типовые литералы, такие как String! (Non-Nullable String), рассматриваются как обычные строки. Я думаю, вам придется добавить дополнительные тесты, чтобы убедиться, что все переписывания верны.
Также нет специального тега версии или чего-то подобного. Это означает, что библиотека относится ко всем клиентам API одинаково. Я думаю, было бы полезно отслеживать всех клиентов и их версии, но это, похоже, выходит за рамки библиотеки. У меня есть некоторый страх, что со временем это может стать довольно запутанным, если вы не знаете, какие клиенты используют какую версию схемы, если между каждой версией нет четкого разделения. То есть, если вы удаляете одно из переписываний, совершенно непредсказуемо, какие клиенты будут затронуты.

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

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

Однако моя главная критика касается самой стратегии переписывания и связана с отсутствием тегов версий у клиентов. Представьте, что есть поле foo в типе Query. В нашей второй итерации мы добавляем новое поле под названием bar и удаляем поле foo. Чтобы не нарушить работу ни одного клиента, мы добавляем правило переписывания из foo в bar. Позже мы решаем добавить новое поле под названием foo (снова), но с совершенно другим значением. Повторное добавление этого поля на самом деле невозможно, потому что мы можем добавлять нарушающие изменения только в одном направлении. Без временной метки или тега версии в клиенте мы не можем отличить старых клиентов, которые хотели старое поле foo (переписанное в bar), от новых клиентов, которые на самом деле хотят новое поле foo без переписываний.

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

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

Итог и выводы

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

Затем мы сравнили различия версионирования между REST и GraphQL API. Я надеюсь, я ясно показал, что на самом деле большой разницы нет.

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

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

Мы также рассмотрели альтернативный подход и обсудили плюсы и минусы обоих решений.

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