การกำหนดเวอร์ชัน API สำหรับเส้นทาง Rails


141

ฉันกำลังลองรุ่น API ของฉันเหมือนมีลาย ด้านล่างได้รับ API เวอร์ชันล่าสุดคือ 2

/api/users ส่งคืน 301 ถึง /api/v2/users

/api/v1/users ส่งคืนดัชนีผู้ใช้ 200 รายการในเวอร์ชัน 1

/api/v3/users ส่งคืน 301 ถึง /api/v2/users

/api/asdf/users ส่งคืน 301 ถึง /api/v2/users

ดังนั้นโดยทั่วไปสิ่งใดก็ตามที่ไม่ได้ระบุลิงก์เวอร์ชันไปเป็นเวอร์ชันล่าสุดเว้นแต่ว่ามีเวอร์ชันที่ระบุอยู่แล้วจึงเปลี่ยนเส้นทางไปยังเวอร์ชันนั้น

นี่คือสิ่งที่ฉันมี:

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

คำตอบ:


280

รูปแบบเดิมของคำตอบนี้จะแตกต่างกันอย่างดุเดือดและสามารถพบได้ที่นี่ เพียงพิสูจน์ว่ามีมากกว่าหนึ่งวิธีในการดูแลแมว

ฉันได้อัปเดตคำตอบตั้งแต่การใช้เนมสเปซและใช้การเปลี่ยนเส้นทาง 301 - แทนที่จะเป็น 302 เริ่มต้นขอบคุณ pixeltrix และ Bo Jeanes สำหรับการแจ้งเตือนสิ่งเหล่านั้น


คุณอาจต้องการที่จะสวมใส่จริงๆหมวกกันน็อกที่แข็งแกร่งเพราะนี้จะพัดใจของคุณ

API การกำหนดเส้นทาง Rails 3 นั้นยอดเยี่ยมมาก หากต้องการเขียนเส้นทางสำหรับ API ของคุณตามที่คุณต้องการคุณต้องมีสิ่งนี้:

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

หากความคิดของคุณยังคงไม่เปลี่ยนแปลงหลังจากจุดนี้ให้ฉันอธิบาย

ก่อนอื่นเราเรียกnamespaceว่ามีประโยชน์มากเมื่อคุณต้องการกำหนดเส้นทางจำนวนมากไปยังเส้นทางและโมดูลเฉพาะที่มีชื่อคล้ายกัน ในกรณีนี้เราต้องการทุกเส้นทางภายในบล็อกสำหรับเราnamespaceที่จะกำหนดขอบเขตการควบคุมภายในโมดูลและการร้องขอทุกเส้นทางภายในเส้นทางนี้จะนำหน้าด้วยApi apiคำขอเช่น/api/v2/usersคุณรู้หรือไม่?

ภายในเนมสเปซเราจะกำหนดเนมสเปซสองรายการเพิ่มเติม (woah!) เวลานี้เรากำลังกำหนด "v1" namespace ดังนั้นทุกเส้นทางสำหรับตัวควบคุมที่นี่จะเป็นภายในV1โมดูลภายในโมดูล:Api Api::V1โดยกำหนดภายในเส้นทางนี้การควบคุมจะตั้งอยู่ที่resources :users Api::V1::UsersControllerนี้เป็นรุ่นที่ 1 /api/v1/usersและคุณได้รับโดยมีการส่งคำขอเช่น

รุ่นที่ 2 เป็นเพียงเล็ก ๆที่แตกต่างกันเล็กน้อย แทนการควบคุมที่ให้บริการก็เป็นที่ก็ตอนนี้ที่Api::V1::UsersController คุณจะได้รับโดยมีการส่งคำขอเช่นApi::V2::UsersController/api/v2/users

ถัดไปmatchใช้ นี้จะตรงกับเส้นทาง API /api/v3/usersทั้งหมดที่ไปที่สิ่งที่ต้องการ

นี่คือส่วนที่ฉันต้องค้นหา :to =>ตัวเลือกที่ช่วยให้คุณสามารถระบุได้ว่าคำขอเฉพาะควรจะเปลี่ยนเส้นทางไปที่อื่น - ฉันรู้ว่ามาก - แต่ผมไม่ทราบวิธีการที่จะได้รับมันจะเปลี่ยนเส้นทางไปที่อื่นและผ่านในส่วนของการร้องขอเดิมพร้อมกับมัน .

ในการทำเช่นนี้เราเรียกใช้redirectเมธอดและส่งผ่านสตริงด้วย%{path}พารามิเตอร์พิเศษ เมื่อคำขอมาตรงกับรอบสุดท้ายนี้matchจะทำการแทรกpathพารามิเตอร์เข้าไปในตำแหน่ง%{path}ภายในสตริงและเปลี่ยนเส้นทางผู้ใช้ไปยังตำแหน่งที่ต้องการ

สุดท้ายเราใช้อีกmatchเส้นทางเส้นทางที่เหลือทั้งหมดนำหน้าด้วยและเปลี่ยนเส้นทางไปยัง/api /api/v2/%{path}ที่นี้หมายถึงการร้องขอเช่นจะไป/api/users/api/v2/users

ฉันไม่สามารถคิดออกว่าจะได้รับ/api/asdf/usersเพื่อให้ตรงเพราะวิธีการที่คุณจะกำหนดว่าควรจะร้องขอไปยัง/api/<resource>/<identifier>หรือ/api/<version>/<resource>?

อย่างไรก็ตามมันสนุกกับการค้นคว้าและฉันหวังว่ามันจะช่วยคุณได้!


24
เรียนคุณ Ryan Bigg คุณยอดเยี่ยม
maletor

18
หนึ่งไม่เพียงวัดชื่อเสียงของ Ruby Hero
Waseem

1
ไรอัน ... ฉันไม่คิดว่ามันถูกต้องจริงๆ สิ่งนี้จะมี / api และ / api / v2 ให้บริการเนื้อหาเดียวกันแทนที่จะมี URL แบบบัญญัติเดียว / api ควรเปลี่ยนเส้นทางไปยัง / api / v2 (ตามที่ผู้เขียนต้นฉบับระบุไว้) ฉันคาดหวังว่าเส้นทางที่ถูกต้องจะดูเหมือนgist.github.com/2044335 (ให้สิทธิ์ฉันยังไม่ได้ทดสอบว่า) เฉพาะ / api / v [12] ควรส่งคืน 200, / api และ / api / <เวอร์ชันไม่ดี> ควรกลับ 301s เป็น / api / v2
Bo Jeanes

2
เป็นที่น่าสังเกตว่าในไฟล์เส้นทาง 301 ได้ทำการเปลี่ยนเส้นทางเริ่มต้นและด้วยเหตุผลที่ดี จากมัคคุเทศก์: 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
ไม่สร้างการเปลี่ยนเส้นทางแบบไม่สิ้นสุดหากเส้นทางไม่ถูกต้องหรือไม่ ตัวอย่างเช่นการร้องขอ / api / v3 / path_that_dont_match_the_routes จะสร้างการเปลี่ยนเส้นทางแบบไม่สิ้นสุดใช่ไหม?
Robin

38

สองสามสิ่งที่จะเพิ่ม:

การแข่งขันการเปลี่ยนเส้นทางของคุณจะไม่ทำงานสำหรับบางเส้นทาง - The *apiพระรามเป็นโลภและจะกลืนทุกอย่างเช่นจะเปลี่ยนเส้นทางไป/api/asdf/users/1 คุณต้องการจะดีกว่าโดยใช้พารามิเตอร์ปกติเช่น/api/v2/1 :apiเป็นที่ยอมรับว่าจะไม่ตรงกับกรณีเช่นนี้/api/asdf/asdf/users/1แต่ถ้าคุณมีทรัพยากรที่ซ้อนกันใน API ของคุณมันเป็นทางออกที่ดีกว่า

ไรอันทำไมคุณไม่ชอบnamespace? :-), เช่น:

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

ซึ่งมีประโยชน์เพิ่มเติมของเส้นทางที่มีชื่อรุ่นและทั่วไป หมายเหตุเพิ่มเติมหนึ่งข้อ - ข้อตกลงเมื่อใช้:moduleคือใช้เครื่องหมายขีดล่างเช่น: api/v1ไม่ใช่ 'Api :: V1' ณ จุดหนึ่งหลังไม่ทำงาน แต่ฉันเชื่อว่ามันได้รับการแก้ไขใน Rails 3.1

นอกจากนี้เมื่อคุณปล่อย v3 ของ API เส้นทางจะได้รับการอัปเดตดังนี้:

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

แน่นอนว่าเป็นไปได้ว่า API ของคุณมีเส้นทางที่แตกต่างกันระหว่างเวอร์ชันที่คุณสามารถทำได้:

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

คุณจะจัดการกับกรณีสุดท้ายอย่างไร คือ/api/asdf/users?เช่นเดียวกับ/api/users/1? ฉันไม่สามารถหาคำตอบที่ได้รับการอัปเดตได้ดังนั้นคิดว่าคุณอาจจะรู้วิธี
Ryan Bigg

ไม่มีวิธีง่ายๆในการดำเนินการ - คุณจะต้องกำหนดการเปลี่ยนเส้นทางทั้งหมดก่อนที่จะทำการตรวจจับทั้งหมด แต่คุณจะต้องดำเนินการแต่ละอย่างสำหรับแต่ละทรัพยากรหลักเช่น / api / users / * path => / api / v2 / users /% {path}
pixeltrix

13

หากเป็นไปได้ทั้งหมดฉันขอแนะนำให้คิด URL ของคุณใหม่เพื่อไม่ให้มี URL ในเวอร์ชัน แต่ใส่ไว้ในส่วนหัวของการยอมรับ คำตอบล้นสแต็คนี้เข้ากันได้ดี:

แนวทางปฏิบัติที่ดีที่สุดสำหรับการกำหนดเวอร์ชัน API หรือไม่

และลิงค์นี้แสดงให้เห็นว่าจะทำอย่างไรกับการกำหนดเส้นทางราง:

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


นี่เป็นวิธีที่ยอดเยี่ยมในการทำเช่นนี้และอาจตอบสนองคำขอ "/ api / asdf / users" เช่นกัน
Ryan Bigg

9

ฉันไม่ใช่แฟนตัวยงของการกำหนดเส้นทาง เราสร้างVersionCakeเพื่อรองรับรูปแบบการวางเวอร์ชัน API ที่ง่ายขึ้น

โดยการรวมหมายเลขรุ่น API ในชื่อไฟล์ของแต่ละมุมมองของเรา (jbuilder, RABL และอื่น ๆ ) เรายังคงเวอร์ชันที่ไม่เป็นการรบกวนและอนุญาตให้การย่อยสลายง่ายเพื่อรองรับความเข้ากันได้แบบย้อนหลัง (เช่นถ้า v5 ของมุมมองไม่มีอยู่จริง) แสดง v4 ของมุมมอง)


8

ฉันไม่แน่ใจว่าทำไมคุณต้องการเปลี่ยนเส้นทางไปยังรุ่นที่ระบุหากไม่มีการร้องขออย่างชัดเจน ดูเหมือนว่าคุณเพียงแค่ต้องการกำหนดรุ่นเริ่มต้นที่จะให้บริการหากไม่มีการขอรุ่นอย่างชัดเจน ฉันเห็นด้วยกับ David Bock ว่าการกำจัดเวอร์ชันออกจากโครงสร้าง URL เป็นวิธีที่สะอาดกว่าเพื่อรองรับการกำหนดเวอร์ชัน

ปลั๊กไร้ยางอาย: Versionist รองรับกรณีการใช้งานเหล่านี้ (และอื่น ๆ )

https://github.com/bploetz/versionist


2

คำตอบของไรอันบิ๊กทำงานสำหรับฉัน

หากคุณต้องการเก็บพารามิเตอร์การสืบค้นไว้ตลอดการเปลี่ยนเส้นทางคุณสามารถทำได้ดังนี้

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

2

การดำเนินการนี้ในวันนี้และพบว่าสิ่งที่ผมเชื่อว่าจะเป็น 'วิธีการที่เหมาะสม' ในRailsCasts - REST API รุ่น ง่ายมาก บำรุงรักษาได้ มีประสิทธิภาพมาก

เพิ่มlib/api_constraints.rb(ไม่ต้องเปลี่ยน 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

ตั้งค่าconfig/routes.rbเช่นนั้น

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'

แก้ไขคอนโทรลเลอร์ของคุณ (เช่น/controllers/api/v1/squads_controller.rb)

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

จากนั้นคุณสามารถเปลี่ยนการเชื่อมโยงทั้งหมดใน app ของคุณจาก/api/v1/squadsไป/api/squadsและคุณสามารถได้อย่างง่ายดายใช้รุ่นใหม่ API โดยไม่ต้องมีการเปลี่ยนแปลงการเชื่อมโยง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.