Salesforceカスタムオブジェクト開発者向け徹底解説

背景と応用シナリオ

Salesforce開発者の皆さん、こんにちは。日々の開発業務において、私たちが最も頻繁に扱うものの一つが「オブジェクト」です。Salesforceには、Account (取引先)、Contact (取引先責任者)、Opportunity (商談) といった強力なStandard Objects (標準オブジェクト)が標準で備わっており、多くのビジネス要件をカバーできます。

しかし、現実のビジネスは多種多様です。製造業の品質管理プロセス、大学の履修科目管理、不動産業の物件情報管理など、標準オブジェクトだけでは表現しきれない独自のデータモデルが必要になる場面が必ず訪れます。

このような固有の要件に応えるための強力なソリューションが Custom Objects (カスタムオブジェクト) です。カスタムオブジェクトは、いわばSalesforceプラットフォーム上に自社専用のデータベーステーブルを作成する機能です。これにより、ビジネスプロセスに完全に合致したデータ構造を定義し、Salesforceを単なるCRMツールから、ビジネス全体の基盤システムへと昇華させることが可能になります。

例えば、以下のようなシナリオでカスタムオブジェクトは絶大な効果を発揮します:

  • プロジェクト管理:「プロジェクト」というカスタムオブジェクトを作成し、「タスク」や「マイルストーン」といった関連オブジェクトと連携させる。
  • 資産管理:社内のPCや車両などの資産を「資産」オブジェクトで管理し、使用者やメンテナンス履歴を追跡する。
  • 採用管理:「候補者」や「求人」オブジェクトを作成し、選考プロセス全体をSalesforce上で一元管理する。

開発者として、私たちはこのカスタムオブジェクトをApexコードやSOQLを通じて自在に操作することで、複雑なビジネスロジックを実装し、自動化を進め、ユーザーに価値を提供していくのです。本記事では、開発者の視点からカスタムオブジェクトの原理を深く掘り下げ、具体的なコード例を交えながら、その活用方法と注意点を徹底的に解説します。


原理説明

開発者にとって、カスタムオブジェクトは単なるUI上の設定項目ではありません。それはApex、SOQL、APIを通じて対話するべき、明確な定義を持つデータ構造です。その核心を理解することが、高品質な開発の第一歩となります。

API参照名 (API Name)

全てのカスタムオブジェクトとカスタム項目には、ユーザーが見る「表示ラベル」とは別に、プログラムからアクセスするためのユニークなAPI Name (API参照名)が与えられます。開発者が最も意識すべきはこのAPI参照名です。カスタムオブジェクトやカスタム項目のAPI参照名には、必ず末尾に `__c` (アンダースコア2つ + c) というサフィックスが付きます。これは、Salesforceの標準のオブジェクトや項目と区別するための重要なルールです。

  • 標準オブジェクトのAPI参照名: `Account`, `Contact`
  • カスタムオブジェクトのAPI参照名: `Project__c`, `Asset__c`
  • 標準項目のAPI参照名: `Name`, `AnnualRevenue` (Accountオブジェクト上)
  • カスタム項目のAPI参照名: `Project_Status__c`, `Purchase_Date__c` (Asset__cオブジェクト上)

この `__c` サフィックスは、SOQLクエリやApexコード内でオブジェクトや項目を指定する際に必須となります。

sObjectとしての表現

Apexの世界では、Salesforceの全てのオブジェクト(標準・カスタム問わず)は sObject 型として抽象化されます。そして、作成したカスタムオブジェクトは、Apexのファーストクラス市民として、そのAPI参照名を型名として直接利用できます。

例えば、`Project__c` というカスタムオブジェクトを作成した場合、Apex内では以下のように `Project__c` 型の変数を宣言し、インスタンス化できます。これは、JavaやC#におけるクラスのインスタンス化と非常によく似ています。

// Project__c 型の変数を宣言し、新しいレコードのインスタンスを作成
Project__c newProject = new Project__c();

// 項目に値を設定 (API参照名を使用)
newProject.Name = 'New Website Development';
newProject.Status__c = 'Not Started';
newProject.Budget__c = 50000;

このsObjectインスタンスをDMLステートメント(`insert`, `update`など)に渡すことで、データベースへの操作が実行されます。

リレーション (Relationship)

オブジェクト間の関連付けは、データモデルの根幹をなします。開発者は、特に以下の2つのリレーションシップの違いを正確に理解しておく必要があります。

  • Lookup Relationship (参照関係):

    オブジェクト同士を緩やかに結合します。子オブジェクトには、親オブジェクトのレコードIDを保持するための項目(API参照名は `__c` で終わる)が作成されます。親がいなくても子は存在でき、セキュリティ設定も独立しています。プログラム的には、親レコードのIDをこの参照項目に設定することでリレーションを構築します。

  • Master-Detail Relationship (主従関係):

    オブジェクト同士を密に結合します。子は親(主)の一部と見なされ、親レコードが削除されると子(従)レコードも連鎖的に削除されます(カスケード削除)。子のセキュリティ設定は親に依存します。この関係性により、親オブジェクト上で子レコードの値を集計する積み上げ集計項目 (Roll-Up Summary Field)が利用可能になります。これは非常に強力な機能ですが、データ構造が硬直化しやすいため、設計には注意が必要です。


サンプルコード

ここでは、「プロジェクト」を管理するためのカスタムオブジェクト `Project__c` を例に、具体的なコードを見ていきましょう。 `Project__c` には以下のカスタム項目があるとします。

  • `Status__c` (Picklist: 'Planning', 'In Progress', 'Completed', 'On Hold')
  • `Budget__c` (Currency)
  • `DueDate__c` (Date)
  • `Project_Owner__c` (Lookup to User)

SOQLによるデータ問い合わせ

SOQL (Salesforce Object Query Language) は、Salesforceデータベースからレコードを取得するための言語です。カスタムオブジェクトを問い合わせる際は、`__c` を含めたAPI参照名を使用します。

// 進行中の全てのプロジェクトを予算の降順で取得する
List inProgressProjects = [
    SELECT 
        Id,                 // レコードID
        Name,               // 標準の名前項目
        Status__c,          // カスタム項目: 状況
        Budget__c,          // カスタム項目: 予算
        DueDate__c,         // カスタム項目: 期日
        Project_Owner__r.Name // リレーション先の項目 (所有者の名前)
    FROM 
        Project__c          // カスタムオブジェクトの指定
    WHERE 
        Status__c = 'In Progress'
    ORDER BY 
        Budget__c DESC
];

// 取得したプロジェクトの件数をデバッグログに出力
System.debug('進行中のプロジェクト数: ' + inProgressProjects.size());

// 1件目のプロジェクトの情報を出力
if (!inProgressProjects.isEmpty()) {
    System.debug('最初のプロジェクト名: ' + inProgressProjects[0].Name);
    System.debug('プロジェクトオーナー: ' + inProgressProjects[0].Project_Owner__r.Name);
}

注釈: `Project_Owner__c` は参照項目ですが、クエリ内で `Project_Owner__r.Name` のように `__c` を `__r` に変えることで、関連先の親レコード(この場合はUserオブジェクト)の項目にアクセスできます。これはリレーションクエリと呼ばれ、非常に効率的です。

Apex DMLによるレコード作成 (Bulk対応)

Apexでレコードを作成する際は、DML (Data Manipulation Language) ステートメント(`insert`)を使用します。ガバナ制限を回避するため、必ずリストにまとめて一括処理(バルク化)する癖をつけましょう。

// 作成するプロジェクトのリストを初期化
List projectsToCreate = new List();

// 1つ目のプロジェクト
Project__c project1 = new Project__c();
project1.Name = 'Q1 Marketing Campaign';
project1.Status__c = 'Planning';
project1.Budget__c = 25000;
project1.DueDate__c = Date.today().addMonths(3);
project1.Project_Owner__c = UserInfo.getUserId(); // 現在のユーザーをオーナーに設定
projectsToCreate.add(project1);

// 2つ目のプロジェクト
Project__c project2 = new Project__c();
project2.Name = 'Internal System Upgrade';
project2.Status__c = 'In Progress';
project2.Budget__c = 120000;
project2.DueDate__c = Date.today().addMonths(6);
project2.Project_Owner__c = UserInfo.getUserId();
projectsToCreate.add(project2);

// try-catchブロックでDMLエラーを処理
try {
    // リストを一度のDMLコールでデータベースに挿入 (バルク処理)
    insert projectsToCreate;
    System.debug(projectsToCreate.size() + '件のプロジェクトが正常に作成されました。');
} catch (DmlException e) {
    // DMLエラーが発生した場合の処理
    System.debug('プロジェクトの作成中にエラーが発生しました: ' + e.getMessage());
}

Apex DMLによるレコード更新 (Bulk対応)

レコードの更新も作成と同様に、まず対象レコードをSOQLで取得し、変更を加えてからリストにまとめ、一度の `update` DMLで処理します。絶対にループ内でDMLを実行してはいけません。

// 更新対象のプロジェクトをリストとして初期化
List projectsToUpdate = new List();

// "Planning"ステータスのプロジェクトを全て取得
for (Project__c proj : [SELECT Id, Status__c, DueDate__c FROM Project__c WHERE Status__c = 'Planning']) {
    // ステータスを 'In Progress' に変更
    proj.Status__c = 'In Progress';
    // 期日を1週間延長
    if (proj.DueDate__c != null) {
        proj.DueDate__c = proj.DueDate__c.addDays(7);
    }
    // 更新用リストに追加
    projectsToUpdate.add(proj);
}

// 更新対象のレコードが存在する場合のみDMLを実行
if (!projectsToUpdate.isEmpty()) {
    try {
        // リストを一度のDMLコールで更新
        update projectsToUpdate;
        System.debug(projectsToUpdate.size() + '件のプロジェクトが更新されました。');
    } catch (DmlException e) {
        System.debug('プロジェクトの更新中にエラーが発生しました: ' + e.getMessage());
    }
}

注意事項

カスタムオブジェクトをプログラムから扱う際には、プラットフォームの制約とセキュリティモデルを常に意識する必要があります。

権限と共有 (Permissions and Sharing)

Apexクラスはデフォルトではシステムコンテキスト(`without sharing`)で実行されることが多く、オブジェクトや項目に対する実行ユーザーのアクセス権を無視して動作します。これは意図しないデータ漏洩や変更につながる可能性があります。

  • CRUD/FLS: ユーザープロファイルや権限セットで定義された、オブジェクトに対するCRUD (Create, Read, Update, Delete)権限と、項目に対するFLS (Field-Level Security)をコードが尊重すべきか考慮が必要です。
  • Sharingキーワード: クラス定義時に `with sharing` を指定すると、そのクラスは実行ユーザーの共有ルールを尊重して動作します。`without sharing` は共有ルールを無視し、`inherited sharing` は呼び出し元の共有設定を継承します。機密データを扱う場合は `with sharing` の使用を原則としましょう。
  • スキーマチェック: コード内で動的に権限をチェックすることもベストプラクティスです。`Schema`クラスのメソッド(例: `SObjectType.Project__c.isAccessible()` や `Schema.sObjectType.Project__c.fields.Budget__c.isUpdateable()`)を使用することで、DML実行前にユーザーが必要な権限を持っているかを確認できます。

ガバナ制限 (Governor Limits)

Salesforceはマルチテナント環境であるため、1つのトランザクション内で使用できるリソースには厳格な制限(Governor Limits)が設けられています。カスタムオブジェクトを扱う開発者が特に注意すべきは以下の制限です。

  • SOQLクエリ発行回数: 1トランザクションあたり100回
  • DMLステートメント発行回数: 1トランザクションあたり150回
  • DMLで処理されるレコード総数: 1トランザクションあたり10,000件

これらの制限に抵触しないために、前述のサンプルコードで示した「バルク化」が不可欠です。ループ内でSOQLやDMLを発行するコードは、データ量が増えた際に容易に制限を超過し、致命的なエラーを引き起こします。

エラーハンドリング (Error Handling)

DML操作は常に成功するとは限りません。必須項目の欠落、入力規則違反、ユニーク制約違反など、様々な理由で失敗する可能性があります。`try-catch` ブロックで `DmlException` を捕捉するのは基本ですが、部分的な成功を許容したい場合は `Database` クラスのメソッドが有効です。

List projectsToInsert = getProjects(); // プロジェクトリストを取得するメソッド (架空)

// 第2引数を false にすることで、一部のレコードが失敗しても例外をスローせず、処理を続行する
Database.SaveResult[] saveResults = Database.insert(projectsToInsert, false);

// 結果をループで確認
for (Database.SaveResult sr : saveResults) {
    if (sr.isSuccess()) {
        // 成功したレコードのID
        System.debug('正常に作成されたプロジェクトID: ' + sr.getId());
    } else {
        // 失敗したレコードのエラーを処理
        for (Database.Error err : sr.getErrors()) {
            System.debug('エラーが発生しました。');
            System.debug('  ステータスコード: ' + err.getStatusCode());
            System.debug('  エラーメッセージ: ' + err.getMessage());
            System.debug('  影響を受けた項目: ' + err.getFields());
        }
    }
}

このアプローチにより、どのレコードがなぜ失敗したのかを詳細に把握し、より洗練されたエラーハンドリングやログ記録が可能になります。


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

カスタムオブジェクトは、Salesforceをビジネスに合わせてカスタマイズするための根幹をなす機能です。開発者としてその真価を引き出すためには、UIでの設定方法だけでなく、API参照名、sObjectとしての振る舞い、リレーションの特性、そしてプログラムからのアクセス方法を深く理解することが不可欠です。

最後に、カスタムオブジェクトを扱う上でのベストプラクティスをまとめます。

  1. 明確な命名規則: オブジェクトや項目のAPI参照名は、内容が直感的に理解できる、一貫性のある命名規則に従いましょう。
  2. 説明を記述する: 全てのカスタムオブジェクトとカスタム項目に、その目的や用途を説明する「説明」を必ず記述してください。これは将来の自分や他の開発者にとって非常に価値のある情報となります。
  3. コードのバルク化の徹底: 何があっても、ループ内でのSOQLクエリやDMLステートメントの発行は避けてください。これはSalesforce開発における鉄則です。
  4. セキュリティを常に意識: データアクセスに関する要件を明確にし、必要に応じて `with sharing` を使用したり、スキーマチェックを実装したりして、堅牢なコードを書きましょう。
  5. 適切なリレーションの選択: データの独立性、連動削除の要否、積み上げ集計の必要性を考慮し、参照関係と主従関係を慎重に選択してください。安易な主従関係の選択は、後々のデータモデル変更を困難にします。

これらの原理とベストプラクティスを身につけることで、皆さんは単にコードを書くだけでなく、スケーラブルで保守性が高く、安全なSalesforceアプリケーションを構築できる真のプロフェッショナルへと成長できるはずです。

コメント