Wersjonowanie API dla tras Railsowych


141

Próbuję zmienić wersję mojego API tak, jak ma to Stripe. Poniżej podano najnowszą wersję API to 2.

/api/users zwraca 301 do /api/v2/users

/api/v1/users zwraca indeks 200 użytkowników w wersji 1

/api/v3/users zwraca 301 do /api/v2/users

/api/asdf/users zwraca 301 do /api/v2/users

Więc w zasadzie wszystko, co nie określa wersji, łączy się z najnowszą wersją, chyba że określona wersja istnieje, a następnie przekierowuje do niej.

Oto, co mam do tej pory:

scope 'api', :format => :json do
  scope 'v:api_version', :api_version => /[12]/ do
    resources :users
  end

  match '/*path', :to => redirect { |params| "/api/v2/#{params[:path]}" }
end

Odpowiedzi:


280

Oryginalna forma tej odpowiedzi jest zupełnie inna i można ją znaleźć tutaj . To tylko dowód, że istnieje więcej niż jeden sposób na oskórowanie kota.

Od tamtej pory zaktualizowałem odpowiedź, aby używać przestrzeni nazw i przekierowań 301 - zamiast domyślnego 302. Dzięki pixeltrix i Bo Jeanes za podpowiadanie tych rzeczy.


Możesz chcieć nosić naprawdę mocny kask, ponieważ to oszaleje .

Interfejs API routingu Rails 3 jest super zły. Aby napisać trasy dla swojego interfejsu API, zgodnie z powyższymi wymaganiami, potrzebujesz tylko tego:

namespace :api do
  namespace :v1 do
    resources :users
  end

  namespace :v2 do
    resources :users
  end
  match 'v:api/*path', :to => redirect("/api/v2/%{path}")
  match '*path', :to => redirect("/api/v2/%{path}")
end

Jeśli po tym momencie twój umysł jest nadal nienaruszony, pozwól mi wyjaśnić.

Po pierwsze, wywołujemy, namespaceco jest bardzo przydatne, gdy chcesz, aby grupa tras obejmowała określoną ścieżkę i moduł o podobnej nazwie. W tym przypadku chcemy, aby wszystkie trasy wewnątrz bloku namespacebyły ograniczone do kontrolerów w Apimodule, a wszystkie żądania do ścieżek wewnątrz tej trasy będą poprzedzone prefiksem api. Prośby takie jak /api/v2/users, wiesz?

Wewnątrz przestrzeni nazw definiujemy jeszcze dwie przestrzenie nazw (woah!). Tym razem mamy do definiowania nazw „v1”, więc tutaj wszystkie trasy dla kontrolerów będzie wewnątrz V1modułu wewnątrz Apimodułu: Api::V1. Definiując resources :userswewnątrz tej trasy, kontroler będzie zlokalizowany pod adresem Api::V1::UsersController. To jest wersja 1 i możesz się tam dostać, wykonując żądania, takie jak /api/v1/users.

Wersja 2 jest tylko maleńki nieco inna. Zamiast kontrolera obsługującego to jest Api::V1::UsersControllerteraz, jest teraz Api::V2::UsersController. Dostajesz się tam, składając prośby takie jak /api/v2/users.

Następnie matchużywany jest. Spowoduje to dopasowanie wszystkich tras API, które prowadzą do rzeczy takich jak /api/v3/users.

To jest ta część, na którą musiałem spojrzeć. :to =>Opcja pozwala na określenie, że wniosek powinien zostać przekierowany specyficzny gdzieś indziej - Wiedziałem, że dużo - ale nie wiem jak zrobić to przekierować do innego miejsca i przekazać w kawałku pierwotnego wniosku wraz z nim .

Aby to zrobić, wywołujemy redirectmetodę i przekazujemy jej ciąg znaków ze specjalnym %{path}parametrem interpolowanym . Kiedy przychodzi żądanie, które pasuje do tego końcowego match, interpoluje pathparametr do lokalizacji %{path}wewnątrz ciągu i przekierowuje użytkownika do miejsca, w którym musi się udać.

Na koniec używamy innego matchdo kierowania wszystkich pozostałych ścieżek z prefiksem /apii przekierowywania do nich /api/v2/%{path}. Oznacza to, że żądania takie jak /api/userstrafią do /api/v2/users.

Nie mogłem wymyślić, jak się /api/asdf/usersdopasować, ponieważ jak określisz, czy to ma być prośba o /api/<resource>/<identifier>czy /api/<version>/<resource>?

W każdym razie było to fajne badanie i mam nadzieję, że ci pomoże!


24
Drogi Ryanie Bigg. Jesteś wspaniały.
maletor

18
Nie mierzy się po prostu reputacji Rubinowego Bohatera.
Waseem

1
Ryan ... Nie sądzę, żeby to było dokładne. Spowodowałoby to, że / api i / api / v2 wyświetlałyby tę samą zawartość zamiast jednego kanonicznego adresu URL. / api powinien przekierowywać do / api / v2 (zgodnie z określeniem oryginalnego autora). Spodziewałbym się, że poprawne trasy będą wyglądać jak gist.github.com/2044335 ( oczywiście , nie testowałem tego). Tylko / api / v [12] powinno zwracać wartość 200, / api i / api / <zła wersja> powinny zwracać 301 do / api / v2
Bo Jeanes

2
Warto zauważyć, że w pliku tras 301 zostało ustawione domyślne przekierowanie i nie bez powodu. Od przewodników: Please note that this redirection is a 301 “Moved Permanently” redirect. Keep in mind that some web browsers or proxy servers will cache this type of redirect, making the old page inaccessible.
maletor

3
Czy nie tworzy nieskończonych przekierowań, jeśli ścieżka nie jest poprawna? Na przykład żądanie / api / v3 / path_that_dont_match_the_routes utworzy nieskończone przekierowanie, prawda?
Robin

38

Kilka rzeczy do dodania:

Twoje dopasowanie przekierowania nie zadziała na niektórych trasach - *apiparametr jest chciwy i pochłonie wszystko, np. /api/asdf/users/1Przekieruje do /api/v2/1. Lepiej byłoby użyć zwykłego parametru, takiego jak :api. Wprawdzie nie będzie pasować do przypadków takich jak, /api/asdf/asdf/users/1ale jeśli masz zagnieżdżone zasoby w swoim api, jest to lepsze rozwiązanie.

Ryan DLACZEGO NIE LUBISZ namespace? :-), np .:

current_api_routes = lambda do
  resources :users
end

namespace :api do
  scope :module => :v2, &current_api_routes
  namespace :v2, &current_api_routes
  namespace :v1, &current_api_routes
  match ":api/*path", :to => redirect("/api/v2/%{path}")
end

Co ma dodatkową zaletę w postaci wersjonowanych i ogólnych nazwanych tras. Dodatkowa uwaga - konwencja podczas używania :modulepolega na używaniu notacji podkreślenia, np .: api/v1nie „Api :: V1”. W pewnym momencie ta ostatnia nie działała, ale wydaje mi się, że została naprawiona w Railsach 3.1.

Ponadto, gdy wydasz wersję 3 swojego API, trasy zostaną zaktualizowane w następujący sposób:

current_api_routes = lambda do
  resources :users
end

namespace :api do
  scope :module => :v3, &current_api_routes
  namespace :v3, &current_api_routes
  namespace :v2, &current_api_routes
  namespace :v1, &current_api_routes
  match ":api/*path", :to => redirect("/api/v3/%{path}")
end

Oczywiście jest prawdopodobne, że Twój interfejs API ma różne trasy między wersjami, w takim przypadku możesz to zrobić:

current_api_routes = lambda do
  # Define latest API
end

namespace :api do
  scope :module => :v3, &current_api_routes
  namespace :v3, &current_api_routes

  namespace :v2 do
    # Define API v2 routes
  end

  namespace :v1 do
    # Define API v1 routes
  end

  match ":api/*path", :to => redirect("/api/v3/%{path}")
end

Jak poradzisz sobie z ostatnią sprawą? tj. /api/asdf/users?jak również /api/users/1? Nie mogłem tego rozgryźć w mojej zaktualizowanej odpowiedzi, więc pomyślałem, że możesz znać sposób
Ryan Bigg

Nie jest to łatwy sposób - musiałbyś zdefiniować wszystkie przekierowania przed przechwyceniem wszystkich, ale wystarczyłoby to zrobić dla każdego zasobu nadrzędnego, np. / Api / users / * path => / api / v2 / users /% {ścieżka}
pixeltrix

13

Jeśli to w ogóle możliwe, sugerowałbym przemyślenie swoich adresów URL, tak aby wersja nie była w adresie URL, ale została umieszczona w nagłówku accept. Ta odpowiedź na przepełnienie stosu pasuje do tego dobrze:

Najlepsze praktyki dotyczące wersjonowania API?

a ten link pokazuje dokładnie, jak to zrobić z routingiem rails:

http://freelancing-gods.com/posts/versioning_your_ap_is


Jest to również doskonały sposób na zrobienie tego i prawdopodobnie spełniłby również żądanie „/ api / asdf / users”.
Ryan Bigg

9

Nie jestem wielkim fanem wersjonowania trasami. Stworzyliśmy VersionCake, aby wspierać łatwiejszą formę wersjonowania API.

Umieszczając numer wersji API w nazwie pliku każdego z naszych odpowiednich widoków (jbuilder, RABL itp.), Utrzymujemy wersjonowanie dyskretne i pozwalamy na łatwą degradację w celu wsparcia kompatybilności wstecznej (np. Jeśli widok v5 nie istnieje, możemy render v4 widoku).


8

Nie jestem pewien, dlaczego chcesz przekierować do określonej wersji, jeśli wersja nie jest wyraźnie wymagana. Wygląda na to, że chcesz po prostu zdefiniować domyślną wersję, która będzie wyświetlana, jeśli żadna wersja nie zostanie wyraźnie zażądana. Zgadzam się również z Davidem Bockiem, że utrzymywanie wersji poza strukturą adresu URL jest czystszym sposobem obsługi wersjonowania.

Shameless plug: Versionist obsługuje te przypadki użycia (i nie tylko).

https://github.com/bploetz/versionist


2

Odpowiedź Ryana Bigga zadziałała dla mnie.

Jeśli chcesz również zachować parametry zapytania przez przekierowanie, możesz to zrobić w następujący sposób:

match "*path", to: redirect{ |params, request| "/api/v2/#{params[:path]}?#{request.query_string}" }

2

Zaimplementowałem to dzisiaj i znalazłem to, co uważam za „właściwą drogę” na RailsCasts - REST API Versioning . Tak prosty. Tak łatwe do utrzymania. Tak skuteczny.

Dodaj lib/api_constraints.rb(nie musisz nawet zmieniać vnd.example.)

class ApiConstraints
  def initialize(options)
    @version = options[:version]
    @default = options[:default]
  end

  def matches?(req)
    @default || req.headers['Accept'].include?("application/vnd.example.v#{@version}")
  end
end

Konfiguracja config/routes.rbtaka jak ta

require 'api_constraints'

Rails.application.routes.draw do

  # Squads API
  namespace :api do
    # ApiConstaints is a lib file to allow default API versions,
    # this will help prevent having to change link names from /api/v1/squads to /api/squads, better maintainability
    scope module: :v1, constraints: ApiConstraints.new(version:1, default: true) do
      resources :squads do
        # my stuff was here
      end
    end
  end

  resources :squads
  root to: 'site#index'

Edytuj swój kontroler (tj. /controllers/api/v1/squads_controller.rb)

module Api
  module V1
    class SquadsController < BaseController
      # my stuff was here
    end
  end
end

Następnie możesz zmienić wszystkie linki w swojej aplikacji z /api/v1/squadsna /api/squadsi ŁATWO wdrażać nowe wersje API bez konieczności zmiany linków

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.