+2

Multi-Database với postgresql

1. Giới thiệu

  • Đối với các dự án lớn, với databases quá nhiều thì việc sử dụng multi-databases có lẽ cũng là một giải pháp hay và nên thực hiện. Chúng ta sẽ chia databases lớn đó thành các databases nhỏ hơn và bên trong là table phục vụ cho một chức năng cụ thể. Điều này giúp cho người lập trình xử lý, quản lý table của các chức năng này tốt hơn.

  • Ví dụ đơn giản như: Tôi làm một app liên quan đến quản lý thư viện, tôi chia databases của tôi thành các databases nhỏ làm các nhiệm vụ:

    • Quản lý sách
    • Chức năng cho mượn, trả sách
    • Xuất nhập kho, quản lý người dùng ...
  • Trên đây là một ví dụ nhỏ, và với các chức năng như trên, tôi sẽ có 1 databases tương ứng với các model cụ thể ....

  • Ví dụ khác là tôi có 2 ứng dụng A và B riêng biệt nhưng sử dụng chung một bảng User, tức là User đã signup ó thể login đc ở B. Vậy multi-databases cũng là một lựa chọn thích hợp cho trường hợp này.

  • Thực tế là có rất nhiều giải pháp cho việc setting multi-database trong rails, nhưng ở bài viết này tôi muốn giới thiệu cách là chúng ta sẽ tạo ra các file migrate và schema riêng biệt, generator các db riêng biệt, và với mỗi databases này đều sử dụng được các rake như create, drop, migration ...

2. Setting multi-databases

Tôi lấy ví dụ, tôi sẽ tạo ra 2 databases với tên gọi là db1 và db2. db1 sẽ được ưu tiên là databases default. Còn db2 là một databases mới tôi cần thêm vào trong app của mình

# config/database.yml
default: &default
  adapter: postgresql
  encoding: unicode
  username: username
  pool: 5
  host: localhost
  port: 5432
  password: *****

development:
  <<: *default
  database: db1_development
test:
  <<: *default
  database: db1_test
staging:
  <<: *default
  database: db1_staging

Đối với db1 việc rake db:create, db:drop, db:migrate là cách mà chúng ta vẫn thường làm việc, không có gì phải bàn cãi ở đây. Cái chúng ta quan tâm là với db2- một databases được viết chung cùng với db1 thì sẽ làm thế nào để create, drop, migrate

Bước 1 Tạo 1 file database_db2.yml cùng thư mục với database.yml để config cho db2

efault: &default
  adapter: postgresql
  encoding: unicode
  username: username
  pool: 5
  host: localhost
  port: 5432
  password: alphapassword

development:
  <<: *default
  database: db2_development
test:
  <<: *default
  database: db2_test
staging:
  <<: *default
  database: db2_staging

Bước 2: Chúng ta sẽ tạo 1 thư mục db_db2 giống như thư mục db

Đến đây có lẽ chúng ta cũng hiểu cách làm mà tôi muốn giới thiệu là chúng ta đang xây dựng những file, thư mục liên quan đến databases theo như những gì mà câu lệnh

rails app new

sinh ra cho chúng ta 😄

Bước 3: Tạo các file rake để có thể sử dụng rake db:create, db:drop ... như những gì chúng ta đã làm

task spec: ["db2:db:test:prepare"]

namespace :db2 do

  namespace :db do |ns|
    task :create do
      Rake::Task["db:create"].invoke
    end
    
    task :drop do
      Rake::Task["db:drop"].invoke
    end
    
    task :migrate do
      Rake::Task["db:migrate"].invoke
    end
        
    task :setup do
      Rake::Task["db:setup"].invoke
    end
    
    task :rollback do
      Rake::Task["db:rollback"].invoke
    end

    task :seed do
      Rake::Task["db:seed"].invoke
    end

    task :version do
      Rake::Task["db:version"].invoke
    end

    namespace :schema do
      task :load do
        Rake::Task["db:schema:load"].invoke
      end

      task :dump do
        Rake::Task["db:schema:dump"].invoke
      end
    end

    namespace :test do
      task :prepare do
        Rake::Task["db:test:prepare"].invoke
      end
    end

    # append and prepend proper tasks to all the tasks defined here above
    ns.tasks.each do |task|
      task.enhance ["db2:set_custom_config"] do
        Rake::Task["db2:revert_to_original_config"].invoke
      end
    end
  end

  task :set_custom_config do
    # save current vars
    @original_config = {
      env_schema: ENV['SCHEMA'],
      config: Rails.application.config.dup
    }

    # set config variables for custom database
    ENV['SCHEMA'] = "db_db2/schema.rb"
    Rails.application.config.paths['db'] = ["db_db2"]
    Rails.application.config.paths['db/migrate'] = ["db_db2/migrate"]
    Rails.application.config.paths['db/seeds'] = ["db_db2/seeds.rb"]
    Rails.application.config.paths['config/database'] = ["config/database_db2.yml"]
  end

  task :revert_to_original_config do
    # reset config variables to original values
    ENV['SCHEMA'] = @original_config[:env_schema]
    Rails.application.config = @original_config[:config]
  end
end

Bạn từng thực hiện với databases các thao tác như thế nào, thì chúng ta sẽ viết các file rake như thế. Chỉ khác namspace chúng ta cần thêm vào ví dụ:

$rake db2:db:create

Chú ý: Rake task set_custom_config, revert_to_original_config cung cấp cho chúng ta những biến môi trường, biến cấu hình để phù hợp với db2 của chúng ta

Đến đây, chúng ta có thể sử dụng: Chúng ta sẽ vào psql để kiểm tra xem databases d2_development đã có trong databses chưa

$ sudo -su postgres psql
postgres=# \list

Kết quả: Ngoài databases db1_development và db1_test thì chúng ta đã tạo đc db2_development và db2_test, vậy là câu lệnh của chúng ta đã hoạt động

Một câu hỏi đặt ra ở thời điểm này. Là khi tôi create một table làm thế nào để table đó biết là nó thuộc db1 hay db2 ? Và đối với db2 thì việc chạy lệnh

rails g migration CreateModel

có được hay không?

3. Custom generator

Câu trả lời cho phần 2 là: chúng ta ko thể sử dụng câu lệnh migration ở trên để create model cho db2 vì:

def create_migration_file
  set_local_assigns!
  validate_file_name!
  migration_template @migration_template, "db/migrate/#{file_name}.rb"
end

Như ta thấy thì method create_migration_file đã fix cứng thư mục tạo ra file migration là "db/migrate/#{file_name}.rb"

Vậy không còn cách nào khác là chúng ta sẽ ghi đè method này khi muốn create file migration trong db_db2/migrate

# lib/generators/stats_migration_generator.rb
require 'rails/generators/active_record/migration/migration_generator'
 
class Db2MigrationGenerator < ActiveRecord::Generators::MigrationGenerator
  source_root File.join(File.dirname(ActiveRecord::Generators::MigrationGenerator.instance_method(:create_migration_file).source_location.first), "templates")
 
  def create_migration_file
    set_local_assigns!
    validate_file_name!
    migration_template @migration_template, "db_db2/migrate/#{file_name}.rb"
  end
end

Chung ta thử kết quả chạy lệnh tạo table nào:

4. Conection model

  • Tạo file config/initializers/db_db2.rb với nội dung
DB_STATS = YAML::load(ERB.new(File.read(Rails.root.join("config","database_db2.yml"))).result)[Rails.env]

trong model Book

class Book < ActiveRecord::Base
  establish_connection DB_DB2

end

Kiểm tra bằng rails console

5. Kết luận

Việc sử dụng multi-databases đối với những dự án lớn với số lượng table lớn là một giải pháp hữu ích để người lập trình có thể quản lý, xử lý một cách dễ dàng, giúp nắm bắt chức năng của các table tốt hơn. Tuy nhiên đối với các dự án nhỏ thì việc sử dụng nó lại khiến cho dự án trở nên phức tạp. Do đó, người đọc nên cân nhắc trước khi sử dụng.

6. Tài liệu tham khảo

http://www.ostinelli.net/setting-multiple-databases-rails-definitive-guide/ http://www.thegreatcodeadventure.com/managing-multiple-databases-in-a-single-rails-application/


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í