Salesforce Apexクラス入門:開発者のための基礎とベストプラクティス

執筆者:Salesforce 開発者


背景と応用シナリオ

Salesforceプラットフォームでカスタムのビジネスロジックを実装する際、中心的な役割を果たすのが Apex (エイペックス) です。Apexは、Javaに似た強固な型付けのオブジェクト指向プログラミング言語であり、Salesforceのサーバ上で実行されます。そして、そのApexコードの基本的な構成単位が Apex Class (Apexクラス) です。

Apexクラスは、再利用可能なコードの集合を定義するための設計図やテンプレートとして機能します。開発者はこのクラスを用いて、複雑なビジネスプロセスを自動化したり、外部システムと連携したり、ユーザーインターフェースを拡張したりと、Salesforceの標準機能だけでは実現不可能な、多岐にわたる要件に対応できます。

主な応用シナリオ:

  • カスタムビジネスロジックの実装: トリガ(Triggers)と連携し、レコードの作成・更新・削除時に複雑なデータ検証や関連レコードの自動更新処理を実行します。
  • カスタムコントローラ: VisualforceページやAura/Lightning Web Componentsのサーバサイドコントローラとして機能し、画面の表示ロジックやデータ操作を担います。
  • バッチ処理: Batch Apex (バッチApex) を使用して、数百万件のレコードを非同期で効率的に処理します。
  • Webサービスの提供と連携: REST APIやSOAP APIを公開するクラスを作成し、外部システムからのリクエストを受け付けたり、逆に外部システムのAPIをコールしたりします。
  • 非同期処理: FutureメソッドやQueueable Apexを利用して、時間のかかる処理をバックグラウンドで実行し、ユーザーエクスペリエンスを向上させます。

このように、ApexクラスはSalesforce開発におけるあらゆる場面で活用される、必須の知識です。本記事では、開発者の視点からApexクラスの基本原理、具体的なコード例、そして実践で役立つ注意事項やベストプラクティスについて詳しく解説します。


原理説明

Apexクラスを理解するためには、オブジェクト指向プログラミング(Object-Oriented Programming, OOP)の基本的な概念を把握することが重要です。

Apexクラスとは?

Class (クラス) は、オブジェクトの「設計図」です。どのようなデータ(プロパティ)を持ち、どのような振る舞い(メソッド)をするかを定義します。例えば、「自動車」というクラスを考えると、そのプロパティには「色」「メーカー」「最高速度」などがあり、メソッドには「走る」「止まる」「方向転換する」といった操作が含まれます。

一方、Object (オブジェクト) は、クラスに基づいて作成された「実体」です。先ほどの例で言えば、「赤いフェラーリ」や「青いトヨタ」が具体的なオブジェクトに該当します。これらはすべて「自動車」クラスのインスタンス(実例)です。Apexでは、new キーワードを使ってクラスからオブジェクトを生成します。

クラスの基本構造

Apexクラスは、アクセス修飾子、class キーワード、クラス名、そして波括弧 {} で囲まれた本体で構成されます。

public class MyFirstClass {
    // ここにプロパティ(変数)やメソッド(処理)を定義します
}
  • アクセス修飾子 (Access Modifiers): クラスやそのメンバー(メソッド、変数)がどこからアクセス可能かを制御します。
    • private: そのクラス内からのみアクセス可能です。
    • public: 同じ名前空間内のどこからでもアクセス可能です。最も一般的に使用されます。
    • global: すべてのApexコードからアクセス可能です。主に管理パッケージやWebサービスで外部に公開する際に使用します。

メソッドとプロパティ

クラスの本体には、主にプロパティとメソッドを定義します。

  • プロパティ (Properties): またはメンバ変数とも呼ばれ、オブジェクトの状態を保持するデータです。例えば、取引先責任者を表すクラスなら、String firstName;String email; といったプロパティを持ちます。
  • メソッド (Methods): オブジェクトの振る舞いを定義するコードブロックです。メソッドは特定の処理を実行し、値を返すこともできます。例えば、void sendWelcomeEmail()Decimal calculateTotalPrice(Integer quantity, Decimal unitPrice) といったメソッドが考えられます。

静的 (Static) vs インスタンス (Instance)

メソッドやプロパティには static キーワードを付与することができます。

  • インスタンスメンバー: static キーワードがないメンバーです。これらにアクセスするには、まずクラスからオブジェクトをインスタンス化(new で生成)する必要があります。各オブジェクトは、それぞれ独自のプロパティ値を持つことができます。
  • 静的メンバー: static キーワードが付与されたメンバーです。これらはクラスに直接属しており、オブジェクトをインスタンス化することなく クラス名.メソッド名 の形式で呼び出せます。静的メンバーは、すべてのインスタンスで共有されます。ユーティリティメソッドなど、特定のオブジェクトの状態に依存しない処理に適しています。

示例代码

ここでは、Salesforceの公式ドキュメントにある例を参考に、特定の取引先に関連するすべての取引先責任者にメールを送信するというシナリオのApexクラスを作成し、それを実行する方法を示します。

クラスの定義 (EmailManager.cls)

このクラスは、特定の取引先IDを受け取り、関連するすべての取引先責任者のメールアドレスを更新し、メールを送信する単一のpublicメソッドを持ちます。

// publicクラスとして定義し、他のApexコードからアクセス可能にします。
public class EmailManager {
    // publicかつstaticなメソッドとして定義します。
    // staticにすることで、クラスをインスタンス化せずに `EmailManager.sendMailToContacts(...)` のように直接呼び出せます。
    // パラメータとして、取引先(Account)のIDリストとメールの件名、本文を受け取ります。
    public static void sendMailToContacts(List<ID> accountIds, String subject, String body) {
        
        // メールの送信先となる取引先責任者のメールアドレスを格納するリストを初期化します。
        List<String> toAddresses = new List<String>();
        
        // 指定された取引先IDに紐づく取引先責任者(Contact)を問い合わせるSOQLクエリ。
        // Email項目がnullでないレコードのみを対象とします。
        // 一括処理(Bulkification)のため、WHERE句で `IN` を使用しています。
        List<Contact> contacts = [SELECT Id, Email FROM Contact WHERE AccountId IN :accountIds AND Email != null];
        
        // 問い合わせ結果の取引先責任者リストをループ処理します。
        for(Contact con : contacts) {
            // 各取引先責任者のメールアドレスをリストに追加します。
            toAddresses.add(con.Email);
        }
        
        // 送信するメールオブジェクトを作成します。
        Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
        
        // 宛先アドレスリストを設定します。
        mail.setToAddresses(toAddresses);
        
        // メールの件名を設定します。
        mail.setSubject(subject);
        
        // メールの本文(プレーンテキスト形式)を設定します。
        mail.setPlainTextBody(body);
        
        // メールの送信を実行します。
        // governor limit(ガバナ制限)により、1トランザクションあたりのメール送信数には上限があります。
        // sendEmailメソッドはリストを受け取ることも可能で、その方が効率的です。
        if (!toAddresses.isEmpty()) {
            Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
        }
    }
}

クラスメソッドの呼び出し

作成した `EmailManager` クラスの `sendMailToContacts` メソッドを、開発者コンソールの匿名実行(Anonymous Apex)ウィンドウから呼び出してみます。

// メールを送信したい取引先の名前を指定
String accountName = 'GenePoint';

// 指定した名前の取引先を検索します。
// 複数のレコードが返る可能性があるため、リストで結果を受け取ります。
List<Account> accounts = [SELECT Id FROM Account WHERE Name = :accountName LIMIT 1];

// 取引先が見つかった場合のみ処理を実行します。
if (!accounts.isEmpty()) {
    // 取引先のIDをリストに格納します。
    List<ID> accountIds = new List<ID>();
    accountIds.add(accounts[0].Id);

    // メールの件名と本文を定義します。
    String mailSubject = 'Important Update';
    String mailBody = 'This is an important update for all contacts.';

    // EmailManagerクラスの静的メソッドを呼び出します。
    // クラス名.メソッド名() の形式でアクセスします。
    EmailManager.sendMailToContacts(accountIds, mailSubject, mailBody);

    // 成功メッセージをデバッグログに出力します。
    System.debug('Email sent successfully to contacts of ' + accountName);
} else {
    // 取引先が見つからなかった場合のメッセージをデバッグログに出力します。
    System.debug('Account not found: ' + accountName);
}

このコードを実行すると、'GenePoint'という名前の取引先に紐づくすべての取引先責任者に対してメールが一括で送信されます。


注意事項

Apexクラスを開発・運用する上では、Salesforceプラットフォーム特有の制約やルールを遵守する必要があります。

ガバナ制限 (Governor Limits)

Apexはマルチテナント環境で実行されるため、特定の組織がリソースを独占しないように、Governor Limits (ガバナ制限) と呼ばれる厳格な実行制限が設けられています。これを考慮しないコードは、本番環境で予期せぬエラーを引き起こします。

  • SOQLクエリの発行回数: 1トランザクションあたり100回まで。
  • DMLステートメントの実行回数: 1トランザクションあたり150回まで。
  • 合計ヒープサイズ: 同期処理で6MB、非同期処理で12MBまで。
  • CPU実行時間: 同期処理で10,000ミリ秒、非同期処理で60,000ミリ秒まで。

特に、ループ内でSOQLクエリやDML文を実行することは、最も典型的なガバナ制限違反の原因です。必ずループの外でデータを一括処理するように設計してください(Bulkification)。

セキュリティと共有設定

Apexクラスの実行コンテキストを定義するために、共有キーワードを指定することが重要です。

  • with sharing: クラスは現在のユーザーの共有ルールに基づいて動作します。ユーザーがアクセス権を持たないレコードは、参照・更新できません。セキュリティを担保するための基本です。
  • without sharing: クラスは共有ルールを無視して動作し、システムコンテキストですべてのレコードにアクセスできます。権限昇格が必要な特定の処理でのみ、慎重に使用する必要があります。
  • 指定なし(デフォルト): 呼び出し元のコンテキストを引き継ぎます。例えば、トリガから呼ばれた場合はwithout sharing、Visualforceコントローラから呼ばれた場合はwith sharingとして動作することがありますが、意図しない動作を避けるため、常に明示的に指定することが推奨されます。

例外処理 (Exception Handling)

予期せぬエラー(例:必須項目がnullのレコードを挿入しようとする、レコードが見つからない)が発生した場合に備え、try-catch-finally ブロックを使用して適切にエラーを処理することが不可欠です。

try {
    // 例外が発生する可能性のあるコード
    insert myAccount;
} catch (DmlException e) {
    // エラーをキャッチして処理
    System.debug('DML failed: ' + e.getMessage());
    // ユーザーへのフィードバックや、エラーログの記録など
} finally {
    // 成功・失敗にかかわらず常に実行されるコード
}

適切な例外処理は、トランザクションの完全なロールバックを防ぎ、ユーザーに分かりやすいエラーメッセージを提供し、デバッグを容易にします。


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

本記事では、Salesforce開発の中核をなすApexクラスの基本概念から実践的なコード例、そして注意点までを解説しました。効果的で保守性の高いApexコードを書くためには、以下のベストプラクティスを常に意識することが重要です。

  1. 一括処理 (Bulkification) を徹底する: コードが単一レコードだけでなく、複数レコードのリストを効率的に処理できるように設計します。ループ内でのSOQL/DMLは絶対に避けてください。
  2. 単一責任の原則 (Single Responsibility Principle) を守る: 1つのクラスには1つの責任だけを持たせましょう。例えば、トリガロジックを処理するクラス、ユーティリティメソッドを提供するクラス、外部サービスと連携するクラスなど、役割を明確に分離することで、コードの可読性、再利用性、テスト容易性が向上します。
  3. 命名規則を統一する: クラス名、メソッド名、変数名は、その役割が明確にわかるように命名します。(例: AccountService, calculateDiscounts(), activeContacts
  4. テストクラスを作成する: 本番環境にデプロイするためには、Apexコードの75%以上をカバーするテストクラスが必須です。テストはコードの品質を保証し、将来の変更による意図しない不具合(リグレッション)を防ぐために不可欠です。
  5. ハードコーディングを避ける: IDやURL、特定の文字列などをコード内に直接書き込む(ハードコーディングする)のではなく、カスタムメタデータやカスタム設定、カスタム表示ラベルを使用して管理し、柔軟性と保守性を高めましょう。

Apexクラスは、Salesforceプラットフォームの能力を最大限に引き出すための強力なツールです。これらの原理とベストプラクティスを習得し、堅牢でスケーラブルなソリューションを構築していきましょう。

コメント