Salesforce開発者ガイド:FlowからApexを呼び出し、高度な自動化を実現する

背景と応用シナリオ

Salesforce Flow (フロー)は、Salesforceプラットフォームにおける宣言的な自動化ツールの中心的存在です。クリック操作で複雑なビジネスプロセスを自動化できるため、多くの管理者や開発者にとって強力な武器となっています。しかし、標準のFlow要素だけでは解決できない、より高度な要件に直面することもあります。

例えば、以下のようなシナリオが考えられます。

  • 複雑なデータ処理: 複数のオブジェクトにまたがる複雑な計算ロジックや、標準のFlow数式では表現が難しいデータ操作。
  • 外部システム連携: 外部サービスのAPIを呼び出し(コールアウト)、データを取得または送信する必要がある場合。
  • 一括処理(Bulkification)の精密な制御: 大量レコードに対するDML操作やSOQLクエリで、Governor Limits (ガバナ制限)を回避するための高度な制御が必要な場合。
  • 標準でサポートされていない操作: 例えば、レコードの共有ルールをプログラムで操作したり、特定のメタデータを操作したりするなど、Apexでしか実現できない処理。

このような時、Salesforce開発者の出番です。Flowの宣言的な利便性と、Apex (エイペックス:Salesforceのプログラミング言語)の強力な処理能力を組み合わせることで、これらの課題をエレガントに解決できます。本記事では、Salesforce開発者の視点から、FlowからApexを呼び出す「呼び出し可能なアクション(Invocable Action)」の実装方法、その原理、そしてベストプラクティスについて詳細に解説します。


原理説明

FlowからApexクラスのメソッドを呼び出すためには、特定のルールに従ってApexコードを記述する必要があります。その中心となるのが @InvocableMethod アノテーションです。

@InvocableMethod アノテーション

このアノテーションを付与されたメソッドは、Flow Builderから「Apexアクション」要素として認識され、呼び出すことが可能になります。@InvocableMethod にはいくつかの重要な制約と特徴があります。

  • メソッドは public static である必要があります。
  • メソッドの戻り値は、プリミティブデータ型のリスト、sObjectのリスト、またはApexで定義されたカスタムクラスのインスタンスのリストでなければなりません。voidを返すことも可能です。
  • メソッドは引数を1つだけ取ることができます。この引数のデータ型も、プリミティブデータ型のリスト、sObjectのリスト、またはApexで定義されたカスタムクラスのインスタンスのリストでなければなりません。

この「引数と戻り値が必ずリスト(List)でなければならない」という制約は、SalesforceのBulkification (一括処理)の設計思想に基づいています。Flowが一度に複数のレコードを処理する場合でも、Apexメソッドはそれらをリストとして一度に受け取り、効率的に処理することができます。これにより、ループ内でSOQLクエリやDMLステートメントを発行することなく、Governor Limitsを遵守したコードを記述することが容易になります。

@InvocableVariable アノテーション

引数や戻り値にカスタムApexクラスを使用する場合、そのクラスのプロパティをFlow Builder内で個別の変数としてマッピングできるようにするために @InvocableVariable アノテーションを使用します。

  • このアノテーションを付与されたプロパティは、Flow Builderの入力・出力マッピング画面に表示されます。
  • label 属性を指定することで、Flow Builder上での表示名を分かりやすく設定できます。
  • description 属性で説明を追加することも可能です。
  • required=true を設定すると、Flowでその入力が必須になります。

これらのアノテーションを組み合わせることで、FlowとApex間でデータを安全かつ効率的に受け渡すための明確なインターフェースを定義することができます。Flowは入力としてリストをApexに渡し、Apexは処理結果をリストとしてFlowに返す、という一貫したデータフローが実現されます。


サンプルコード

ここでは、取引先(Account)のIDリストを受け取り、関連する商談(Opportunity)の中から最も金額の大きいものを検索し、その商談名と金額を返すというシンプルなApexアクションの例を見ていきましょう。このコードはSalesforceの公式ドキュメントに基づいています。

1. Apexクラスの作成

まず、Flowから呼び出されるApexクラスを作成します。このクラスには、@InvocableMethodを持つメソッドと、データの入出力を定義するための内部クラスが含まれます。

public class AccountProcessor {

    // Flowから呼び出されるメインのメソッド
    // label属性でFlow Builderでの表示名を定義
    // description属性で処理内容の説明を追加
    @InvocableMethod(label='Get Top Opportunity' description='Finds the highest-value Opportunity for a given set of Accounts.' category='Account')
    public static List<OpportunityResult> getTopOpportunity(List<AccountRequest> requests) {

        // 返却用の結果リストを初期化
        List<OpportunityResult> results = new List<OpportunityResult>();
        // 入力リクエストから取引先IDのリストを抽出
        List<Id> accountIds = new List<Id>();
        for (AccountRequest req : requests) {
            accountIds.add(req.accountId);
        }

        // 関連する商談を一括で取得するためのSOQLクエリ
        // Governor Limitsを回避するため、ループの外で一度だけ実行する
        Map<Id, Account> accountsWithOpps = new Map<Id, Account>([
            SELECT Id, (SELECT Name, Amount FROM Opportunities ORDER BY Amount DESC LIMIT 1)
            FROM Account
            WHERE Id IN :accountIds
        ]);

        // 各リクエストに対して処理を実行
        for (AccountRequest req : requests) {
            OpportunityResult result = new OpportunityResult();
            Account acct = accountsWithOpps.get(req.accountId);

            // 関連する商談が存在するかどうかをチェック
            if (acct != null && !acct.Opportunities.isEmpty()) {
                Opportunity topOpp = acct.Opportunities[0];
                result.opportunityName = topOpp.Name;
                result.opportunityAmount = topOpp.Amount;
            } else {
                // 商談が見つからない場合はnullを設定
                result.opportunityName = null;
                result.opportunityAmount = null;
            }
            results.add(result);
        }

        return results;
    }

    // Flowからの入力を受け取るための内部クラス
    public class AccountRequest {
        @InvocableVariable(label='Account ID' description='The ID of the Account to process.' required=true)
        public Id accountId;
    }

    // Flowへ結果を返すための内部クラス
    public class OpportunityResult {
        @InvocableVariable(label='Top Opportunity Name' description='Name of the highest-value Opportunity.')
        public String opportunityName;

        @InvocableVariable(label='Top Opportunity Amount' description='Amount of the highest-value Opportunity.')
        public Decimal opportunityAmount;
    }
}

コードの解説

  • AccountProcessorクラス: このクラス全体がApexアクションのコンテナです。
  • @InvocableMethod getTopOpportunityメソッドがFlowから呼び出し可能であることを示します。labeldescriptioncategory属性は、Flow Builderの「アクション」要素選択画面で表示され、管理者がアクションを簡単に見つけられるようにします。
  • List<AccountRequest> requests メソッドの唯一の引数です。Flowから渡されたデータがこのリストに格納されます。たとえFlowから1つの取引先IDしか渡されなくても、Apex側では必ずリストとして受け取ります。
  • List<OpportunityResult> メソッドの戻り値です。処理結果がこのリストに格納されてFlowに返されます。
  • AccountRequestクラス: Flowからの入力を定義する内部クラスです。accountIdプロパティには@InvocableVariableアノテーションが付与されており、Flow Builderで「Account ID」というラベルの入力項目として表示されます。
  • OpportunityResultクラス: Flowへの出力を定義する内部クラスです。opportunityNameopportunityAmountプロパティも同様に@InvocableVariableを持ち、Flow Builderで出力変数としてマッピングできます。
  • 一括処理(Bulkification):コードの中では、まず入力された全ての取引先IDをリストに集め、WHERE Id IN :accountIds句を使った1回のSOQLクエリで必要なデータを全て取得しています。これにより、レコード数が増えてもSOQLクエリの発行回数が1回で済むため、Governor Limitsに抵触するリスクを大幅に低減できます。

注意事項

FlowからApexを呼び出す際には、いくつかの重要な点に注意する必要があります。これらを理解しておくことで、安定的でスケーラブルな自動化を構築できます。

権限と実行コンテキスト

Flowから呼び出されたApexアクションは、デフォルトでシステムコンテキスト (System Context)で実行されます。これは、Apexコードが実行ユーザのプロファイルや権限セット、共有ルールを無視して、組織の全てのデータにアクセスできることを意味します。これは非常に強力ですが、同時に注意が必要です。意図せずユーザがアクセスすべきでないデータにアクセスしたり、更新したりする可能性があるため、Apexコード内でのデータアクセス制御は慎重に設計する必要があります。もしユーザの権限を尊重したい場合は、Apexクラスの宣言時にwith sharingキーワードを使用することを検討してください。

Governor Limits (ガバナ制限)

Apexアクションは、単一のトランザクション内で他のFlow要素と共に実行されます。そのため、Flow全体の処理がSalesforceのGovernor Limitsの対象となります。

  • SOQLクエリ: トランザクション全体で合計100回まで。
  • DMLステートメント: トランザクション全体で合計150回まで。
  • CPU時間: トランザクション全体で合計10,000ミリ秒まで。

前述の通り、Apexコードは必ず一括処理を念頭に置いて設計する必要があります。ループ内でSOQLやDMLを実行するコードは絶対に避け、リストやマップを活用して効率的な処理を実装してください。

エラー処理

Apexコード内でハンドルされなかった例外(Exception)が発生すると、Flowは実行時エラーとなり停止します。これを防ぐためには、Apex側でtry-catchブロックを使用して例外を適切に捕捉し、処理することが推奨されます。

さらに、Flow Builder側でも堅牢なエラーハンドリングが可能です。Apexアクション要素からフォルトコネクタ (Fault Connector)を引くことで、アクションが失敗した場合の代替パスを定義できます。例えば、エラー内容をカスタムオブジェクトに記録したり、システム管理者にメールで通知したりする処理をフォルトパスに組み込むことができます。

コールアウトに関する考慮事項

@InvocableMethodを持つメソッドから外部APIへのコールアウトを行うことも可能です。その場合、メソッドに(callout=true)属性を追加する必要があります。

@InvocableMethod(label='My Action' callout=true)
public static void myAction(List<String> params) {
    // Callout logic here
}

ただし、1つのトランザクション内で未確定のDML操作(INSERT, UPDATEなど)がある状態でコールアウトを実行することはできません。コールアウトを行う場合は、トランザクションの制御に注意が必要です。


まとめとベストプラクティス

FlowからApexを呼び出す機能は、宣言的ツールとプログラム的ツールの長所を組み合わせた、Salesforceプラットフォームの強力なハイブリッド開発モデルを象徴しています。これにより、開発者は複雑なロジックを再利用可能で管理しやすいコンポーネントとしてカプセル化し、管理者やコンサルタントがFlow Builder上でそれらを柔軟に活用できるようになります。

最後に、成功のためのベストプラクティスをまとめます。

  1. 単一責任の原則: 1つのApexアクションは、1つの明確な責務を持つべきです。例えば、「取引先の格付けを更新する」「外部システムから在庫を確認する」など、具体的で再利用しやすい単位でメソッドを作成します。
  2. 常に一括処理を意識する: メソッドの設計は、常に複数のレコードが一度に渡されることを前提とします。入力も出力もリストであることを忘れないでください。
  3. 明確な命名と説明: @InvocableMethod@InvocableVariablelabelおよびdescription属性を必ず設定しましょう。これにより、開発者でないユーザもFlow Builder上でそのアクションが何をするものなのかを容易に理解できます。
  4. 堅牢なエラーハンドリング: Apex内でのtry-catchと、Flow側でのフォルトコネクタの両方を活用し、予期せぬエラーが発生してもプロセスが安全に停止または回復できるように設計します。
  5. テストカバレッジ: 呼び出し可能なApexメソッドも、通常のApexクラスと同様に単体テストが必須です。最低75%のコードカバレッジを維持し、ロジックの品質を担保してください。

これらのプラクティスに従うことで、FlowとApexを組み合わせた高度で信頼性の高い自動化ソリューションを構築し、ビジネスの要求に迅速かつ効果的に応えることができるでしょう。

コメント