+2

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.

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

http://railscasts.com/episodes/350-rest-api-versioning


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí