Versi API untuk Rute Rel

141

Saya mencoba versi API saya seperti Stripe miliki. Di bawah ini diberikan versi API terbaru adalah 2.

/api/users mengembalikan 301 ke /api/v2/users

/api/v1/users mengembalikan 200 indeks pengguna di versi 1

/api/v3/users mengembalikan 301 ke /api/v2/users

/api/asdf/users mengembalikan 301 ke /api/v2/users

Sehingga pada dasarnya segala sesuatu yang tidak menentukan tautan versi ke yang terbaru kecuali jika ada versi yang ditentukan kemudian mengarahkannya.

Inilah yang saya miliki sejauh ini:

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
Maletor
sumber

Jawaban:

280

Bentuk asli dari jawaban ini sangat berbeda, dan dapat ditemukan di sini . Buktinya ada lebih dari satu cara menguliti kucing.

Saya telah memperbarui jawaban sejak menggunakan ruang nama dan menggunakan pengalihan 301 - daripada default 302. Terima kasih kepada pixeltrix dan Bo Jeanes untuk dorongan pada hal-hal itu.


Anda mungkin ingin mengenakan helm yang benar - benar kuat karena ini akan membuat Anda berpikir .

API perutean Rails 3 sangat jahat. Untuk menulis rute untuk API Anda, sesuai kebutuhan Anda di atas, Anda hanya perlu ini:

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

Jika pikiran Anda masih utuh setelah titik ini, izinkan saya menjelaskannya.

Pertama, kami memanggil namespaceyang sangat berguna ketika Anda ingin banyak rute dicakup ke jalur dan modul tertentu yang diberi nama serupa. Dalam hal ini, kami ingin semua rute di dalam blok untuk kami namespacescoping ke controller di dalam Apimodul dan semua permintaan untuk jalur di dalam rute ini akan diawali dengan api. Permintaan seperti /api/v2/users, Anda tahu?

Di dalam namespace, kami mendefinisikan dua ruang nama lagi (woah!). Kali ini kita mendefinisikan "v1" namespace, sehingga semua rute untuk kontroler sini akan berada di dalam V1modul di dalam Apimodul: Api::V1. Dengan mendefinisikan resources :usersdi dalam rute ini, pengontrol akan berlokasi di Api::V1::UsersController. Ini adalah versi 1, dan Anda sampai di sana dengan membuat permintaan seperti /api/v1/users.

Versi 2 hanya kecil sedikit berbeda. Alih-alih controller yang menyajikannya berada Api::V1::UsersController, sekarang di Api::V2::UsersController. Anda sampai di sana dengan membuat permintaan seperti /api/v2/users.

Selanjutnya, a matchdigunakan. Ini akan cocok dengan semua rute API yang menuju ke hal-hal seperti /api/v3/users.

Ini adalah bagian yang harus saya cari. The :to =>pilihan memungkinkan Anda untuk menentukan bahwa permintaan khusus harus diarahkan di tempat lain - saya tahu bahwa banyak - tapi aku tidak tahu bagaimana untuk mendapatkannya untuk mengarahkan ke tempat lain dan lulus dalam sepotong permintaan asli bersama dengan itu .

Untuk melakukan ini, kita memanggil redirectmetode dan meneruskannya string dengan %{path}parameter interpolasi khusus . Ketika permintaan masuk yang cocok dengan matchtugas akhir ini , itu akan menginterpolasi pathparameter ke lokasi %{path}di dalam string dan mengarahkan pengguna ke tempat yang mereka tuju.

Akhirnya, kami menggunakan yang lain matchuntuk merutekan semua jalur yang tersisa yang diawali /apidan mengarahkannya ke /api/v2/%{path}. Ini berarti permintaan seperti /api/usersakan dituju /api/v2/users.

Saya tidak tahu bagaimana cara /api/asdf/usersmencocokkan, karena bagaimana Anda menentukan apakah itu seharusnya menjadi permintaan /api/<resource>/<identifier>atau /api/<version>/<resource>?

Bagaimanapun, ini menyenangkan untuk diteliti dan saya harap ini membantu Anda!

Ryan Bigg
sumber
24
Ryan Bigg sayang. Kamu brilian.
maletor
18
Seseorang tidak hanya mengukur reputasi Pahlawan Ruby.
Waseem
1
Ryan ... Saya rasa ini tidak akurat. Ini akan membuat / api dan / api / v2 menyajikan konten yang sama alih-alih memiliki satu URL kanonik. / api harus dialihkan ke / api / v2 (seperti yang ditentukan penulis asli). Saya berharap rute yang benar terlihat seperti gist.github.com/2044335 (memang, saya belum menguji itu). Hanya / api / v [12] yang harus mengembalikan 200, / api dan / api / <versi buruk> harus mengembalikan 301s ke / api / v2
Bo Jeanes
2
Perlu dicatat bahwa dalam file rute 301 telah dibuat pengalihan default dan untuk alasan yang baik. Dari panduan: 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
Tidakkah itu membuat pengalihan tanpa batas jika jalurnya tidak benar? Misalnya, meminta / api / v3 / path_that_dont_match_the_routes akan membuat pengalihan tanpa batas, bukan?
Robin
38

Beberapa hal untuk ditambahkan:

Pencocokan pengalihan Anda tidak akan berfungsi untuk rute tertentu - *apiparam itu serakah dan akan menelan segalanya, misalnya /api/asdf/users/1akan dialihkan ke /api/v2/1. Anda akan lebih baik menggunakan param seperti biasa :api. Memang itu tidak akan cocok dengan kasus seperti /api/asdf/asdf/users/1tetapi jika Anda memiliki sumber daya bersarang di api Anda itu solusi yang lebih baik.

Ryan MENGAPA TIDAK SAYA SUKA namespace? :-), mis:

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

Yang memiliki manfaat tambahan dari rute yang diberi nama dan generik. Satu catatan tambahan - konvensi saat menggunakan :moduleadalah menggunakan notasi garis bawah, mis .: api/v1not 'Api :: V1'. Pada satu titik yang terakhir tidak berfungsi tetapi saya percaya itu diperbaiki di Rails 3.1.

Juga, ketika Anda merilis v3 API Anda, rute akan diperbarui seperti ini:

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

Tentu saja kemungkinan API Anda memiliki rute yang berbeda antar versi dalam hal ini Anda dapat melakukan ini:

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
pixeltrix
sumber
Bagaimana Anda menangani kasus terakhir? yaitu /api/asdf/users?juga /api/users/1? Saya tidak bisa mengetahuinya dalam jawaban saya yang diperbarui, jadi saya tahu Anda mungkin tahu cara
Ryan Bigg
Tidak ada cara mudah untuk melakukannya - Anda harus mendefinisikan semua arahan ulang sebelum menangkap semua tetapi Anda hanya perlu melakukan masing-masing untuk setiap sumber daya induk, misalnya / api / pengguna / * path => / api / v2 / pengguna /% {path}
pixeltrix
13

Jika memungkinkan, saya akan menyarankan untuk memikirkan kembali url Anda sehingga versinya tidak ada di url, tetapi dimasukkan ke dalam header accepts. Jawaban stack overflow ini masuk dengan baik:

Praktik terbaik untuk versi API?

dan tautan ini menunjukkan bagaimana melakukannya dengan routing rel:

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

David Bock
sumber
Ini adalah cara yang sangat baik untuk melakukannya juga, dan mungkin akan memenuhi permintaan "/ api / asdf / users" juga.
Ryan Bigg
9

Saya bukan penggemar versi dengan rute. Kami membuat VersionCake untuk mendukung bentuk versi API yang lebih mudah.

Dengan memasukkan nomor versi API dalam nama file dari masing-masing pandangan kita masing-masing (jbuilder, RABL, dll), kami menjaga agar versi tersebut tidak mengganggu dan memungkinkan degradasi yang mudah untuk mendukung kompatibilitas mundur (mis. Jika v5 tampilan tidak ada, kami render v4 dari view).

aantix
sumber
8

Saya tidak yakin mengapa Anda ingin mengalihkan ke versi tertentu jika suatu versi tidak diminta secara eksplisit. Sepertinya Anda hanya ingin mendefinisikan versi default yang dilayani jika tidak ada versi yang diminta secara eksplisit. Saya juga setuju dengan David Bock bahwa menjaga versi dari struktur URL adalah cara yang lebih bersih untuk mendukung versi.

Steker tak tahu malu: Versionist mendukung kasing ini (dan banyak lagi)

https://github.com/bploetz/versionist

Brian Ploetz
sumber
2

Jawaban Ryan Bigg bekerja untuk saya.

Jika Anda juga ingin menyimpan parameter kueri melalui pengalihan, Anda dapat melakukannya seperti ini:

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

Diterapkan ini hari ini dan menemukan apa yang saya yakini sebagai 'cara yang benar' di RailsCasts - REST API Versioning . Sangat sederhana. Sangat terpelihara. Sangat efektif.

Tambahkan lib/api_constraints.rb(bahkan tidak perlu mengubah 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

Setup config/routes.rbseperti itu

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'

Edit controller Anda (yaitu /controllers/api/v1/squads_controller.rb)

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

Kemudian, Anda dapat mengubah semua tautan di aplikasi dari /api/v1/squadsmenjadi /api/squadsdan Anda dapat MUDAH menerapkan versi api baru tanpa harus mengubah tautan

weteamsteve
sumber