it-swarm.com.de

API-Versionierung für Rails Routes

Ich versuche meine API wie Stripe zu versionieren. Unten ist die neueste API-Version 2 angegeben.

/api/users Gibt 301 an /api/v2/users Zurück

/api/v1/users Gibt einen Index von 200 Benutzern bei Version 1 zurück

/api/v3/users Gibt 301 an /api/v2/users Zurück

/api/asdf/users Gibt 301 an /api/v2/users Zurück

Damit im Grunde genommen alles, was die Version nicht angibt, auf die neueste Version verweist, es sei denn, die angegebene Version existiert, dann leiten Sie sie weiter.

Das habe ich bisher:

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
140
maletor

Die ursprüngliche Form dieser Antwort ist völlig anders und kann hier gefunden werden . Nur ein Beweis dafür, dass es mehr als einen Weg gibt, eine Katze zu häuten.

Ich habe die Antwort seitdem aktualisiert, um Namespaces zu verwenden und 301-Weiterleitungen zu verwenden - und nicht die Standardeinstellung von 302. Vielen Dank an Pixeltrix und Bo Jeanes für die Aufforderung zu diesen Dingen.


Vielleicht möchten Sie einen wirklich starken Helm tragen, weil dieser Sie umhauen wird .

Die Routing-API von Rails 3) ist unglaublich. Um die Routen für Ihre API gemäß Ihren obigen Anforderungen zu schreiben, benötigen Sie nur Folgendes:

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

Wenn Ihr Verstand nach diesem Punkt noch intakt ist, lassen Sie es mich erklären.

Zuerst rufen wir namespace auf, was sehr praktisch ist, wenn Sie eine Reihe von Routen für einen bestimmten Pfad und ein bestimmtes Modul mit ähnlichem Namen benötigen. In diesem Fall möchten wir, dass alle Routen innerhalb des Blocks für unser namespace auf Controller innerhalb des Moduls Api beschränkt werden und allen Anforderungen an Pfade innerhalb dieser Route api vorangestellt wird. ]. Anfragen wie /api/v2/users, weißt du?

Innerhalb des Namespaces definieren wir zwei weitere Namespaces (woah!). Dieses Mal definieren wir den Namespace "v1", sodass sich alle Routen für die Controller hier innerhalb des Moduls V1 innerhalb des Moduls Api befinden: Api::V1. Durch Definieren von resources :users innerhalb dieser Route befindet sich der Controller unter Api::V1::UsersController. Dies ist Version 1, und Sie gelangen dorthin, indem Sie Anfragen wie /api/v1/users stellen.

Version 2 ist nur ein winziges bisschen anders. Anstatt dass der Controller, der es bedient, bei Api::V1::UsersController ist, ist es jetzt bei Api::V2::UsersController. Sie gelangen dorthin, indem Sie Anfragen wie /api/v2/users stellen.

Als nächstes wird ein match verwendet. Dies stimmt mit allen API-Routen überein, die zu Dingen wie /api/v3/users führen.

Dies ist der Teil, den ich nachschlagen musste. Mit der Option :to => können Sie angeben, dass eine bestimmte Anforderung an eine andere Stelle umgeleitet werden soll - das wusste ich -, aber ich wusste nicht, wie ich sie dazu bringen kann, an eine andere Stelle umzuleiten und einen Teil der umzuleiten ursprüngliche Anfrage zusammen mit.

Dazu rufen wir die Methode redirect auf und übergeben ihr einen String mit einem speziell interpolierten %{path} -Parameter. Wenn eine Anforderung eingeht, die mit diesem endgültigen match übereinstimmt, interpoliert sie den Parameter path an die Position von %{path} in der Zeichenfolge und leitet den Benutzer dorthin weiter, wo er hingelangen muss.

Schließlich verwenden wir ein anderes match, um alle verbleibenden Pfade mit dem Präfix /api weiterzuleiten und sie zu /api/v2/%{path} umzuleiten. Dies bedeutet, dass Anfragen wie /api/users an /api/v2/users gehen.

Ich konnte nicht herausfinden, wie ich /api/asdf/users zum Matching bringe, denn wie stellst du fest, ob das eine Anfrage an /api/<resource>/<identifier> oder /api/<version>/<resource> sein soll?

Wie auch immer, die Recherche hat Spaß gemacht und ich hoffe, es hilft Ihnen!

275
Ryan Bigg

Ein paar Dinge zum Hinzufügen:

Ihre Weiterleitungsübereinstimmung funktioniert auf bestimmten Routen nicht - das *apiparam ist gierig und verschlingt alles, z. /api/asdf/users/1 leitet weiter zu /api/v2/1. Sie sind besser dran, wenn Sie einen regulären Parameter wie :api. Zugegeben, es passt nicht zu Fällen wie /api/asdf/asdf/users/1 Wenn Sie jedoch Ressourcen in Ihrer API verschachtelt haben, ist dies eine bessere Lösung.

Ryan WARUM MÖCHTEN SIE NICHT namespace? :-), z.B:

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

Welches den zusätzlichen Vorteil von versionierten und generisch benannten Routen hat. Eine zusätzliche Anmerkung - die Konvention bei der Verwendung von :module verwendet die Unterstreichungsnotation, z. B .: api/v1 nicht 'Api :: V1'. An einem Punkt funktionierte Letzteres nicht, aber ich glaube, es wurde behoben in Rails 3.1.

Wenn Sie Version 3 Ihrer API freigeben, werden die Routen folgendermaßen aktualisiert:

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

Natürlich ist es wahrscheinlich, dass Ihre API unterschiedliche Routen zwischen den Versionen hat. In diesem Fall können Sie dies tun:

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
37
pixeltrix

Wenn möglich, würde ich vorschlagen, Ihre URLs so zu überdenken, dass die Version nicht in der URL enthalten ist, sondern in den accept-Header eingefügt wird. Diese Stapelüberlauf-Antwort geht gut hinein:

Best Practices für die API-Versionierung?

und dieser Link zeigt genau, wie man das mit Rails routing macht:

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

13
David Bock

Ich bin kein großer Fan der Versionierung nach Routen. Wir haben VersionCake entwickelt, um eine einfachere Form der API-Versionierung zu unterstützen.

Indem wir die API-Versionsnummer in den Dateinamen der jeweiligen Ansichten (jbuilder, RABL usw.) aufnehmen, behalten wir die Versionsverwaltung unauffällig bei und ermöglichen eine leichte Beeinträchtigung, um die Abwärtskompatibilität zu unterstützen (z. B. wenn Version 5 der Ansicht nicht vorhanden ist) Render v4 der Ansicht).

9
aantix

Ich bin nicht sicher, warum Sie zu einer bestimmten Version umleiten möchten , wenn eine Version nicht ausdrücklich angefordert wird. Anscheinend möchten Sie einfach eine Standardversion definieren, die bereitgestellt wird, wenn keine explizite Version angefordert wird. Ich stimme auch David Bock zu, dass das Vermeiden von Versionen aus der URL-Struktur eine sauberere Möglichkeit darstellt, die Versionierung zu unterstützen.

Schamloser Plug: Versionist unterstützt diese Anwendungsfälle (und mehr).

https://github.com/bploetz/versionist

8
Brian Ploetz

Implementierte dies heute und fand, was ich glaube, den "richtigen Weg" auf RailsCasts - REST API Versioning . So einfach. So wartbar. So effektiv.

Fügen Sie lib/api_constraints.rb Hinzu (Sie müssen nicht einmal vnd.example ändern.)

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

Richten Sie config/routes.rb Wie folgt ein

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'

Bearbeite deinen Controller (zB /controllers/api/v1/squads_controller.rb)

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

Dann können Sie alle Links in Ihrer App von /api/v1/squads Auf /api/squads Ändern und Sie können [~ # ~] einfach [~ # ~] neue API-Versionen implementieren, ohne die Links ändern zu müssen

1
weteamsteve

Ryan Bigg Antwort hat bei mir funktioniert.

Wenn Sie die Abfrageparameter auch über die Umleitung beibehalten möchten, können Sie dies folgendermaßen tun:

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