傑克達與瑞兒絲的大小事

快速筆記整理網站開發上碰到的各種技巧和問題,免得年紀大忘光,至少還有一個地方可以查找

如何在 Rails 實作 Multitenant Application 架構

最近在專案開發中必須使用到 Multitenant Application 的概念來實作

由於第一次接觸,還是快速筆記免得之後又忘記

什麼是 Multitenant Application

在使用 Multitenant Application 前,還是必須先搞清楚什麼是 Multitenant Application 才能正確理解各項操作的意義
在這邊引用了 阿舍的隨手記記、隨手寫寫… – 什麼是 Multitenant Application (Multitenancy) 這篇文章的解釋

「如果你有一個應用系統(程式)用來服務不同的客戶,而這些客戶用的都是同一台主機,同一個資料庫,同一個應用系統(程式),只有透過程式設計的方式來讓不同的客戶存取不同的資料,那麼就可以說這是一個 Multitenant Application 」

另外這篇 Multi-Tenant Data Architecture 分享 (多租戶的資料設計架構) part1 文章裡有更明確的架構說明

在這次專案裡,只實作最簡單的 “Shared Database, Shared Schemas" 架構,共用一個資料庫,同一個主機

Rails 如何實作

在 Gemfile 載入 multitenant,相關的 github 說明可看這裡

gem 'multitenant'

完成設定後,執行安裝

$ bundle install

安裝成功後,請在 /config/environments/development.rb|production.rb|test.rb 三個環境設定下加入 config.domain = “#{你的網域名稱}",如:

config.domain = 'localhost'

由於需要服務不同的客戶,所以會將每個客戶的帳號做為 subdomain(子網域名稱) 建立對應的 URL 路徑
最後應用程式便可透過 subdomain 取得客戶身份,要做到這件事,便需要從 routing 這邊下手
修改 routes.rb,並使用 :constraints (特殊限定) 設定檢查

定義 constraints 所需要使用的檢查條件類別,建立於 lib 目錄下,命名為 sub_domain.rb

# /lib/sub_domain.rb
class SubDomain
def self.matches?( requrest )
# 取得 environments 環境參數定義的 domain 設定值
# 用來比對目前執行的 host 的 domain 是否為可被允許執行的網站名單
domain = TestProject::Application.config.domain
case requrest.host
# 以下幾個網站都是預設被使用的 domain
when "www.#{domain}", "#{domain}", nil
false
else
true
end
end
end

註: 請確定 /config/application.rb 設定中有自動載入 lib 目錄檔案,設定方式可於 /config/application.rb 加入 config.autoload_paths += %W(#{config.root}/lib)


在 routes.rb 建立限定檢查,檢查目前的 request host 的 subdomain name,是否為不被允許的 subdomain name,這邊定義的是 www 或是 空白,則一律導回至 www 首頁

# routes.rb
# 檢查目前傳入的 request host 是否為不被允許的 subdomain name
# 允許的話則可繼續使用服務,否則統一導向 www 首頁
constraints( SubDomain ) do
root :to => 'index#index'
end
# 導回 www 首頁
root :to => 'index#destroy'

IndexController 請定義 index 和 destroy 二個 action 處理導向狀況

class IndexController < ApplicationController
def index
end

# 負責處理請求網址的客戶不在的情況
# 一律導向至產品網站首頁
#----------------------------------------------------
def destroy
redirect_to "http://www.testproject.com.tw"
end
end

當完成 Multitenant 路徑設定後,接下來是要取得 User 的資料,判斷目前傳入的路徑是否有對應的 User 資料存在

先建立 migration 針對 User model 加入一些必要的欄位用來保存註冊的網域資訊,

$ rails g migration add_domain_fields_to_users.rb

內容如下:

add_column :users, :subdomain_name, :string, :null => false, :default => ""
add_column :users, :cache_domain, :string, :null => false, :default => ""
add_index :users, :subdomain_name, :unique => true

執行資料庫欄位建立

$ rake db:migrate

完成 subdomain 資料欄位新增後,即可將會員註冊的 subdomain 資料保存至資料庫,留待接下來的驗證使用
接著修改 application_controller.rb,加入驗證邏輯,如下:

# application_controller.rb

class ApplicationController < ActionController::Base
protect_from_forgery

before_filter :check_user_domain_exists!

protected
# 透過 subdomain 取得對應的 user 資料
#----------------------------------------------------
def check_user_domain_exists!
# 將目前的 request host 拿來查詢是否有其會員註冊使用,取得該會員
user ||= User.select('id').find_by_cache_domain( request.host )

# 如果會員為空,則一律導向至 www 網站首頁
if( user.blank? )
redirect_to "http://www.testproject.com.tw"
end
end
end

到這為止,基本的 Multitenant 驗證檢查都已經完成,接下來就可以透過 multitenant gem 發功
舉個簡單的例子,一位會員可以擁有很多本書,1對多的關係,所以我們會有 User 和 Book 二個 model,在 db 的結構裡,book table 會記錄 user_id,以前的做法就會是利用 user_id 關聯找出所有的 book 資料,multitenant 也是做類似的事,只是它幫我們包裝的更好,更容易使用

Book Model 結構設定如下:

# models/book.rb
class Book < ActiveRecord::Base
# 設定 Book 和 User 之間的關聯
belongs_to :user
# 設定 multitenant 關聯
belongs_to_multitenant :user
end

之後我們要取得目前使用者擁有的所有書本,使用方式是將目前登入使用者 (current_user) 做為參數傳入 Multitenant.with_tenant 函式中,表示縮限在這個 User 範圍下處理相關操作,包含新增、修改、刪除

Multitenant.with_tenant current_user do
# queries within this block are automatically
# scoped to the current tenant
Book.all

# new objects created within this block are automatically
# assigned to the current tenant
Book.create :name => 'Harry Potter and the Deathly Hallows (Book 7) '
end

後記

以上是本次專案執行的 multitenant 設定流程,因應各種不同的狀況,可能會有不同的處理方式,要留意!!
如果有任何意見指教,也歡迎指正小弟

參考文章:

說明:
http://www.arthurtoday.com/2010/02/multi-tenant-application-multitenancy.html
http://kevingo75.blogspot.com/2011/05/multi-tenant-data-architecture-part1.html

技術:
https://github.com/wireframe/multitenant
http://ihower.tw/rails3/routing.html
http://intridea.com/posts/use-lambdas-for-rails3-route-constraints

廣告

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com Logo

您的留言將使用 WordPress.com 帳號。 登出 / 變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 / 變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 / 變更 )

Google+ photo

您的留言將使用 Google+ 帳號。 登出 / 變更 )

連結到 %s

%d 位部落客按了讚: