Salesforce開発者向けガイド:Schedulable Apexによる自動化の徹底解説

背景と応用シーン

Salesforceプラットフォームは、ビジネスプロセスの自動化において非常に強力な機能を提供します。フローやプロセスビルダーは宣言的な自動化ツールとして広く利用されていますが、特定の時間や定期的な間隔でロジックを実行する必要がある場合、プログラムによるアプローチが必要となります。ここで登場するのが Schedulable Apex (スケジューラブル Apex) です。

私は Salesforce 開発者として、日々、顧客の複雑な要件を解決するためにコードを書いています。その中で、時間ベースのトリガーを必要とするタスクは非常に一般的です。例えば、以下のようなシナリオが考えられます。

  • 毎晩のデータクリーンアップ: 古くなったログレコードや不要なタスクを深夜に一括で削除する。
  • 週次のレポート生成: 毎週月曜日の朝に、先週の営業活動を集計し、関連するマネージャーにメールでサマリーを送信する。
  • 月次のデータ同期: 月末に外部のERPシステムから最新の製品価格情報を取得し、Salesforce内の価格表を更新する。
  • 契約期限のリマインダー: 契約終了日が30日以内に迫っている契約を毎日チェックし、担当者向けのフォローアップタスクを自動で作成する。
  • データの集計処理: ダッシュボードのパフォーマンスを向上させるため、詳細なトランザクションデータを夜間に集計オブジェクトにまとめる。

これらのシナリオに共通するのは、「特定のスケジュールに基づいて、バックグラウンドで処理を自動実行したい」という要求です。Schedulable Apexは、このような時間駆動型の自動化を実現するための核心的な機能であり、開発者が Salesforce の自動化能力を最大限に引き出すための不可欠なツールです。


原理説明

Schedulable Apex の仕組みは非常にシンプルです。開発者は、Salesforce が提供する Schedulable インターフェースを実装した Apex クラスを作成します。このインターフェースには、実装必須のメソッドが一つだけ定義されています。

Schedulable インターフェース

このインターフェースをクラスに実装することで、そのクラスがスケジュール可能な処理であることを Salesforce システムに伝えます。

global class MySchedulableClass implements Schedulable {
    global void execute(SchedulableContext sc) {
        // ここに実行したい処理を記述します
    }
}

execute メソッドは、スケジュールされた時刻になると Salesforce によって自動的に呼び出されます。このメソッドは SchedulableContext オブジェクトを引数として受け取ります。このオブジェクトからは、現在実行中のジョブの ID (getTriggerId()) などを取得できますが、多くの場合は使用されません。

ジョブのスケジューリング

クラスを作成しただけでは、処理は実行されません。そのクラスをいつ実行するかを Salesforce に教える必要があります。スケジューリングは主に2つの方法で行えます。

1. 開発者コンソールからのプログラム実行
System.schedule メソッドを使用して、Apex コードから直接ジョブをスケジュールします。これは最も柔軟性の高い方法です。

MySchedulableClass myJob = new MySchedulableClass();
String cronExp = '0 0 1 * * ?'; // 毎日午前1時に実行
String jobName = 'My Daily Job ' + System.now();
System.schedule(jobName, cronExp, myJob);
このメソッドは3つの引数を取ります。
  • jobName (ジョブ名): スケジュール済みジョブのリストで識別するためのユニークな名前。
  • cronExp (CRON 式): ジョブをいつ実行するかを定義する文字列。後述します。
  • schedulableClass (クラスインスタンス): Schedulable インターフェースを実装したクラスのインスタンス。

2. Salesforce UI からの設定
[設定] > [Apex クラス] に移動し、「Apexをスケジュール」ボタンをクリックすることで、GUIを通じてクラスと実行スケジュールを設定することもできます。これは、システム管理者がコードを書かずにジョブをスケジュールしたい場合に便利です。

CRON 式

CRON (クロン) 式は、ジョブの実行タイミングを定義するための標準的なフォーマットです。Salesforce の CRON 式は以下の7つの要素で構成されます。

[秒] [分] [時] [日] [月] [曜日] [年(任意)]

例えば、

  • 0 0 13 * * ?: 毎日午後1時に実行
  • 0 30 8 ? * MON-FRI: 月曜日から金曜日の毎朝8時30分に実行
  • 0 0 2 1 * ?: 毎月1日の午前2時に実行

特に重要なのは ? (指定なし) ワイルドカードです。「日」と「曜日」は同時には指定できないため、一方を指定した場合はもう一方を ? にする必要があります。


示例代码

Schedulable Apex の最も一般的な利用パターンの一つは、Batch Apex (バッチ Apex) を呼び出すことです。Schedulable クラス自体は軽量なトリガーの役割を果たし、大量のレコード処理は Batch Apex に委ねるのがベストプラクティスです。
以下に、developer.salesforce.com の公式ドキュメントで解説されている原則に基づいた、商談のフェーズが「成立」で、最終更新日から90日以上経過したものをクリーンアップ(ここでは削除)する例を示します。

1. バッチ処理を行う Batch Apex クラス

まず、実際のデータ処理ロジックを持つ Batch Apex クラスを作成します。

// 古い成立商談を削除するBatch Apexクラス
global class CleanUpOldOpportunitiesBatch implements Database.Batchable<sObject> {

    // startメソッド: 処理対象のレコードを決定するSOQLクエリを返す
    global Database.QueryLocator start(Database.BatchableContext bc) {
        // 最終更新日から90日以上経過した「Closed Won」の商談を対象とする
        Date ninetyDaysAgo = System.today().addDays(-90);
        return Database.getQueryLocator(
            'SELECT Id FROM Opportunity WHERE StageName = \'Closed Won\' AND LastModifiedDate <= :ninetyDaysAgo'
        );
    }

    // executeメソッド: startで取得したレコードの塊(chunk)ごとに処理を実行する
    global void execute(Database.BatchableContext bc, List<Opportunity> scope) {
        // このスコープ(レコードのリスト)を削除する
        // try-catchブロックでエラーハンドリングを実装することが推奨される
        try {
            delete scope;
        } catch (DmlException e) {
            System.debug('An error occurred during Opportunity deletion: ' + e.getMessage());
            // ここでカスタムログオブジェクトへの記録や、管理者に通知するロジックを実装する
        }
    }

    // finishメソッド: すべてのバッチ処理が完了した後に実行される
    global void finish(Database.BatchableContext bc) {
        // 完了通知メールを送信するなどの後処理をここに記述
        System.debug('Old Opportunity cleanup batch job finished.');
    }
}

2. Batch Apex を呼び出す Schedulable Apex クラス

次に、上記のバッチクラスを特定の時間に呼び出すための Schedulable クラスを作成します。

// CleanUpOldOpportunitiesBatchを定期的に実行するSchedulableクラス
global class ScheduledBatchable implements Schedulable {

    global void execute(SchedulableContext sc) {
        // Batch Apexクラスのインスタンスを作成
        CleanUpOldOpportunitiesBatch batchJob = new CleanUpOldOpportunitiesBatch();
        
        // バッチジョブを実行
        // 第2引数(batchSize)は、executeメソッドに一度に渡されるレコード数を指定する
        // デフォルトは200。最大2000まで指定可能。
        Database.executeBatch(batchJob, 200); 
    }
}

3. ジョブのスケジュール実行

最後に、このジョブをスケジュールします。開発者コンソールの「匿名実行ウィンドウ」で以下のコードを実行します。

// 毎週日曜日の午前3時に実行するようにスケジュールする
ScheduledBatchable myScheduledJob = new ScheduledBatchable();
String cronExpression = '0 0 3 ? * SUN';
String jobName = 'Weekly Old Opportunity Cleanup';

// システムにジョブをスケジュールする
System.schedule(jobName, cronExpression, myScheduledJob);

これで、毎週日曜日の午前3時に ScheduledBatchable クラスが実行され、それが CleanUpOldOpportunitiesBatch バッチジョブを起動し、古い商談のクリーンアップが自動的に行われるようになります。


注意事項

Schedulable Apex は強力ですが、利用する上でいくつかの重要な注意点と制限があります。

ガバナ制限 (Governor Limits)

  • スケジュールジョブ数の上限: 1つの Salesforce 組織で同時にスケジュールできるジョブの数には上限があります。通常は100件です。Limits.getLimitScheduledJobs()Limits.getScheduledJobs() で現在の使用状況を確認できます。
  • トランザクションの分離: スケジュールされた Apex ジョブは、非同期処理として実行されます。つまり、スケジューリングのトリガーとなったトランザクションとは別の、独自のガバナ制限を持つトランザクションで実行されます。これにより、同期処理の制限を気にすることなく、より多くのリソースを利用できます。

CRON 式の注意点

  • タイムゾーン: CRON 式で指定する時間は、ジョブをスケジュールしたユーザーのタイムゾーンに基づきます。複数地域のユーザーが利用する組織では、どのユーザーがスケジュールするかによって実行時間が変わる可能性があるため、注意が必要です。通常は、タイムゾーンが固定されたインテグレーション専用ユーザーでスケジュールすることが推奨されます。

テスト (Testing)

  • System.schedule の直接呼び出し不可: テストメソッド内から System.schedule を直接呼び出すことはできません。呼び出すと "System.AsyncException: The System.schedule method cannot be called in test execution." というエラーが発生します。
  • Test.startTest()Test.stopTest(): Schedulable クラスをテストするには、System.schedule の呼び出しを Test.startTest()Test.stopTest() の間に配置します。Test.stopTest() が実行されると、そのブロック内でスケジュールされた非同期処理が同期的に実行されます。これにより、テストメソッド内でジョブの実行結果を検証できます。
@isTest
static void testScheduledJob() {
    // テストデータの準備
    
    Test.startTest();
    // スケジュール処理を呼び出す
    ScheduledBatchable myJob = new ScheduledBatchable();
    String cron = '0 0 1 * * ?';
    System.schedule('Test Job', cron, myJob);
    Test.stopTest();
    
    // ジョブが実行された後の結果をアサートで検証する
    // 例えば、特定のレコードが削除されたことを確認するなど
}

エラー処理と監視

  • 堅牢なエラーハンドリング: execute メソッド全体を try-catch ブロックで囲むことは非常に重要です。予期せぬエラーでジョブ全体が失敗するのを防ぎ、エラー内容をカスタムオブジェクトに記録したり、管理者にメールで通知したりする処理を実装すべきです。
  • ジョブの監視: スケジュールしたジョブは、[設定] > [ジョブ] > [Apex ジョブ] で監視できます。ここでは、ジョブのステータス(待機中、処理中、完了、失敗など)を確認できます。

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

Schedulable Apex は、Salesforce プラットフォームにおける時間ベースの自動化を実現するための強力な機能です。定期的なデータメンテナンス、レポート作成、システム連携など、その応用範囲は多岐にわたります。開発者としてこの機能を最大限に活用するためには、以下のベストプラクティスを心掛けることが重要です。

  • 責務の分離 (Separation of Concerns)

    Schedulable クラスの役割は、あくまで「処理を適切なタイミングで開始するトリガー」に限定すべきです。実際の重いデータ処理は、Batch Apex や Queueable Apex に委譲しましょう。これにより、コードの可読性、再利用性、保守性が向上します。

  • 冪等性 (Idempotency) の確保

    ジョブは、何らかの理由で複数回実行されても問題が発生しないように設計する(冪等性を持たせる)ことが理想です。例えば、処理済みのレコードにはフラグを立て、次回実行時にはそのレコードをスキップするなどの工夫が考えられます。

  • 設定の外部化 (Externalize Configuration)

    CRON 式、SOQL クエリの条件、バッチサイズなどの設定値をコード内にハードコーディングするのではなく、Custom Metadata Types (カスタムメタデータ型) や Custom Settings (カスタム設定) に格納しましょう。これにより、コードを修正・デプロイすることなく、システム管理者が設定を柔軟に変更できるようになります。

  • 一括処理 (Bulkification) の徹底

    呼び出される処理(特に Batch Apex)は、常に大量のデータを効率的に扱えるように設計する必要があります。SOQL や DML 操作をループ内で実行しないなど、Salesforce のガバナ制限を意識したコーディングを徹底してください。

  • 能動的な監視と通知

    ジョブが失敗した際に、管理者が Apex ジョブの画面を毎回確認しにいくのは非効率です。finish メソッドや catch ブロック内で、ジョブの失敗や異常を検知し、Chatter 投稿、メール通知、Platform Event の発行などによって能動的に関係者に通知する仕組みを構築しましょう。

これらのプラクティスに従うことで、単に動作するだけでなく、スケーラブルで保守性が高く、信頼性のある自動化ソリューションを構築することができます。Schedulable Apex を使いこなし、Salesforce の真の力を引き出しましょう。

コメント