🎉
Apartment gem の仕組みを理解する
-
rails-on-services/apartment: Database multi-tenancy for Rack (and Rails) applications
- こちらのコードを読みつつ、手を動かしてみる
- これも読み込んだ
memo
- Apartmentは本来、動的にdatabaseを生成する仕組みを作れる
- テナントの管理者がログインしたらそのテナント用のdatabaseをcreateする、ということができる(後述のElevators)
- "public" tenant
- 通常、テナントDBでの処理が終わった後、"public" tenantに接続が戻る(Apartment::Tenant.switch! を使ってると戻らない)
- ここらへんの仕組みは、下記に書いてある
- https://github.com/rails-on-services/apartment?tab=readme-ov-file#middleware-considerations
- middlewareの順番によってはトラブルになる
- ここらへんの仕組みは、下記に書いてある
- 通常、テナントDBでの処理が終わった後、"public" tenantに接続が戻る(Apartment::Tenant.switch! を使ってると戻らない)
Elevators
- 「アパートメント」の中にある「エレベーター」を使って、それぞれの「住居(テナント)」に行く(作る)ことを指してる?
- のことを「ルーティング」とも言ってそう
- 「エレベーター」には、様々な行き方があるので、色々な「Elevator」をサポートしてる
- だから「Elevator[s]」
- initialize時点の Elevator の行き方は
subdomainになってる- 該当の subdomain でアクセスしてきてログインしたらテナントDBを作るとかできる
- 他に、
domain,first_subdomain,hostがある - https://github.com/influitive/apartment/tree/f266f73e58835f94e4ec7c16f28443fe5eada1ac/lib/apartment/elevators
- Elevator を移住者(リクエスト)毎に変える場合は、Rackミドルウェア(順番に気を付けて)使ってね
Config
抜粋
- config.use_schemas = false
- use を発行してスキーマを切り替えない
- config.append_environment = false
- テナント名の末尾に環境名を付与しない
- config.tenant_presence_check = false
- テナントを切り替えるたびに、スキーマの存在チェックをしない
- config.prepend_environment = false
- テナント名の先頭に環境名を付与しない
- config.db_migrate_tenants = false
- db:migrate による全てのテナントのマイグレーションが無効
- config.database_schema_file = nil
-
db/schema.rb以外を使う時に指定する
-
- config.seed_after_create = false
- テナント作成したあとに、seedは入れない
Managing Migrations
- config.tenant_names
- 全テナントのdatabase名を設定
- ここで設定されたdatabaseが、マイグレーション実行される
- 内部的には
Apartment::Tenant.migrate(#{tenant_name})が呼ばれるだけ
- 内部的には
ローカルで試す
- テナントDBを3つ作成(下記のテナント名変えて3回実行しただけ)
- まだ schema.rb 作成してないので、無いよーって言われて、マイグレーションもされない、DB作られるだけ
- schema.rb があると、CREATEと同時にマイグレーションもされる(設定次第ではある)
irb(main):001:0> Apartment::Tenant.create('blog_tenant_1_development')
(8.2ms) CREATE DATABASE `blog_tenant_1_development` DEFAULT CHARACTER SET `utf8mb4`
(0.9ms) use `blog_tenant_1_development`
(0.6ms) use `blog_development`
/Users/kazumeat/opt/rails_sandbox/blog/vendor/bundle/ruby/3.0.0/gems/ros-apartment-2.11.0/lib/apartment/adapters/abstract_adapter.rb:219:in `load_or_raise': /Users/kazumeat/opt/rails_sandbox/blog/db/schema.rb doesn't exist yet (Apartment::FileNotFound)
- switch 試した
irb(main):001:0> Apartment::Tenant.switch('blog_tenant_3') { p 'hoge' }
(0.8ms) use `blog_tenant_3`
"hoge"
(0.6ms) use `blog_development`
=> "hoge"
- マイグレートの準備
config/initializers/apartment.rb
config.tenant_names = lambda do
(1..3).to_a.map do |i|
"blog_tenant_#{i}_development"
end
end
- モデルの生成(schemaファイル作成される)
bin/rails generate model Article title:string body:text
- マイグレートする
bundle exec rake db:migrate
== 20231220142129 CreateArticles: migrating ===================================
-- create_table(:articles)
-> 0.0722s
== 20231220142129 CreateArticles: migrated (0.0722s) ==========================
Migrating blog_tenant_1_development tenant
== 20231220142129 CreateArticles: migrating ===================================
-- create_table(:articles)
-> 0.0205s
== 20231220142129 CreateArticles: migrated (0.0205s) ==========================
Migrating blog_tenant_2_development tenant
== 20231220142129 CreateArticles: migrating ===================================
-- create_table(:articles)
-> 0.0210s
== 20231220142129 CreateArticles: migrated (0.0211s) ==========================
Migrating blog_tenant_3_development tenant
== 20231220142129 CreateArticles: migrating ===================================
-- create_table(:articles)
-> 0.0229s
== 20231220142129 CreateArticles: migrated (0.0229s) ==========================
public db もマイグレートされた!(ActiveRecord の task を実行してるんだから、そりゃそうか・・となった)
なんで、rake db:migrate すると、テナントのDBもマイグレーションされるの?
config の設定値みて、rake db:migrate したら apartment:migrate も実行されるようになってる
CHANGELOG にも書いてあった
vendor/bundle/ruby/3.0.0/gems/ros-apartment-2.11.0/lib/apartment/railtie.rb
#
# Ensure rake tasks are loaded
#
rake_tasks do
load 'tasks/apartment.rake'
require 'apartment/tasks/enhancements' if Apartment.db_migrate_tenants
end
タスクはこれで
vendor/bundle/ruby/3.0.0/gems/ros-apartment-2.11.0/lib/tasks/apartment.rake
desc 'Migrate all tenants'
task :migrate do
Apartment::TaskHelper.warn_if_tenants_empty # <-- ★ 直接 rake apartment:migrate することは deprecated で Warning出る
Apartment::TaskHelper.each_tenant do |tenant|
Apartment::TaskHelper.migrate_tenant(tenant)
end
end
実際の処理はこっち
vendor/bundle/ruby/3.0.0/gems/ros-apartment-2.11.0/lib/apartment/tasks/task_helper.rb
def self.migrate_tenant(tenant_name)
strategy = Apartment.db_migrate_tenant_missing_strategy
create_tenant(tenant_name) if strategy == :create_tenant
puts("Migrating #{tenant_name} tenant")
Apartment::Migrator.migrate tenant_name
rescue Apartment::TenantNotFound => e
raise e if strategy == :raise_exception
puts e.message
end
Railtieの仕組みは、既存の拡張できるんだろうな〜くらいの理解しかないので、これはまた別途
Discussion