Heroku Connectとは
簡単に言うとSalesforceのDBとHeroku Postgresを双方向に同期してくれるアドオンです。なのでSalesforce Advent Calendarの方が向いていたのかも。
2010年にHerokuがSalesforceに買収されて以来、特に何が変わるでもない状況が続いていたのですが、2013年末ごろに発表され、2015年5月にGAした公式の連携機能です。当初はアドオンマーケットプレイスにいなかったんですが、今年に入ってから(多分このころ)誰でも利用できるようになりました。ただしDemoエディション。有料プランはHeroku Enterprise前提のようです。
しごとでHeroku Connectを使うことが何度かあったので、苦労したところなどを雑多に書いてみたいと思います。
Demoエディションの主な制限
Demoエディションの主な制限は同期できるレコード数と同期間隔です。あとExternal Objectという機能も使えないのですが、これは有料プランでも上位プランになるらしいので使ったことがありません。
- 同期できるレコード数が最大1万
- Salesforce→Heroku方向の同期間隔が最短10分(有料プランは最短2分)
- Salesforceでのレコード更新を検出して短時間で同期するEvented Modeが利用できない(と書いてあるがなぜか利用できる)
- Dev Centerには書いてないが、Heroku→Salesforce方向の同期間隔もだいぶ遅い(10秒のはずだが1分前後かかる)
基本的には、同じことを自力で開発しようと思ったらできる範囲のことしかやってくれません。非公開のAPIを使っているとかはないようです。ただし、SalesforceのAPIコール数制限にはほとんどカウントされません。自力で開発するとAPIコール制限がかなり厳しいので助かります。
試してみる
Dev CenterのGetting Started on Heroku with Heroku Connectに添って進めていけば、SalesforceのテーブルがPostgresのテーブルと同期するところまで試せます。サンプルアプリはsinatraとsinatra-activerecordを使っています。
アドオンダッシュボードのGUIで同期設定すると、Heroku Connectが使うPostgresのスキーマにテーブルができるのでマイグレーションする必要はありません。むしろ勝手にテーブルを作るとアドオンGUIでエラーになります。
英語読むのが面倒な人用: http://qiita.com/tarot/items/902f4793d8b5c75e35e8
APIとCLI
Heroku Schedulerと違ってAPIとCLIが用意されています。heroku plugins:install heroku-connect-plugin
でプラグインをインストールすることでCLIのサブコマンドを利用できるようになります。
よく使うのはheroku connect:import
とheroku connect:export
です。アドオンGUIからもインポート・エクスポートできますが、CLIで利用できるのはやはり便利です。
一般的な注意
- テーブル単位ですべてのレコードを同期します。「条件を満たすレコードのみ」などはできません。
- 同期するカラムを選ぶことはできますが、「Postgresにだけ存在するカラム」は作成できません。
- 一応できますがいつエラーになるかわかりません。
- ただし、Heroku Connectが内部利用するシステムカラムはPostgresにだけ存在してSalesforceには同期されません。
- 数式項目はSalesforce→Postgres方向に同期されるまで更新されません。
- Evented Modeを利用していれば、Postgres→Salesforceに同期したあと数秒で逆方向に同期されますが、それでも必ずタイムラグがあります
- 親レコードを参照する数式を同期している場合でも、親レコードを更新しただけでは子レコードは同期されません。
- 親レコードを参照する数式は注意深く利用する必要があります。例えば親レコードの当該項目は絶対に変更しないとか。
-
RecordType.DeveloperName
くらいなら使っても良いでしょう
- Salesforceでインデックスがついていればインデックスになりますが、インデックスを追加することはできません。
- 数値型は基本的に
double
です。小数点以下0桁でもdouble
です。「.0」が表示されてイラッとします。- 標準項目がたまにintになります。ズルイ。
Association
Salesforceのレコードは変更不可の自動採番IDを持っています。一方、Postgres→Salesforce方向の同期もあるので、PostgresでinsertしたレコードはSalesforceのIDではないIDが必要になります。そこでHeroku Connectで同期するテーブルには、Rails的なauto incrementなid
と、Salesforce ID用のsfid
項目が必ず作成されます。
Herokuでレコードを新規作成する場合はsfid
をnull
にします。Heroku→Salesforce同期でSalesforceにinsertされ、その後のSalesforce→Heroku同期の際にsfid
が同期されます。
Salesforceのテーブル同士のAssociationにはsfid
を使います。Railsだとこのようになります。
class Account < ActiveRecord::Base
self.table_name = 'salesforce.account'
has_many :contacts, primary_key: 'sfid', foreign_key: 'accountid'
end
class Contact < ActiveRecord::Base
self.table_name = 'salesforce.contact'
belongs_to :account, primary_key: 'sfid', foreign_key: 'accountid'
end
同期しているテーブルが親、Herokuのみのテーブルが子になるAssociationはid
を使うと良いでしょう。同期しているテーブルにHerokuでinsertした後、同期を待たずにHerokuのみのテーブルにinsertして参照関係を結ぶことができます。
class Contact < ActiveRecord::Base
self.table_name = 'salesforce.contact'
belongs_to :account, primary_key: 'sfid', foreign_key: 'accountid'
has_many :posts
end
class Post < ActiveRecord::Base
belongs_to :contact
end
親子関係のあるレコードを同時にinsertする方法
親オブジェクトが外部IDを持っていれば、子オブジェクトの項目一覧にその外部IDが現れます。例えばAccount.ExternalId__c
という外部IDがあった場合、Contact
の項目一覧でAccount__ExternalId__c
を選べるようになります。
なおこの項目名のAccount
はオブジェクト名ではなく参照名です。同じオブジェクトを参照する複数の参照関係項目を作成できるためこのようになっています。例えばContact
のカスタム項目で、Account
を参照するSecondaryAccount__c
を作った場合、SecondaryAccount__r__ExternalId__c
を同期できるようになります。
この項目はちょっとややこしくて、親オブジェクトの同期設定ページを開いて項目一覧をロードするところまでやると、(同期しなくても)子オブジェクトの同期設定ページに出現するようになります。
ExternalId__c
を設定したAccount
と、Account__ExternalId__c
を設定したContact
をinsertすれば、これらのレコードはContact.AccountId
で関連付けられます。
ただしこれらの項目をAssociationに利用するには注意が必要です。親レコードの外部ID(上の例ではAccount.ExternalId__c
)が変更された場合でも、その子レコードのContact
は同期されず、昔のExternalId__c
のままになるからです。この機能は新規作成時のみ利用すべきです(updateでの利用も少々危険です)。更新時に利用するなら、外部ID項目値を変更しないなどのルールが必要です。
なおこのような外部ID項目はEvent.WhatId
などの"Polymorphic"項目にも出現しますが、利用することはできません。
Salesforce側のオブジェクト定義変更
Salesforce側でオブジェクト定義を変更しても自動的には反映されません。項目を削除したなら同期設定からも削除しなければ、その項目を更新した時にエラーになります。このエラーは通知され、エラーを解決すると同期されますが、同期のタイミングが遅れるので注意しましょう。
テキスト項目の長さ変更などが面倒で、一旦同期解除してからもう一度同期するしかありません。スキーマのインポート(heroku connect:import
コマンド)では対応できないのでマニュアル対応になりがちです。
コンフリクト
Heroku Connectはコンフリクトの解決をしません。後勝ち戦略です。ただし、
- Heroku→Salesforceの同期は変更した項目のみ同期します
- Herokuで更新→同期用のテーブルに変更した項目値をコピー→10秒程度でSalesforceにupdate
- となるので、Herokuで更新した項目は必ずSalesforceに同期されます
- Salesforce→Herokuは同期対象の全項目を同期します
- Heroku→Salesforceの同期はSalesforceのレコードを更新するので、必ず次のSalesforce→Heroku同期のタイミングで取り込まれます
このようになっているので大体うまくマージされます。問題はエラーです。
エラーに注意
Heroku→Salesforceの同期で、ネットワーク以外のエラーが発生したら放ったらかしにされます。
Heroku→Salesforceの同期はSalesforceの入力規則や暗黙の入力規則(Email項目など)、トリガのエラーなどで失敗します。この時、Postgresのレコードは更新されたままになっているので、同じ値でもう一度更新することができません→__ActiveRecordのせいでした。SQLのupdateで更新されたカラムを同期するのですが、ActiveRecordは更新のないカラムをupdateしません__。更新していなかった項目を更新した時や、Salesforce側で更新があった時、突然エラーになっていた項目が元に戻るような挙動を示します。
また、Salesforceに更新できないレコード(後から入力規則を追加した時、古いレコードが入力規則に適合しないなど)があると同期失敗が頻発します。特に面倒なのは、主従関係で積み上げ集計を使っている時に、主レコードが更新できないケースです。Herokuからは従レコードしか更新していないのに、主レコードが更新できなくてエラーになって、アプリケーションからはどうしようもなくなることがあります。
Heroku→Salesforceの同期エラーは、Heroku Connectの管理カラム_hc_lastop
や管理テーブル_trigger_log
のstate
カラムで確認できるので、スケジューラなどで検出できるようにしておいたほうが良いでしょう。
Salesforce→Herokuの同期に失敗するのを見たことはありません。ただし、validationの不一致によってRailsでinvalidになるレコードが同期されることはよくあります。validationの不一致が不安なら、RecordInvalidもバグトラッカー(Airbrakeなど)に通知するようにしたほうが良いかもしれません。
なお、イベントログテーブルとWebhookで、Salesforce→Herokuの同期失敗を検出できたのですが、deprecatedになってしまいました。
監査項目とRailsのtimestamp
Winter'16の新機能で、レコード作成時にCreatedDate
などを設定できる権限が追加されたのですが、まだ対応していません。もともと監査項目はHeroku側で設定していても同期されないのですが、その処理によって無視されているようです。
ただし、監査項目だけを更新しても「空update」されます。あまり必要になることはないですが、おそらくこれが唯一の「空update」する方法です__SQLでupdateすれば「空update」できました__。
Railsのtimestamp
項目にCreatedDate
とLastModifiedDate
またはSystemModstamp
を設定しても問題ないと思います。Salesforce→Heroku同期されたときにSalesforce側の値で上書きされることに注意しましょう。
Railsで規約に従わないtimestamp
項目を利用するにはtimestamp_attributes_for_update
とtimestamp_attributes_for_create
を使います。このようなモジュールをinclude
してやればよいでしょう。
module SalesforceTimestamp
def timestamp_attributes_for_update
super + [:systemmodstamp]
end
def timestamp_attributes_for_create
super + [:createddate]
end
end
削除の検出
イベントログテーブルとWebhookがあるのですが、deprecatedになってしまいました。まだ使えますが。将来的にも利用できる、簡単に削除を検出する方法はまだありません。
これが問題になるのは、Herokuでだけ使っているテーブルと同期しているテーブルに参照関係がある場合や、アドオンなどの外部サービスと連携している場合です。
Herokuで参照関係を作っておいて、スケジュール処理で参照先がないレコードを探して検出したり、Salesforceで削除トリガを作って削除検知テーブルにinsertするなどの対応が必要です。
テーブル名、カラム名が気に入らない
特にRailsだと気になると思いますが、Salesforceはキャメルケース文化なので、Postgresに持ってくると単語区切りがわからなくなります。また、カスタムオブジェクト・カスタム項目には必ず__c
が付くという気持ち悪いルールがあります。
これを回避する方法をいろいろと模索したのですが、結局のところ「我慢する」ことを強く推奨します。SalesforceとHerokuの両方を触っていると、どっちでどんな名前にしていたか分からなくなります。キャメルケース→スネークケース変換と__c
の除去だけなら覚えられるかと思いましたが、検索性が悪くなるのでお勧めしません。
-
alias_attribute
を使う: 無駄にソースが長くなるし、SQLを使うときに混乱します - Postgresの更新可能ビューを使う: ビューの作成は自動化できるので悪くないのですが検索性が問題でした。また、viewをdropしないと同期設定を変更できないのも面倒です。(というか変更できてしまうのにテーブルやカラムが残ったままになるのが困る。そのままだと容量無駄遣いになるし、同期解除したテーブルやカラムをもう一度同期しようとした時に初めてエラーになる)
バージョン管理とCI
マッピングのバージョン管理はheroku connect:export
でエクスポートしたJSONを使えばよいです。ただこいつはマッピングを変更するたびに順番が変わるので、差分が出すぎないようにソートしたほうが良いです。
CIやローカル開発用のDBにはridgepoleがおすすめです。schema_search_path
を指定してやればHeroku Connectのテーブルだけエクスポートすることができます。管理テーブル、管理カラムはすべて_
から始まるので無視してやってもいいのですが、Heroku Postgresからheroku pg:backups capture
で抜いてきたテーブルをローカルにインポートするときに面倒なので無視しないほうがいいと思います。
ridgepoleでもカラムの順番がしょっちゅう変わるのでソートしたほうが良いです。
jsonとSchemafileをこんな感じでソートしてます。
https://gist.github.com/tarot/7f1358ac3f05eb2544d1