Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

サブドメインを使用したマルチテナントRubyonRailsアプリの構築

マルチテナンシーの定義によると、アプリが複数のテナントにサービスを提供する場合、ソフトウェアインスタンスへの共通アクセスを共有するユーザーのグループがいくつかあることを意味します。マルチテナンシーをサポートするアプリの優れた例は、Jiraプラットフォームです。このプラットフォームでは、各企業がソフトウェアにアクセスするためのサブドメインを持っています(例: mycompany.atlassian.net )。 。

この記事では、マルチテナンシーの理論的側面と実践的側面の両方について説明します。アプリケーションで複数のテナントのサポートを実装するためのいくつかの一般的なタイプのアプローチについて説明した後、Railsアプリケーションでそれらのうちの2つを実装する方法を示します。各テナントがサブドメインを持つ複数のテナントを含むシンプルなアプリを一緒に構築します。

この記事を読むと、次のことができるようになります。

  • さまざまな種類のマルチテナントアプリについて話し合い、特定のアプローチが正しい選択であるかどうかを判断します。
  • 複数のテナントをサポートするRubyonRailsアプリケーションを作成します。
  • RubyonRailsアプリケーション内で顧客用のカスタムサブドメインを使用します。

この記事に従い、その利点のほとんどを得るには、Railsに関する基本的な知識が必要です。

理論におけるマルチテナンシー

前述したように、アプリは、アプリケーションの機能への共通アクセスを共有する少数のユーザーグループにサービスを提供する場合、マルチテナンシーをサポートしていると言えます。このようなアプリの良い例は、すべてのユーザーがメインドメインで個別のサブドメインを取得するブログプラットフォームです。各ブログにはサブドメインがありますが、コメント、記事、管理ダッシュボードなど、他のブログと同じ機能を共有しています。

マルチテナントアプリは表面上は同じように機能するように見えるかもしれませんが、内部でさまざまなタイプのマルチテナントアーキテクチャを実装できます。最も人気があり効率的なタイプを見てみましょう。

データベース-行レベル

アプリケーションで複数のデータベースを使用したくない場合は、1つの中央データベースで動作するアプローチを選択できます。データベース内の各テーブルは、 Tenant_idで構成されています 特定のテナントに属するデータをプルするためにアプリケーション内で実行されるすべてのクエリで使用される列。

このような構造でのテナントのセットアップ時間は速く、追加コストはなく、このバリアントの実装はどこでも可能です。ただし、クエリにテナントのIDを含めるのを忘れると、データ漏洩につながる可能性があるため、注意が必要です。データベース構造の変更は、特定のユーザーグループに対して維持するのが難しいため、この欠点を念頭に置く必要があります。

スキーマレベル

中央データベースは1つでも構いませんが、テナントごとに個別のテーブルとスキーマを使用できます。データベース行レベルのアプローチとは対照的に、テナントを切り替えるときは、クエリを変更するのではなく、テーブルへの検索パスを変更する必要があります。 PostgreSQLでは、セット search_pathを使用できます ステートメント、およびMySQLでは use スキーマを切り替えるステートメント。

このアプローチを選択する前に、DBサーバーで上記のステートメントを使用できることを確認してください。また、新しいスキーマを作成してデータベースにテーブルを作成する必要があるため、新しいテナントの追加には比較的時間がかかることにも注意してください。

データベースレベル

アプリケーションに新しいテナントを追加するたびに新しいデータベースのコストを支払う余裕がある場合は、このアプローチが適している可能性があります。このアプローチは、新しいテナントの追加に関して、説明されているすべてのアーキテクチャの中で最も時間がかかります。

別のテナントに切り替えるには、データベースへの新しい接続を確立する必要があるため、データ漏洩につながる可能性が最も高くなります。必要に応じて、ユーザーのすべてのグループのデータベース構造を簡単に変更することもできます。

アプリケーションスケルトンの作成

2つの別々のアプリケーションを作成します。1つは1つのデータベースでサポートされるマルチテナンシーを備え、もう1つは複数のデータベースでサポートされるマルチテナンシーを備えています。これらは、多数のテナントを処理するための異なるアプローチを表していますが、構成フェーズは同じです。

この記事を書いている時点で、Rubyの最新バージョンはRubyonRailsgemの2.7.2および6.1RC1です。システムに正しいバージョンのRubyがインストールされていることを確認し、正しいバージョンのフレームワークをインストールしてみてください。

gem install rails -v 6.1.0.rc1

これで、次のコマンドを使用してプロジェクトファイルを生成できます。

rails _6.1.0.rc1_ new tenantapp -d=mysql

プロジェクトディレクトリに入り、サーバーを実行し、ウェルカム画面が表示されるかどうかを確認して、続行できるようにします。

cd tenantapp/
rails s

1つのデータベースを備えたマルチテナントRailsアプリケーション

何かをコーディングする時が来ました。記事のこの部分では、Railsアプリケーションにマルチテナンシーを実装する方法を説明します。 1つのデータベースと1つのスキーマでこのアプローチを使用します。

計画は次のとおりです。

  • 利用可能な最新バージョンのRubyおよびRubyonRailsフレームワークを使用して、Railsアプリケーションのスケルトンを作成します
  • 操作できるデータを取得するために、いくつかのモデルの足場を作成します
  • 前の手順で作成した機能に、アプリケーションのデータベーステーブルとコードを更新することでマルチテナンシーのサポートを追加します
  • 最後のステップは、カスタムサブドメインのサポートを追加して、ユーザーがテナントに簡単にアクセスできるようにすることです。
  • 後で自分で実装できるいくつかの機能のアイデアを検討します

テストデータの準備

ユーザーが複数の著者によって公開された記事を表示できるブログプラットフォームを構築していると仮定しましょう。

最初に作成者モデルを作成します。これにより、作成者のデータが保存されます。

rails g scaffold author slug:string name:string description:string
rake db:create
rake db:migrate

これで、http:// localhost:3000 / authorsアドレスにアクセスし、数人の著者を追加して、後で記事に割り当てることができます。

次のステップは記事を作成することです:

rails g scaffold article title:string content:text
rake db:migrate

マルチテナンシーサポートの追加

私たちが作成しているアプリケーションでは、作成者がテナントです。各著者は自分の記事にのみアクセスできる必要があります。前に述べたように、1つのデータベースと1つのスキーマにマルチテナンシーサポートを実装する場合、重要な要件は Tenant_idを追加することです。 テナントによって管理されるすべてのモデルへのフィールド。

データベースに適切な移行を作成する

rails g migration AddTenantIdToArticle tenant_id:integer
rake db:migrate

上記の移行により、新しい列が Articleに追加されます モデル。この列は、現在のテナントに割り当てられているデータのみにアクセスするためのクエリで使用されます。

テナントに記事を割り当てる

このステップでは、コードを更新して、指定された作成者を記事に割り当て、後で選択した作成者に対してのみ記事をレンダリングできるようにします。 app / controllers / articles_controller.rbを開きます 次の変更を追加します:

class ArticlesController < ApplicationController
 before_action :set_article, only: [:show, :edit, :update, :destroy]
 before_action :set_authors, only: [:edit, :update, :new, :create]
 
 # ...
 
 private
   # Only allow a list of trusted parameters through.
   def article_params
     params.require(:article).permit(:title, :content, :tenant_id)
   end
 
   def set_authors
     @authors = Author.all
   end
end

私たちの見解では、 @authorsを使用できるようになりました アプリに追加された作成者のコレクションを含む変数。これで、作成者の名前を含む選択フィールドを追加し、適切な Tenant_idを割り当てることができます。 。 app / views / articles / _form.html.erbを開きます 次のセクションを追加します:

<div className="field">
 <%= form.label :author %>
 <%= form.select :tenant_id, options_from_collection_for_select(@authors, 'id', 'name', article.tenant_id), include_blank: true %>
</div>

先に進み、数人の著者を作成してから、いくつかの記事を作成して、後で特定の著者にのみ割り当てられた記事をレンダリングします。

カスタムサブドメインのサポートの追加

John Doeという名前で作成者を作成し、スラッグ値を johndoeに設定しました。 。私たちの目標は、http://johndoe.localhost:3000 /アドレスにアクセスして、特定のテナント、この場合はJohnDoeにのみ関連するデータを確認することです。

サブドメインの構成

入居時に記事を管理・訪問したいと思います。これは、 config / routers.rbを更新することで実現できます。 ファイルを作成し、記事リソースの定義を制約ブロックにラップします:

Rails.application.routes.draw do
 constraints subdomain: /.*/ do
   resources :articles
 end
 
 resources :authors
end

デフォルトでは、Railsはトップレベルドメインの長さを1に設定しますが、localhostを使用してこの設定を0に設定します。これを行うには、ファイル config / environment /development.rb<に次の行を追加します。 / code> :

config.action_dispatch.tld_length = 0

サブドメインをマルチテナンシーで機能させる

これで、任意の作成者を記事に割り当てることができます。サブドメインが使用されている場合は不可能です。 ArticlesControllerの動作を変更する必要があります 、およびすべての作成者を設定する代わりに、サブドメインが要求された作成者を設定する必要があります:

 
class ArticlesController < ApplicationController
 before_action :set_author
 before_action :set_article, only: [:show, :edit, :update, :destroy]
 
 # GET /articles
 # GET /articles.json
 def index
   @articles = Article.where(tenant_id: @author.id)
 end
 
 # ...
 
 private
   def set_article
     @article = Article.find_by!(id: params[:id], tenant_id: @author.id)
   end
 
   # ...
 
   def set_author
     @author = Author.find_by!(slug: request.subdomain)
   end
end

コントローラにいくつか変更を加えました:

  • set_authorsの代わりに メソッド、 set_authorを定義しました サブドメインを介して要求された作成者を設定するメソッド。 before_filterでこのメソッドを呼び出すことが重要です set_articleの前 と呼ばれます。記事を設定する前に、著者を割り当てる必要があります。
  • set_articleを更新しました 指定されたIDと割り当てられた作成者を持つ記事を検索するメソッド。現在のテナントがジョンである間は、トムが作成した記事をレンダリングしたくありません。
  • 現在のテナントに割り当てられている記事のみを選択するようにインデックスアクションを更新しました。

マルチテナントアプリで作業し、1つのスキーマで1つのデータベースを使用する場合は、常に Tenant_idでクエリのスコープを設定することを忘れないでください。 桁;それ以外の場合は、要求されたテナントに割り当てられていないデータを提供します。

コントローラが更新されるので、次のステップはフォームビューを更新することです。 app / views / articles / _form.html.erbを開きます ファイルを作成し、前のセクションを単純な非表示フィールドに置き換えます:

<%= form.hidden_field :tenant_id, value: @author.id %>

これらの変更の前に、ユーザーはフォームを使用して記事の著者を選択することができました。ユーザーは、Webサイトアドレスの特定のサブドメインを使用して、すべてのアクションの作成者を選択できます。

これで、作成したコードをテストできます。たとえば、JohnDoeという名前で新しい作成者を作成します。johndoeをスラッグします。 。 https://johndoe.localhost:3000/articles/new addressにアクセスして、新しい記事を追加します。新しい記事を追加すると、http://johndoe.localhost:3000/articlesの下にあるリストでその記事を表示できます。

おめでとう!各作成者がサブドメインを持つマルチテナントアプリを作成しました。

さらなる改善

新しい機能を追加して、アプリケーションのコードを拡張できるようになりました。私たちのブログプラットフォームは単純です。コメントセクションを追加する時が来たのでしょうか?追加する機能に関係なく、 Tenant_idを追加することを忘れないでください。 列を新しいモデルに変換し、リソースをクエリするときに使用します。

特別なスコープを作成して、懸念事項にラップすることができます:

module Tenantable
 extend ActiveSupport::Concern
 
 included do
   scope :for_author, -> (author) { where(tenant_id: author.id) }
 end
end

次に、テナントのデータを含むすべてのモデルで使用します。

class Article < ApplicationRecord
 include Tenantable
end
 
author = Author.find(1)
Article.for_author(author)

上記のアプローチでは、 Tenant_id の名前を変更する場合は、1か所だけ更新する必要があります。 テナント関連データのクエリに関しては、列またはより多くの条件を導入します。

複数のデータベースを備えたマルチテナントRailsアプリケーション

前の段落では、1つのデータベースと1つのスキーマで複数のテナントをサポートするRailsアプリケーションを構築しました。このようなアプローチの最も重大な欠点は、データ漏洩の可能性が高いことです。

幸いなことに、Ruby on Railsフレームワークの最新バージョンには、複数のデータベースを管理するためのサポートが組み込まれています。それを調べて、それを使用して、多数のデータベースとカスタムサブドメインを備えたマルチテナントRailsアプリケーションを構築する方法を示します。

計画は次のとおりです。

  • 利用可能な最新バージョンのRubyおよびRubyonRailsフレームワークを使用して、Railsアプリケーションのスケルトンを作成します
  • 操作できるデータを取得するために、いくつかのモデルの足場を作成します
  • カスタムサブドメインのサポートを追加します。
  • 新しいデータベースを作成して構成することにより、新しいテナントを追加する方法を学習します。
  • 最後のステップは、指定されたサブドメインが要求されたときにデータベースを切り替えるようにアプリケーションを更新することです。

テストデータの準備

ユーザーが複数の著者によって公開された記事を表示できるブログプラットフォームを構築していると仮定しましょう。各著者には専用のデータベースがあります。

Authorの作成から始めます 新しい作成者を表示および作成するのに役立つスキャフォールド機能を備えたモデル:

rails g scaffold author slug:string name:string description:string
rake db:create
rake db:migrate

これで、http:// localhost:3000 / authorsにアクセスして、Railsが生成したものを確認できます。

新しい作成者の追加

JohnDoeという名前で新しい作者を作成しました。johndoeをスラッグします。 。スラッグ値は、サブドメインを使用して情報を表示する必要がある作成者を検出するために後で使用されます。

各作成者は個別のデータベースを持っているため、Johnの新しいデータベースを手動で追加する必要があります。 config / database.ymlを開きます 次の変更を追加します:

development:
 primary:
   <<: *default
   database: tenantapp_development
 primary_johndoe:
   <<: *default
   database: tenantapp_johndoe_development
   migrations_paths: db/tenants_migrations

テナントが使用する移行には、別のディレクトリを使用します。中央データベースには必要ありません。これで、デフォルトのコマンドを使用してJohnのデータベースを作成できます。

rake db:create

記事の追加

これで、 Articleの足場を作ることができます コントローラーとビューを含むモデル:

rails g scaffold article title:string content:text --database primary_johndoe

-databaseを渡しました 移行をデフォルトのdb/ migrationsに配置してはならないことをRailsに通知するパラメータ プライマリデータベースによって使用されるディレクトリ。これで、migrateコマンドを実行できます:

rake db:migrate

現在、2つのスキーマがあります: db / schema.rb およびdb/ primary_johndoe_schema.rb 。テナント用に異なるテーブルを作成する場合は、一意の migrations_pathを設定することでこれを実現できます。 config / database.ymlの値 指定されたテナントのファイル。この記事では、すべてのテナントで同じテーブルを使用するため、移行へのパスは同じになります。

マルチテナンシーサポートの追加

1つのデータベースでマルチテナンシーがサポートされているアプリケーションでは、特定のテナントのデータを取得するには、適切な Tenant_idを使用してデータベースへのクエリを更新する必要があります。 価値。この場合、各テナントにはデータベースがあるため、データベースを切り替える必要があります。

Rails 6.1には、水平シャーディングのサポートが組み込まれています。シャードは、データセット全体のサブセットを含む水平方向のデータパーティションです。すべての著者の記事を1つのデータベースの1つのテーブルに保存できますが、シャーディングのおかげで、データを同じ構造で別々のデータベースに配置された複数のテーブルに分割できます。

すべてのモデルの親クラスでシャードを定義しましょう。これらはapp/ models / application_record.rbに保存されます :

ActiveRecord::Base.connected_to(role: :reading, shard: :johndoe) do
  Article.all # get all articles created by John
end

呼び出しをconnected_toにラップせずに ブロックの場合、デフォルトのシャードが使用されます。先に進む前に、もう1つの変更を導入する必要があります。すべてのシャードが同じデータ構造を共有しているため、 app / models / primary_johndoe_record.rbを削除できます。 記事の足場を作ったときに自動的に作成されたモデル。

app / models / article.rbも編集する必要があります 親クラスをモデル化し、 PrimaryJohndoeRecordから変更します ApplicationRecordへ :

class Article < ApplicationRecord
end

テナントの追加

現在、データベースには1人の著者しかいません。テナント(データベース)間の切り替え機能をテストするには、作成者をもう1人追加する必要があります。 http:// localhost:3000 / authors / new addressを開き、新しい作成者を追加します。著者をTimDoeという名前で追加し、 timdoeをスラッグしました。 。

新しい作成者のレコードがあるため、新しいデータベースを定義する必要があります:

development:
 primary:
   <<: *default
   database: tenantapp_development
 primary_johndoe:
   <<: *default
   database: tenantapp_johndoe_development
   migrations_paths: db/tenants_migrations
 primary_timdoe:
   <<: *default
   database: tenantapp_timdoe_development
   migrations_paths: db/tenants_migrations

次に、新しいデータベースを作成して移行を実行します。

rake db:create
rake db:migrate

最後のステップは、 ApplicationRecordを更新することです 新しいシャードをモデル化して定義します:

class ApplicationRecord < ActiveRecord::Base
 self.abstract_class = true
 
 connects_to shards: {
   default: { writing: :primary, reading: :primary },
   johndoe: { writing: :primary_johndoe, reading: :primary_johndoe },
   timdoe: { writing: :primary_timdoe, reading: :primary_timdoe },
 }
end

著者ごとに記事を作成できるようになりました:

ActiveRecord::Base.connected_to(role: :writing, shard: :johndoe) do
  Article.create!(title: 'Article from John', content: 'content')
end
 
ActiveRecord::Base.connected_to(role: :writing, shard: :timdoe) do
  Article.create!(title: 'Article from Tim', content: 'content')
end

カスタムサブドメインのサポートの追加

ジョンとティムの記事をいつ表示するかわからないため、特定の著者の記事リストにアクセスすることはできません。カスタムサブドメインを実装することで、この問題を解決します。 http://johndoe.localhost:3000 / articlesにアクセスすると、Johnの記事が表示され、http://timedoe.localhost:3000 / articlesにアクセスすると、Timの記事が表示されます。

サブドメインの構成

入居時に記事を管理・訪問したいと思います。これは、 config / routers.rbを更新することで実現できます。 ファイルを作成し、記事リソースの定義を制約ブロックにラップします:

Rails.application.routes.draw do
 constraints subdomain: /.*/ do
   resources :articles
 end
 
 resources :authors
end

デフォルトでは、Railsはトップレベルドメインの長さを1に設定しますが、localhostを使用してこの設定を0に設定します。これを行うには、ファイル config / environment /development.rb<に次の行を追加します。 / code> :

config.action_dispatch.tld_length = 0

サブドメインをマルチテナンシーで機能させる

現在のテナントのデータベースの読み取りを標準化するために、 Tenantableと呼ばれるコントローラーの懸念事項を作成します 。 read_with_tenantを提供します ブロックを受け入れ、要求されたテナントのコンテキストでそれを実行するメソッド:

module Tenantable
 extend ActiveSupport::Concern
 
 private
 
 def read_with_tenant(&block)
   author = Author.find_by!(slug: request.subdomain)
 
   ActiveRecord::Base.connected_to(role: :reading, shard: author.slug.to_sym) do
     block.call
   end
 end
end

このファイルをapp/ controllers / concerns / Tenantable.rbとして保存します ArticlesControllerに含めます :

class ArticlesController < ApplicationController
 include Tenantable
 
 before_action :set_article, only: [:show, :edit, :update, :destroy]
 
 def index
   read_with_tenant do
     @articles = Article.all
   end
 end
 # ...
end

これで、http://johndoe.localhost:3000 / articlesまたはhttp://timdoe.localhost:3000 / articlesにアクセスでき、リストにさまざまな記事が表示されます。

フォームを使用して新しい記事を作成する場合は、 write_with_tenantという新しいメソッドを定義する必要があります。 ArticlesController内のテナント可能な懸念事項とメソッドを更新します それに応じて。

さらなる改善

上記のアプローチは、データベースへの特定の接続内のブロックにラップされたコードを実行する単純なラッパーメソッドです。よりユニバーサルにするために、コードを実行する前にサブドメインを解析して接続を確立するミドルウェアを作成できます。

ActiveRecord::Base.establish_connection(:primary_timdoe)

最終的な解決策は、ニーズと、特定のテナントに割り当てられたデータを使用する場所の数によって異なります。

概要

おめでとうございます。マルチテナントRailsアプリケーションの2つのバージョンを作成し、最新のWebアプリケーションで複数のテナントを処理するさまざまな方法についての知識を習得しました。

この記事で学んだことを簡単に要約しましょう:

  • Webアプリケーションには、データベース行、スキーマ、データベースレベルの3つの主要なマルチテナンシーレベルがあります。
  • それぞれのアプローチには長所と短所があり、どちらを選択するかは、ハードウェアの可能性とテナント情報の分離レベルによって異なります。
  • 外部ライブラリなしで、RubyonRailsフレームワーク内にすべてのマルチテナンシーレベルを実装することが可能です。
  • Ruby on Railsは、すぐに使用できるカスタムサブドメインをサポートしているため、各テナントにサブドメインを割り当てることができるマルチテナントアプリケーションに最適です。

この記事を読んで、マルチテナントのRubyonRailsアプリケーションを構築して楽しんでいただけたでしょうか。

P.S。 Ruby Magicの投稿をマスコミから離れたらすぐに読みたい場合は、Ruby Magicニュースレターを購読して、投稿を1つも見逃さないでください。


  1. Rails5でのAngularの使用

    あなたは前にその話を聞いたことがあります。分散型で完全に機能するバックエンドAPIと、通常のツールセットで作成されたフロントエンドで実行されているアプリケーションがすでにあります。 次に、Angularに移動します。または、AngularをRailsプロジェクトと統合する方法を探しているだけかもしれません。これは、この方法を好むためです。私たちはあなたを責めません。 このようなアプローチを使用すると、両方の世界を活用して、たとえばRailsとAngularのどちらの機能を使用してフォーマットするかを決定できます。 構築するもの 心配する必要はありません。このチュートリアルは、この目的のた

  2. Rubyを使用してコマンドラインアプリケーション(CLI)を構築する方法

    多くの人は、RubyがWebアプリケーションではないことを実行できることを忘れています。この記事では、それを改善するのに役立つコマンドラインアプリケーションを構築する方法を紹介したいと思います! 使い慣れているコマンドラインアプリケーションは次のとおりです。 psql rails bundler gem git コマンドラインアプリケーションを構築する方法はたくさんあります。この記事では、そのうちの3つに焦点を当てます。 あなたは学ぶつもりです : ARGVアレイ OptParseライブラリ トールの宝石 始めましょう! RubyARGV定数 コマンドラインア