Multiple database with subdomain use apartment gem

Làm thế nào để cùng một soures code duy nhất có thể chạy với nhiều subdomain khác nhau mà không làm lẫn dữ liệu giữa chúng? Có lẽ bài toán này đã quá quen thuộc với cộng đồng lập trình viên nói chung, có khá nhiều cách để làm việc này tuy nhiên hôm nay mình xin giới thiệu gem apartment sử dụng trong framework Ruby on Rails. Về cơ bản các bạn có thể hiểu gem apartment cung cấp cách cho nhiều databases có cấu trúc bảng giống nhau chạy trên cùng một ứng dụng. Mỗi một database sẽ được trỏ bởi một subdomain khác nhau và lưu dữ liệu khi người dùng truy nhập ứng dụng bằng subdmain nào sẽ được lưu vào database tương ứng. Do vậy không có khả năng bị lẫn dữ liệu giữa các subdomain khi truy vấn vì bản chất dữ liệu được lưu ở những database khác nhau.

Cài đặt

Cách cài đặt gem apartment cũng tương tự như những gem khác: Thêm dòng sau trong Gemfile

gem "apartment", ">= 1.2.0"

Chạy lệnh

bundle install

Tạo ra file config của gem bằng lệnh

bundle exec rails generate apartment:install

SLệnh trên sẽ tự động tạo file config/initializers/apartment.rb trong thư mục initializers có dạng:

require 'apartment/elevators/subdomain'
Apartment.configure do |config|
  #apartment config
end
Rails.application.config.middleware.use 'Apartment::Elevators::Subdomain'

Cách sử dụng

Chúng ta sẽ tìm hiểu cách sử dụng gem apartment thông qua một ví dụ cụ thể.

1. Tạo một bảng User để chứa thông tin subdomain

rails g scaffold User email subdomain

2. Thêm config sau trong file config/initializers/apartment.rb

config.excluded_models = %w{ User }
# Config này tương ứng với việc bảng User sẽ được public giữa tất các database nghĩa là dù có truy vấn bằng subdomain nào thì dữ liệu lấy ra đều giống nhau
config.tenant_names = lambda { User.pluck :subdomain }
# Config này nghĩa là sẽ sử dụng dữ liệu trong cột subdomain của bảng User để tạo các tenant DB

3. Thêm config sau trong application.rb

config.middleware.use Apartment::Elevators::Subdomain

Cho phép ứng dụng chạy với nhiều subdomain khác nhau. Note: Cần thêm require 'apartment/elevators/subdomain' vào application.rb trước khi thêm config trên.

4. Run

rake db:migrate

Cho kết quả như sau cùng với một số cảnh báo về việc sử dụng tenant DB.

framgia@framgia0216-lt:~/Jobs/viblo201705$ rake db:migrate
== 20170524141552 CreateUsers: migrating ======================================
-- create_table(:users)
   -> 0.0025s
== 20170524141552 CreateUsers: migrated (0.0026s) =============================

        [WARNING] - The list of tenants to migrate appears to be empty. This could mean a few things:

          1. You may not have created any, in which case you can ignore this message
          2. You've run `apartment:migrate` directly without loading the Rails environment
            * `apartment:migrate` is now deprecated. Tenants will automatically be migrated with `db:migrate`

        Note that your tenants currently haven't been migrated. You'll need to run `db:migrate` to rectify this.

5. Thêm đoạn code sau để tạo tự động database khi thêm một bản ghi vào bảng User

class User < ApplicationRecord
	after_create :create_tenant

	private
	def create_tenant
      Apartment::Tenant.create subdomain
	end

end

Đây là cách thức hoạt động khi thêm một bản ghi mới vào bảng User

Started POST "/users" for 127.0.0.1 at 2017-05-24 21:25:39 +0700
Processing by UsersController#create as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"dy92oqOX22ACMXFYNB5j2Ve/TL5ndqRL09Xvt1xE7yJz/Udyd1z0G2k1EhDesG/mVGUAvysIS6LzCZh7cLkEtg==", "user"=>{"email"=>"[email protected]", "subdomain"=>"subdomain"}, "commit"=>"Create User"}
   (0.3ms)  begin transaction
  SQL (0.4ms)  INSERT INTO "users" ("email", "subdomain", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["email", "[email protected]"], ["subdomain", "subdomain"], ["created_at", "2017-05-24 14:25:39.550230"], ["updated_at", "2017-05-24 14:25:39.550230"]]
   (0.1ms)  SELECT "users"."subdomain" FROM "users"
   (156.6ms)  CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "email" varchar, "subdomain" varchar, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL)
   (160.9ms)  CREATE TABLE "schema_migrations" ("version" varchar NOT NULL PRIMARY KEY)
   (0.4ms)  SELECT version FROM "schema_migrations"
   (120.7ms)  INSERT INTO "schema_migrations" (version) VALUES (20170524141552)
   (114.4ms)  CREATE TABLE "ar_internal_metadata" ("key" varchar NOT NULL PRIMARY KEY, "value" varchar, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL)
  ActiveRecord::InternalMetadata Load (0.7ms)  SELECT  "ar_internal_metadata".* FROM "ar_internal_metadata" WHERE "ar_internal_metadata"."key" = ? LIMIT ?  [["key", "environment"], ["LIMIT", 1]]
   (0.3ms)  begin transaction
  SQL (0.9ms)  INSERT INTO "ar_internal_metadata" ("key", "value", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["key", "environment"], ["value", "development"], ["created_at", "2017-05-24 14:25:40.192029"], ["updated_at", "2017-05-24 14:25:40.192029"]]
   (106.9ms)  commit transaction
   (0.2ms)  SELECT "users"."subdomain" FROM "users"
   (87.5ms)  commit transaction

Bây giờ thì run rails server để kiểm tra kết quả nhé. 6. Kết quả Tạo dữ liệu cho bảng User như sau:

Dù chạy bằng url gốc http://lvh.me:3000/users hay subdomain http://subdomain.lvh.me:3000/users thì kết quả cũng giống nhau lý do là bảng User đã được public giữa các database: Bây giờ để kiểm chứng việc không bị trùng dữ liệu khi chạy các subdomain khác nhau chúng ta tạo một bảng private Project vói cấu trúc dưới đây:

rails g scaffold Project name year

Khi chạy với subdomain http://lvh.me:3000/projects sẽ cho kết quả như sau:

Tuy nhiên khi chạy với subdomain http://subdomain.lvh.me:3000/projects sẽ không thấy dữ liệu do bảng Project không được public với tất cả database:

7. Một số phương thức mà gem apartment cung cấp

  • Tạo một database mới
Apartment::Tenant.create('tenant_name')
  • Chuyển đổi giữa các database với nhau
Apartment::Tenant.switch!('tenant_name')
  • Kiểm tra xem thời điểm hiện tại đang sử dụng database nào
Apartment::Tenant.current_tenant

Việc sử dụng nhiều database riêng biệt cho từng subdomain quá dễ ràng phải không nào? Nếu bạn có ý định làm một ứng dụng với chức năng như vậy thì gem apartment là một lựa chọn sáng suốt. Còn rất nhiều điều mà gem apartment có thể làm được, để tìm hiểu chi tiết hơn các bạn có thể tham khảo Tại đây Thanks you for reading !!!