[Salesforce] プラットフォームイベントを利用して重いフローの処理を最後にまわす

2021.09.06

重いフローの取り扱い

取引先(Account)を作成・更新すると紐づいている全取引先責任者(Contact)のメール送信除外(HasOptedOutOfEmail)にチェックを入れるフローがあるとします。このフローは紐付くContactの件数が多いほど処理時間が長くなります。

処理の間、SalesforceはAccountに排他ロックをかけます。そのため、当該Accountに対する他の子レコード(例えば、商談(Opportunity))の作成・更新する処理が続いている場合、Accountに対するロックが取得できず、UNABLE_TO_LOCK_ROWエラーが発生する可能性が高まります1

このような場合、プラットフォームイベントを利用することで、重いフロー処理を一時停止し、条件が整った後でイベントを発火して実行する(例えば、Opportunityの作成・更新処理が終わった後に実行する)ことが可能です。

プラットフォームイベントを定義する

まず、プラットフォームイベントを定義します。 プラットフォームイベントはカスタムオブジェクトと同様の仕組みでSalesforce上で簡単に定義できます。

1) Salesforceにて[設定] > [プラットフォームイベント]にアクセスします。

プラットフォームイベント設定

2) [新規プアットフォームイベント]をクリックし、次のように設定しました。

項目 設定値
表示ラベル Processed
オブジェクト名 Processed
公開動作 コミット後に公開

プラットフォームイベント作成

作成したプラットフォームイベント

3) 作成したプラットフォームイベントにカスタム項目を設定します。データ型はチェックボックスを選びました。

プラットフォームイベントにカスタム項目を作る

カスタム項目を次のように設定しました。

項目 設定値
項目の表示ラベル Completed
デフォルト値 チェックなし
項目名 Completed
説明 処理が完了したらTrueにするフラグ項目。

プラットフォームイベントカスタム項目設定

フローでプラットフォームイベントの発火を待つ

フローの完成版は次の通りです。

フロー完成イメージ

最初に一時停止ロジックを配置し、プロセスからコールされた場合は一時停止してプラットフォームイベントの発火を待ちます。プロセスからのコールでない場合は一時停止しません。その後の処理は共通で、引数で受け取ったAccountに紐づくContactのメール送信除外(HasOptedOutOfEmail)をTrueにセットします。

以下、作り方になります。

まず、一時停止ロジックを使うために、「自動起動フロー(トリガなし)」を選択します。

フローの種類選択

最初の要素として一時停止ロジックを配置し、一時停止条件とイベント再開条件を設定します。

一時停止条件では、変数(IsCalledByProcess)がTrueの時に一時停止する設定を行なっています。プロセスからこのフローをコールする場合には、IsCalledByProcess変数にTrueを設定して呼ぶことにより一時停止を発動させる仕組みにしています。

一時停止条件

イベント再開条件では、プラットフォームイベント(Processed)のComplete__cにてTrueを受信したら再開という設定を行なっています。

イベント再開条件

フローを呼び出すプロセスを用意する

定義したフローを呼び出すプロセスをプロセスビルダーで用意します。 ここでは、Accountにカスタム項目「オプトアウト実行(execOptOut__c)」を定義し、この項目にチェックが入った時にフローを呼び出すプロセスにしました。

フローを呼び出すプロセス 開始タイミング

フローを呼び出すプロセス 実行条件

フローを呼び出すプロセス フロー起動

プラットフォームイベントを発火する

準備ができましたので、プラットフォームイベントの発火を行なってみたいと思います。

ここでは、JavaScriptを使って次の段取りで処理を行いました。

  1. 特定Accountのオプトアウト実行(execOptOut__c)をTrueに更新し、フローを呼び出して一時停止させる。
  2. フローが一時停止中に当該Accountに紐づく200件のOpportunityを作成する。
  3. Opportunity作成が終わったらプラットフォームイベントを発火する。プラットフォームイベントの発火によりフローの一時停止が解除され、オプトアウト処理が実行される。

1でオプトアウトを実行するAccountには予めContactを2,000個紐付けておいて、10秒以上オプトアウト処理に時間がかかるようにしておきます。10秒ロックが取れないとUNABLE_TO_LOCK_ROWエラーが発生するため、Opportunity作成処理の後にオプトアウト処理を実行しないとUNABLE_TO_LOCK_ROWエラーが発生する状況を作っています。

コードは次の通りです。

const jsforce = require('jsforce');

const sf_account = '<Salesforceアカウント名>';
const sf_password = '<Salesforceパスワード>' + '<Salesforceセキュリティトークン>';
const sf_env = 'https://login.salesforce.com';

const targetAccountId = '<処理対象のAccountId>';
const closeDate = '<商談のCloseDate>';

let conn = new jsforce.Connection({
  loginUrl: sf_env
});

function login() {
  return new Promise(function(resolve, reject) {
    conn.login(sf_account, sf_password, function(err, res) {
      if (err) {
        reject(err);
      }
      resolve();
    });
  });
}

function pre(obj) {
  return JSON.stringify(obj, null, 2);
}

async function execAll() {
  try {
    await login();
    conn.sobject("Account").retrieve(targetAccountId, function(err, account) {
      if (err) { return console.error(err); }

      // オプトアウト実行にAccountを更新
      conn.sobject("Account").update({
        Id : account.Id,
        execOptOut__c : true,
      }, function(err, ret) {
        if (err || !ret.success) { return console.error(err); }

        // 商談作成
        const opportunities = [];
        for (let i = 0; i < 200; i++) {
          opportunities.push({
            AccountId: account.Id,
            Name: 'Opportunity #' + i,
            StageName: 'Closed Won',
            CloseDate: closeDate,
          });
        }
        conn.sobject("Opportunity").create(opportunities, function(err, rets) {
          if (err) { return console.error(err); }
          for (let j=0; j < rets.length; j++) {
            if (rets[j].success) {
              console.log("Created record id : " + rets[j].id);
            }
          }

          // プラットフォームイベントを発火
          conn.sobject('Processed__e').create({
            Completed__c : true,
          }).then(function(ret) {
            if (!ret.success) { return console.error(pre(ret.error)); }
            console.log("fire platform event!");
          });

        });
      });
    });
    return;
  } catch(err) {
    console.log(err);
  }
}

execAll();

プラットフォームイベントの発火はプラットフォームイベントをオブジェクトのように作成することで実現できます。

// プラットフォームイベントを発火
conn.sobject('Processed__e').create({
 Completed__c : true,
}).then(function(ret) {
  if (!ret.success) { return console.error(pre(ret.error)); }
  console.log("fire platform event!");
});

コードを実行するとOpportunityが作成完了するまでフローが一時停止し、プラットフォームイベント発火後にフローが再開して、UNABLE_TO_LOCK_ROWエラーが発生しないで処理が完了します。