概要
目的
Salesforce のオブジェクトを Ruby on Rails アプリケーション上のデータベースに反映します。今回のサンプルコードは、 Account
というオブジェクトの全レコードを Rails アプリケーションの accounts
テーブル( Account
モデル)に反映します。
同期スケジュール
サーバーが cron に対応していると仮定して、5分に1度 cron で rake タスクを実行して新規のレコードを反映します。既存レコードの属性を変更された場合の反映まで5分に1度行うと、サーバーやデータベースへの負荷が大きいので今回は毎日0時に反映することにします。つまり、新規レコードの反映は5分毎、更新レコードの反映は毎日0時に行います。
環境
- Ruby 2.5.1
- Rails 5.2.1
使用するGem
この記事のサンプルは、以下の2つの Gem を README に書いてある通りに使用して組み合わせることにより、定期的に Salesforce からデータを受け取る手順を書いただけの単純なものです。
用意するもの
Salesforce API を利用するための情報
最低限、Salesforce API を利用するための情報を用意する必要があります。credentials や環境変数を参照するようにすると良いでしょう。今回のサンプルでは、とりあえず環境変数に記述されているものとして話を進めます。各情報を得る場所は頻繁に変更されるので salesforce の最新のドキュメントを参照するか、気合いで見つけて頂くようお願いします。(記事中最難関)
私は https://developer.salesforce.com/docs/atlas.ja-jp.api_rest.meta/api_rest/quickstart_oauth.htm を参考に取得しました。記事執筆時点ではクライアントIDが「コンシューマ鍵」、クライアントシークレットが「コンシューマの秘密」といった名前で公開されていて少し探しにくかったです。
SALESFORCE_HOST="login.salesforce.com"
SALESFORCE_CLIENT_ID="id"
SALESFORCE_CLIENT_SECRET="secret"
SALESFORCE_USER_NAME="user@example.com"
SALESFORCE_PASSWORD="password"
Salesforce の ID を保存する属性
同期先のテーブル(今回のサンプルでは accounts
テーブル)に Salesforce の ID を保存する属性(列)を追加します。
class AddSalesforceIdToAccounts < ActiveRecord::Migration[5.2]
def change
add_column :accounts, :salesforce_id, :string
end
end
この属性を見て新規レコードなのか更新レコードなのかを判定します。作成日時や更新日時を見て判定しても良いのですが、どちらにせよ Salesforce の一意な ID は Rails アプリケーションに保存していた方が良いでしょう。
Salesforce 通信の準備
Salesforce API のラッパーの Gem は databasedotcom と Restforce の2つがあり、ググってヒットする情報も記事執筆時点で二分されておりますが databasedotcom
の README に
DEPRECATED: Please use restforce instead.
と書かれている通り、現在は Restforce 一択となっています。
Restforce のインストール
書くまでもありませんが、Gemfile に追加して
gem 'restforce'
インストールします。
$ bundle
ラッパーのラッパー
Restforce::Data::Client
のインスタンスを返すクラスを作っておきます。API のラッパーに何を使うかと、Salesforce の各種情報の在り処(下記のサンプルでは環境変数)を隠蔽するためだけのクラスです。
class SalesforceApi
attr_reader :client
def initialize
@client = Restforce.new(
host: ENV['SALESFORCE_HOST'],
username: ENV['SALESFORCE_USER'],
password: ENV['SALESFORCE_PASSWORD'],
client_id: ENV['SALESFORCE_CLIENT_ID'],
client_secret: ENV['SALESFORCE_CLIENT_SECRET']
)
end
end
新規レコード作成用の rake タスク
Salesforce の Account
オブジェクトの Id
と Name
属性を、アプリケーションの accounts
テーブルの salesforce_id
と name
属性に同期します。タスクの内容はシンプルで、アプリケーションが知らない salesforce_id
のみをアプリケーションに追加するだけです。このサンプルは API の結果を全て取得してループをぶん回す大変富豪的な実装になっています。Salesforce の API を叩ける回数には限りがありますし、アプリケーションのスペックにも限りがあるのでバランスを取って最適な実装を選択する必要があります。
desc 'Create accounts from salesforce'
task create_accounts_from_salesforce: :environment do
Account.transaction do
salesforce_ids = Account.pluck(:salesforce_id)
SalesforceApi.new.client.query('select Id,Name from Account').each do |o|
Account.create!(salesforce_id: o.Id, name: o.Name) if salesforce_ids.exclude?(o.Id)
end
end
end
既存レコードの属性更新用の rake タスク
アプリケーションが知っている salesforce_id
の属性を update!
に渡します。ActiveRecord の update
, update!
メソッドは属性の更新がなければ何もしない実装になっているので、アプリケーションレイヤーで属性に更新がかかったかを判定する必要はありません。
desc 'Update accounts from salesforce'
task update_accounts_from_salesforce: :environment do
Account.transaction do
SalesforceApi.new.client.query('select Id,Name from Account').each do |o|
account = Account.find_by(salesforce_id: o.Id)
account.update!(name: o.Name) if account.present?
end
end
end
余談
上記もまた大変富豪的な実装で、Salesforce の全アカウントを取得してループをぶん回し、毎回 find_by
でデータベースの照会をしています。API を叩く回数とアプリケーションのデータベースにアクセスする回数を減らして負荷を Ruby で背負うのであれば、こんな感じでやればレコード量と構造次第ですが早くなるかもしれません。
objects = client.query('select Id,Name from Account').group_by(&:Id)
salesforce_ids = objects.map(&:first)
Account.where(salesforce_id: salesforce_ids).find_each do |account|
account.update!(name: objects['0012800001b6FF3AAM'].first.Name)
end
いろいろやってみてベンチマーク取ってみて、API の利用可能数やサーバースペックと相談しつつ実装していく形になります。
同期スケジュールの登録
とりあえず新規用と更新用の rake タスクを作ったので、次は5分に1度と毎日0時にこれらを実行する機構の実装を行います。
Whenever のインストール
これも書くまでもありませんが、Gemfile に追加して
gem 'whenever', require: false
インストールします。
$ bundle
スケジュールファイルの作成
Whenever の README に書いてある通りにスケジュールファイルに記述します。5分に1度 create_accounts_from_salesforce
の rake ファイルを実行し、毎日0時に update_accounts_from_salesforce
の rake ファイルを実行するように指定します。 rake
メソッドはデフォルトで RAILS_ENV=production
で crontab に書き込んでくれます。
every 5.minutes do
rake 'create_accounts_from_salesforce'
end
every 1.day, at: '00:00 am' do
rake 'update_accounts_from_salesforce'
end
これで whenever --update-crontab
を実行すれば crontab へ反映してくれます。
サーバー準備
ここまででアプリケーションのコードは書き終えたので、あとはサーバーにデプロイして Salesforce の各情報を credentials なり環境変数なりに登録後、初回の同期を手動で行います。
$ bundle exec rails create_accounts_from_salesforce RAILS_ENV=production
そして、Whenever の crontab 書き込みコマンドを実行して終了です。お疲れ様でした。
$ bundle exec whenever --update-crontab