Controllo delle versioni API per Rails Route


141

Sto provando a versione la mia API come ha fatto Stripe. Di seguito viene fornita l'ultima versione dell'API è 2.

/api/users restituisce un 301 a /api/v2/users

/api/v1/users restituisce un indice 200 di utenti alla versione 1

/api/v3/users restituisce un 301 a /api/v2/users

/api/asdf/users restituisce un 301 a /api/v2/users

In modo che praticamente tutto ciò che non specifica la versione sia collegato all'ultima, a meno che non esista la versione specificata, reindirizza ad essa.

Questo è quello che ho finora:

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

Risposte:


280

La forma originale di questa risposta è molto diversa e può essere trovata qui . Prova solo che esiste più di un modo per scuoiare un gatto.

Da allora ho aggiornato la risposta per utilizzare gli spazi dei nomi e per utilizzare i reindirizzamenti 301, anziché il valore predefinito 302. Grazie a pixeltrix e Bo Jeanes per il suggerimento su queste cose.


Potresti voler indossare un casco davvero forte perché questo ti lascerà a bocca aperta .

L'API di routing di Rails 3 è super malvagia. Per scrivere i percorsi per l'API, in base ai requisiti sopra indicati, è necessario solo questo:

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

Se la tua mente è ancora intatta dopo questo punto, lasciami spiegare.

In primo luogo, chiamiamo ciò namespaceche è molto utile per quando si desidera un gruppo di route mirate a un percorso e un modulo specifici che hanno un nome simile. In questo caso, vogliamo che tutte le rotte all'interno del blocco namespacesiano indirizzate ai controller all'interno del Apimodulo e tutte le richieste ai percorsi all'interno di questa rotta avranno il prefisso api. Richieste come /api/v2/users, sai?

All'interno dello spazio dei nomi, definiamo altri due spazi dei nomi (woah!). Questa volta stiamo definendo lo spazio dei nomi "v1", in modo da tutte le rotte per i controller qui saranno all'interno del V1modulo all'interno del Apimodulo: Api::V1. Definendo resources :usersall'interno di questo percorso, il controller si troverà in Api::V1::UsersController. Questa è la versione 1 e ci si arriva facendo richieste come /api/v1/users.

La versione 2 è solo una piccola po 'diverso. Invece che il controller che lo sta servendo Api::V1::UsersController, è ora su Api::V2::UsersController. Ci si arriva facendo richieste come /api/v2/users.

Successivamente, matchviene utilizzato a. Questo corrisponderà a tutte le rotte API che vanno a cose del genere /api/v3/users.

Questa è la parte che ho dovuto cercare. L' :to =>opzione ti consente di specificare che una specifica richiesta dovrebbe essere reindirizzata da qualche altra parte - lo sapevo molto - ma non sapevo come farla reindirizzare da qualche altra parte e passare un pezzo della richiesta originale insieme ad essa .

Per fare ciò, chiamiamo il redirectmetodo e gli passiamo una stringa con un %{path}parametro interpolato speciale . Quando arriva una richiesta che corrisponde a questa finale match, interpolerà il pathparametro nella posizione %{path}all'interno della stringa e reindirizzerà l'utente dove deve andare.

Infine, ne usiamo un altro matchper instradare tutti i percorsi rimanenti con prefisso /apie reindirizzarli a /api/v2/%{path}. Questo significa che richieste come /api/usersandranno a /api/v2/users.

Non sono riuscito a capire come ottenere la /api/asdf/userscorrispondenza, perché come si determina se si suppone che sia una richiesta /api/<resource>/<identifier>o /api/<version>/<resource>?

Ad ogni modo, è stato divertente cercare e spero che ti aiuti!


24
Caro Ryan Bigg. Tu sei brillante.
Maletor

18
Non si misura semplicemente la reputazione di un Ruby Hero.
Waseem,

1
Ryan ... Non penso sia esattamente accurato. Ciò avrebbe / api e / api / v2 servire gli stessi contenuti invece di avere un singolo URL canonico. / api dovrebbe reindirizzare a / api / v2 (come specificato dall'autore originale). Mi aspetterei che i percorsi corretti assomiglino a qualcosa di simile a gist.github.com/2044335 (garantito, comunque non l'ho provato). Solo / api / v [12] dovrebbe restituire 200, / api e / api / <versione non valida> dovrebbe restituire 301s a / api / v2
Bo Jeanes

2
Vale la pena notare che nel file route 301 è stato impostato il reindirizzamento predefinito e per una buona ragione. Dalle guide: 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
Non crea reindirizzamenti infiniti se il percorso non è corretto? Ad esempio, richiedere / api / v3 / path_that_dont_match_the_routes creerà un reindirizzamento infinito, giusto?
Robin

38

Un paio di cose da aggiungere:

La tua partita di reindirizzamento non funzionerà per determinate rotte: il *apiparametro è avido e inghiottirà tutto, ad esempio /api/asdf/users/1reindirizzerà a /api/v2/1. Faresti meglio ad usare un normale parametro come :api. Certo, non corrisponderà a casi come quello, /api/asdf/asdf/users/1ma se hai risorse nidificate nella tua API è una soluzione migliore.

Ryan PERCHÉ NON TI PIACE namespace? :-), per esempio:

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

Che ha l'ulteriore vantaggio di route denominate generiche e con versione. Una nota aggiuntiva: la convenzione quando si usa :moduleè di usare la notazione di sottolineatura, ad esempio: api/v1non 'Api :: V1'. A un certo punto quest'ultimo non ha funzionato, ma credo che sia stato risolto in Rails 3.1.

Inoltre, quando rilasci la v3 della tua API le rotte verrebbero aggiornate in questo modo:

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

Naturalmente è probabile che la tua API abbia percorsi diversi tra le versioni, nel qual caso puoi farlo:

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

Come gestiresti il ​​caso finale? vale /api/asdf/users?a dire anche /api/users/1? Non sono riuscito a capirlo nella mia risposta aggiornata, quindi ho pensato che potresti conoscere un modo
Ryan Bigg

Non è un modo semplice per farlo: dovresti definire tutti i reindirizzamenti prima di catturare tutto, ma dovresti solo fare ciascuno per ogni risorsa principale, ad esempio / api / users / * path => / api / v2 / users /% {path}
pixeltrix

13

Se possibile, suggerirei di ripensare gli URL in modo che la versione non sia nell'URL, ma venga inserita nell'intestazione accetta. Questa risposta di overflow dello stack va bene:

Best practice per il versioning delle API?

e questo link mostra esattamente come farlo con il routing delle rotaie:

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


Questo è un modo eccellente di farlo anche, e probabilmente risponderebbe anche alla richiesta "/ api / asdf / users".
Ryan Bigg,

9

Non sono un grande fan del versioning per route. Abbiamo creato VersionCake per supportare una forma più semplice di versioning delle API.

Includendo il numero di versione dell'API nel nome file di ciascuna delle nostre rispettive viste (jbuilder, RABL, ecc.), Manteniamo discreto il controllo delle versioni e consentiamo un facile degrado per supportare la compatibilità con le versioni precedenti (ad es. Se la v5 della vista non esiste, noi render v4 della vista).


8

Non sono sicuro del motivo per cui si desidera reindirizzare a una versione specifica se una versione non è esplicitamente richiesta. Sembra che tu voglia semplicemente definire una versione predefinita che viene pubblicata se nessuna versione viene esplicitamente richiesta. Concordo anche con David Bock sul fatto che mantenere le versioni fuori dalla struttura dell'URL sia un modo più pulito per supportare il controllo delle versioni.

Spina senza vergogna: Versionist supporta questi casi d'uso (e altro).

https://github.com/bploetz/versionist


2

La risposta di Ryan Bigg ha funzionato per me.

Se vuoi anche mantenere i parametri della query attraverso il reindirizzamento, puoi farlo in questo modo:

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

2

Ho implementato questo oggi e ho trovato quello che ritengo essere il "modo giusto" su RailsCasts - REST API Versioning . Così semplice. Così mantenibile. Così efficace.

Aggiungi lib/api_constraints.rb(non è nemmeno necessario modificare 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

Installa config/routes.rbcosì

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'

Modifica il tuo controller (es. /controllers/api/v1/squads_controller.rb)

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

Quindi è possibile modificare tutti i link nella tua app da /api/v1/squadsa /api/squadse si può FACILMENTE implementare nuove versioni API senza nemmeno dover cambiare i collegamenti

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.