後から「やらなければよかった」と思った設計
当時の私は、とにかく「変更管理がしやすいフロー」を目指していました。今思えば、それが仇となりました。
取引先オブジェクトには様々な自動化要件があり、それらを全てレコードトリガーフローで実装することになりました。私の最初の設計判断は、「機能ごとにフローを細分化する」というものでした。
- 取引先ランク変更時に特定のChatterグループに投稿するフロー
- 取引先状況が「休眠」になった際に、関連する商談をクローズするフロー
- 特定項目が更新されたら、関連するカスタムオブジェクトの値を更新するフロー
- 新しい取引先が作成されたら、タスクを自動作成するフロー
といった具合に、After Saveのレコードトリガーフローを、取引先オブジェクトだけで10個以上作ってしまいました。当時はこれがベストプラクティスだと信じて疑いませんでした。
なぜ当時は「細分化」が良いと思ったのか
当時の私の中では、以下のようなメリットがあると考えていました。
- 変更管理の容易性:「このフローはChatter投稿用」「このフローは商談クローズ用」と明確に役割が分かれているため、特定の機能に修正が入った場合でも、影響範囲が限定的だと思っていました。
- デバッグのしやすさ:何か問題が発生した際に、「あの機能がおかしいから、あのフローを調べればいい」という思考で、ピンポイントに問題箇所を特定できると考えていました。
- 機能の有効/無効化:例えば、期間限定でChatter投稿を停止したい場合、該当フローを無効化するだけで済む、とシンプルに考えていました。
- Apexの「One Trigger Per Object」はFlowには関係ないという誤解:Apex開発の世界では「1オブジェクト1トリガー」がベストプラクティスとされますが、Flow BuilderはGUIツールであり、Apexとは性質が異なるため、この原則は当てはまらない、と勝手に解釈していました。むしろ細分化が「正義」だとすら思っていました。
そして、何が起きたか(後悔の連鎖)
私の甘い考えは、あっという間に現実の壁にぶつかりました。
-
実行順序の地獄:
同じAfter Saveトリガーを持つフローが複数ある場合、Salesforceはそれらの実行順序を保証しません。これは公式ドキュメントにも明記されていますが、当時はその「不確実性」の破壊力を理解していませんでした。
あるフローが取引先の特定項目を更新し、その項目をトリガー条件とする別のフローが動くはずが、別のフローがその項目をさらに別の値で上書きしてしまい、結果的に期待した自動化が実行されない、という事態が頻発しました。「あれ?このChatter投稿されないな」「なぜか関連レコードのフラグが立ってない!」といった状況でデバッグログを追う羽目になり、原因特定に膨大な時間がかかりました。
デバッグログを複数フローで追うと、ログの量が膨大になり、どこで誰が何を変更したのか、まるで犯人探しのような状況でした。
-
全体像の喪失とメンテナンスコストの増大:
取引先オブジェクトのレコードトリガーフロー一覧を開くと、ズラッと並んだフローの名前。「これ、結局何がどう動くんだっけ?」と、自分でも全体像が把握できなくなりました。新規のSalesforce管理者に引き継ぐ際も、「え、このオブジェクトだけでこんなにフローが動いてるんですか!?」と驚かれ、説明するのに一苦労でした。特定の要件変更があった際に、どのフローが関係するのか、影響範囲はどこまでかを探すだけで時間がかかり、結果的にメンテナンスコストは跳ね上がりました。
-
パフォーマンスへの漠然とした不安:
全ての有効なレコードトリガーフローは、レコードが保存されるたびに評価されます。エントリ条件で早々に終了するフローがほとんどだとしても、この「評価」自体が積み重なると、レコード保存のパフォーマンスに影響を与えるのではないかという漠然とした不安が常にありました。実際にタイムアウトやガバナ制限に抵触するまではいかなかったものの、常にヒヤヒヤしていました。
-
Governor Limits接近のリスク:
複数のAfter Saveフローが、それぞれ関連レコードを更新(DML操作)したり、コールアウトを行ったりするような設計は、一つのトランザクション内でGovernor Limitsに抵触するリスクを高めます。特にDMLの実行回数 (最大150) やSOQLの実行回数 (最大100) は、フローが増えれば増えるほど危険です。当時は幸いにも超えることはありませんでしたが、今振り返ると非常にリスキーな設計だったと反省しています。
⚠️ 公式ドキュメントで各Limit値を確認することをお勧めします。
今ならどう設計するか(当時の自分への伝言)
もしタイムマシンがあるなら、当時の自分にこう伝えます。
「One Flow Per Object, Per Trigger Type」の原則を貫け!
After Saveフローは、取引先オブジェクトに対して基本的には**一つだけ**にしなさい。Before Saveも同様に一つにまとめなさい。Deleteトリガーもね。つまり、オブジェクトごとにBefore/After/Deleteの**最大3つ**のレコードトリガーフローに集約するんだ。
サブフローを積極的に活用しろ!
集約した親フローの中に、個別の機能ロジックは全て「サブフロー」として切り出して呼び出すんだ。これにより、各機能のモジュール性は保たれる。そして何より、親フローの中でサブフローを呼び出す順番を**明示的に指定できる**ようになる。これで実行順序の不確実性は解消される。関連レコードの更新も、Chatter投稿も、全て親フローの制御下で順番通りに実行されるようになるぞ。
例:
// 親フロー (取引先更新後) // ... // エントリ条件: 取引先が更新されたら // デシジョン: ランクが変更されたか? // TRUEの場合: // サブフロー: Chatter投稿_取引先ランク変更通知 (取引先ID, 新ランク を引数として渡す) // // サブフローから戻ってきた後、次のロジックへ // // デシジョン: 状況が「休眠」になったか? // TRUEの場合: // サブフロー: 商談クローズ_取引先休眠時 (取引先ID を引数として渡す) // // サブフローから戻ってきた後、次のロジックへ // // デシジョン: 特定項目が変更されたか? // TRUEの場合: // サブフロー: カスタムオブジェクト更新_特定項目連携 (取引先ID, 変更後の特定項目値 を引数として渡す) // ...
これで、親フローを見れば全体の自動化の流れと実行順序がひと目でわかるようになる。デバッグも、どのサブフローで問題が起きているかを特定しやすくなるし、親フローのパスを追うことで、他のフローの影響を考慮する必要がなくなる。Governor Limitsに関しても、一つのトランザクション内でDMLやSOQLが実行される順序と回数を意識的にコントロールできるようになる。
これは当時の自分向けのメモだ。フローの設計は、いかに変更管理を容易にし、かつ堅牢に、そしてパフォーマンス良く動かすかという、管理者としての腕の見せ所だと今では思う。
コメント
コメントを投稿