Подтвердить что ты не робот

Правильный путь к версии Rails 3 API

У меня есть движок Rails 3, который предоставляет маршруты API для примерно 20 контроллеров. Эти контроллеры представляют несколько различных ресурсов на разных уровнях гнездования и покрываются более чем 500 rspec-тестами. API версируется в версии v1 с использованием пространств имен и ограничения маршрутизации на основе заголовка версии с по умолчанию до v1. Это система управления версиями, описанная в большом количестве сообщений в блогах и, по-видимому, является лучшей практикой.

Что ни одна из этих сообщений в блогах не описывает, как вы фактически управляете развертыванием новой версии. Я должен внести изменения в выход одного контроллера. Это изменение влияет на JSON-ответ объекта, изменяя структуру одного из значений JSON. Это вызовет перерывы в индексе, покажет и отредактирует представления для этого контроллера.

Очевидно, что я могу скопировать все app/api/v1 в app/api/v2 [1]. Затем я могу сделать свое единственное изменение в моем новом сериализаторе v2. У меня теперь есть много дублированного кода для версии 2 API, которая практически не меняла никаких изменений. Мне нужно поддерживать код в двух местах. Вероятно, мне придется использовать весь комплект rspec на контроллерах версии 2, а также на версии 1 с небольшим количеством дополнительных тестов для сериализатора v2. Это звучит как ужасная идея. Мы могли бы иметь несколько контроллеров stub v2 для каждого неизмененного контроллера в пространстве имен v1, которое наследуется от контроллера v1. Это тоже не очень приятно.

Лучший вариант, о котором я могу думать, - это иметь один контроллер (в данном случае, вероятно, только один сериализатор) внутри моего API v2, с некоторой магией маршрутизации, чтобы проверить, существует ли контроллер для требуемой версии и отступить через предыдущие версии, пока не найдет его. Версия сериализатора также должна иметь аналогичную магию, чтобы проверить, существует ли она для этой версии и отпадает, пока она ее не найдет. Это вводит минимальный дополнительный код и не мгновенно удваивает продолжительность моего набора тестов. Это потребовало бы возможности подключить функцию непосредственно к логике маршрутизации рельсов, прежде чем она сможет вернуть 404 для моих отсутствующих контроллеров v2. Возможно, я мог бы анализировать пространства имен для всех контроллеров на основе файловой системы и генерировать маршруты во время загрузки rails с помощью резервных копий, но было бы трудно управлять явным удалением маршрутов из предыдущей версии API.

Кажется, что нам нужно будет продолжать делать это для каждого неаддитивного изменения функционального/выходного формата вплоть до того, что каждая предыдущая версия устарела и удалена. У нас есть дополнительный неизданный API, состоящий из ~ 75 контроллеров, охваченных спецификациями ~ 4000. Что происходит, когда мы начинаем задокументировать и освободить их?

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

[1] Проблема первая. Мы используем ActiveModel:: Сериализаторы для создания ответов JSON. ActiveModel:: Сериализаторы не поддерживает API-версию версии, хотя, кажется, существует способ обхода этого, используя ruby волшебство, чтобы выбрать правильный класс.

4b9b3361

Ответ 1

Проект ActiveModel:: Serializers имеет номер issues, связанный с версией, одним из них идея, как реализовать управление версиями через модули Namespace, но она была закрыта 2 дня назад, а затем один из слов разработчика:

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

Таким образом, проблема с версией AMS существует, но еще не решена.

Вернуться к исходному вопросу:

Очевидно, что я могу скопировать все app/api/v1 в app/api/v2. Я могу затем сделайте мое единственное изменение в моем новом сериализаторе v2. У меня теперь есть большое количество дублированного кода для версии 2 API, которая имеет почти не изменились. Мне нужно сохранить код в двух местах.

Существует компромисс между сложностью наследования с дублированием VS-кода. В случае наличия хорошо протестированной кодовой базы V1, которая должна быть заблокирована для любой модификации, обслуживание означает отсутствие ошибок при запуске набора тестов регрессии. Закончен цикл разработки версии 1, тесты написаны, контрактный контракт подписан. Дублирование кода V1-V2 имеет смысл, и оно позволяет избежать сбоев регрессии.

Мне, вероятно, придется запустить весь пакет rspec на версии 2 контроллеров, а также версии 1 с небольшим количеством дополнительных тестирование для сериализатора v2. Это звучит как ужасная идея.

Я не согласен с тем, что это ужасная идея, это компромисс между ожидаемым поведением и воображаемым удобством с развитием. Также непросто избежать дублирования spec-пакета. Контроллеры, модели могут быть повторно использованы, но спецификация codebase будет более вероятно дублироваться, чтобы быть на 100% уверенным, что новые изменения не нарушают предыдущую версию API.

Лучший вариант, о котором я могу думать, - это иметь один контроллер (в этот случай, вероятно, только один сериализатор) внутри моего API v2, с некоторые магии маршрутизации, чтобы проверить, если контроллер для требуемой версии существует и вернуться к предыдущим версиям, пока не найдет их.

Да, это звучит неплохо и помогает избежать дублирования кода приложения (хотя и не с набором спецификаций), но требует дополнительных усилий по разработке с обслуживанием. То, что вы пытаетесь сделать, называется copy-on-write, только изменения копируются. Это хорошо известный метод оптимизации. Тем не менее, HTTP-ответ кажется более подходящим.

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

Представьте, что у вас есть более двух версий API, и определенный вызов API имеет 2 резервных предка, где вторая ошибка разработчика, вы перехватите не только 404, но и 500 исключений? Что, если последняя версия схемы БД нарушает обратную совместимость?

У нас есть дополнительный неизданный API, состоящий из ~ 75 контроллеров покрытых ~ 4000 спецификаций. Что происходит, когда мы начинаем извне документирование и освобождение этих?

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

Что я рекомендую делать:

  • полностью дублировать V1-V2, включен пакет rspec
  • не бойтесь тратить 2 раза на выполнение тестов
  • подождите, пока AMS выпустит версию (release v0.10.x)
  • разделяет монолитный API на индивидуальные

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

location /v1 {
  proxy_pass http://http://unix:/tmp/v1_backend.socket:/v1/;
}

location /v2 {
  proxy_pass http://http://unix:/tmp/v2_backend.socket:/v2/;
}

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

Обратно к исходному вопросу, API-управление версиями затруднено, и для некоторых API-клиентов имеет смысл иметь конечную точку API-адреса по умолчанию (последний).

Ответ 2

Если бы я правильно понял ваши все требования, не было бы это достаточным решением для маршрутизации запросов v2:

  • Проверьте существование ресурса в разделе v2.
  • Если не найден, убедитесь, что он не является одним из отключенных ресурсов. Если это так, верните 404.
  • Возврат для ресурса v1, если он найден.

Здесь примерный код (одна область для каждого шага в списке выше)

scope constraints: lambda { |request| request.url.split('api/')[1].split('/')[0] == 'v2' } do
  # New resources introduced in v2
end

# Resource was not found in v2 API, check if it is removed
scope constraints: lambda { |request| request.url.split('api/')[1].split('/')[0] == 'v2' } do
  # Resources removed from v2
  resources :resource1, to: proc { [404, {}, ['']] }
end

# Fallback for v2 routes that don't have v2 controller defined
scope constraints: lambda { |request| ['v1', 'v2'].include?(request.url.split('api/')[1].split('/')[0]) } do
  # Original v1 resources
end

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