REST API Versioning
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. 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 ...
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.
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.
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
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
http://railscasts.com/episodes/350-rest-api-versioning