REST API Versioning
Bài đăng này đã không được cập nhật trong 3 năm
Trong bài viết này tôi sẽ trình bày cách để thêm một phiên bản RESTful API. Phiên bản có thể được xác định trong mỗi URL hoặc HTTP headers.
API cơ bản
Chúng ta lấy ví dụ với một ứng dụng quản lý sản phẩm. Nó có thể nhìn tất cả thông tin của sản phẩm, thêm mới, chỉnh sửa hoặc xóa từng sản phẩm.
Ta viết một ProductsController theo RESTful và thêm respond_to cho mỗi action để phản hồi JSON requests:
/app/controllers/products_controller.rb
def index
@products = Product.all
respond_to do |format|
format.html
format.json { render json: @products }
end
end
Khi ta truy cập /products.json
sẽ nhận được list sản phẩm dưới dạng JSON như sau:
Như vậy là chúng ta đã có 1 JSON API cơ bản. Tuy nhiên chúng ta sẽ gặp vấn đề khi muốn muốn thay đổi cột released_on
bằng released_at
. Khi đó ứng dụng sử dụng bản API hiện tại sẽ bị lỗi vì không có trường released_at
.
Vì vậy ta cần các phiên bản khác nhau cho API để giải quyết các vấn đề như vậy.
Các bước chuẩn bị
Routes hiện tại:
/config/routes.rb
Store::Application.routes.draw do
resources :products
root to: 'products#index'
end
Chúng ta sẽ sử dụng namespace api
. Các phiển bản api sẽ được viết sau /api
.
/config/routes.rb
Store::Application.routes.draw do
namespace :api do
namespace :v1 do
resources :products
end
end
resources :products
root to: 'products#index'
end
Trong thư mục /app/controllers
ta cần tạo thêm 1 thư mục api
và thư mục con v1
. Ta sẽ để ProductsController
trong thư mục này:
/app/controllers/api/v1/products_controller.rb
module Api
module V1
class ProductsController < ApplicationController
end
end
end
Bây giờ chúng ta có thể thêm đầy đủ các action trong controller này:
/app/controllers/api/v1/products_controller.rb
module Api
module V1
class ProductsController < ApplicationController
respond_to :json
def index
respond_with Product.all
end
def show
respond_with Product.find(params[:id])
end
def create
respond_with Product.create(params[:product])
end
def update
respond_with Product.update(params[:id], params[:products])
end
def destroy
respond_with Product.destroy(params[:id])
end
end
end
end
Ở đây chúng ta đã xác định mỗi action sẽ gọi hàm respond_with
để nó trả về dạng JSON.
Bây giờ chúng ta có thể lấy danh sách các sản phầm bằng cách truy cập: /api/v1/products.json
Để bỏ phần .json
ở cuối URL chúng ta có thể tùy chỉnh JSON là dạng mặc định
/config/routes.rb
Store::Application.routes.draw do
namespace :api, defaults: {format: 'json'} do
namespace :v1 do
resources :products
end
end
resources :products
root to: 'products#index'
end
Khi đó ta chỉ cần vào http://localhost:3000/api/v1/products
Tạo phiên bản mới
Như đã nói ở trên, giả sử ta muốn đổi cột released_on
thành released_at
.
terminal
$ rails g migration change_products_released_on
/config/db/migrations/201205230000_change_products_released_on.rb
class ChangeProductsReleasedOn < ActiveRecord::Migration
def up
rename_column :products, :released_on, :released_at
change_column :products, :released_at, :datetime
end
def down
change_column :products, :released_at, :date
rename_column :products, :released_at, :released_on
end
end
Chạy rake db:migrate
để thay đổi cơ sở dữ liệu.
Khi đó nếu ta sử dụng released_on
ở đâu đó trong ứng dụng thì với db mới này ứng dụng sẽ bị lỗi vì released_on
đã được thay bằng released_at
.
Có 1 cách nhanh chóng để sửa lỗi này:
/app/controllers/api/v1/products_controller.rb
module Api
module V1
class ProductsController < ApplicationController
class Product < ::Product
def as_json(options={})
super.merge(released_on: released_at.to_date)
end
end
respond_to :json
# Actions omitted
end
end
end
Ở đây ta vẫn trả về key released_on
nhưng giá trị bên trong lại được lấy từ released_at
.
Tuy nhiên với cách sửa này, ta ko thể sử dụng trường released_at
mà vẫn phải sử dụng trường cũ là released_on
.
Để giải quyết cả 2 vấn đề này ta sẽ tạo thêm 1 phiên bản API mới.
Đầu tiên ta sao chép toàn bộ code trong thư mục app/controllers/api/v1
sang thư mục mới v2
.
terminal
$ cp -R app/controllers/api/v1 app/controllers/api/v2
Trong file routes ta thêm namespace v2
giống như v1
/config/routes.rb
Store::Application.routes.draw do
namespace :api, defaults: {format: 'json'} do
namespace :v1 do
resources :products
end
namespace :v2 do
resources :products
end
end
resources :products
root to: 'products#index'
end
Trong controller mới ta cũng chuyển v1
thành v2
/app/controllers/api/v2/products_controller.rb
module Api
module V2
class ProductsController < ApplicationController
respond_to :json
def index
respond_with Product.all
end
def show
respond_with Product.find(params[:id])
end
def create
respond_with Product.create(params[:product])
end
def update
respond_with Product.update(params[:id], params[:products])
end
def destroy
respond_with Product.destroy(params[:id])
end
end
end
end
Bây giờ khi sử dụng phiên bản mới ta sẽ có trường released_at
Với 2 phiên bản API như hiện tại, mới những người dùng sử dụng ứng dụng cũ sẽ dùng API v1 với trường released_on
, còn người dùng sử dụng ứng dụng mới sẽ sử dụng API v2 với trường released_at
.
Nguồn
All rights reserved