Salesforce カスタムオブジェクトの完全マスター:開発者向け SObject と Apex 活用術

背景と応用シナリオ

Salesforce プラットフォームの強力さの根源は、その柔軟なデータモデルにあります。Salesforce は、Account (取引先)、Contact (取引先責任者)、Opportunity (商談) といった、ビジネスで一般的に使用される多数の Standard Objects (標準オブジェクト) を標準で提供しています。これらは多くのビジネスシナリオをカバーできますが、すべての企業の独自のビジネスプロセスに完璧に適合するわけではありません。

ここで登場するのが Custom Objects (カスタムオブジェクト) です。カスタムオブジェクトは、企業の特定のニーズに合わせて、管理者が一から作成できるオブジェクトです。これにより、Salesforce を単なる CRM ツールから、ビジネス全体の運営を支えるカスタムアプリケーションプラットフォームへと昇華させることができます。

開発者の視点から見ると、カスタムオブジェクトは、私たちが構築するカスタムロジック、UI、インテグレーションの基盤となるデータストレージそのものです。例えば、以下のような応用シナリオが考えられます。

  • プロジェクト管理: 「プロジェクト(Project__c)」、「タスク(Task__c)」、「タイムシート(Timesheet__c)」といったカスタムオブジェクトを作成し、プロジェクトの進捗、担当者、工数を一元管理するアプリケーションを構築する。
  • 不動産管理: 「物件(Property__c)」、「内覧(Showing__c)」、「契約(Contract__c)」といったオブジェクトで、不動産の物件情報から顧客の内覧予約、契約プロセスまでを管理する。
  • イベント管理: 「イベント(Event__c)」、「参加者(Attendee__c)」、「セッション(Session__c)」オブジェクトを作成し、イベントの企画から参加者登録、当日のセッション管理までを行う。

このように、カスタムオブジェクトを駆使することで、Salesforce をあらゆる業界、あらゆるビジネスプロセスに適合させることが可能になります。私たち開発者は、これらのカスタムデータ構造を Apex (エイペックス) や SOQL (Salesforce Object Query Language) を用いて操作し、複雑なビジネスロジックを実装していくことになります。


原理説明

Salesforce 開発者にとって、カスタムオブジェクトの技術的な側面を理解することは不可欠です。UI 上で定義されたオブジェクトが、コード上ではどのように表現され、操作されるのかを見ていきましょう。

SObject と API 参照名

Apex の世界では、Salesforce 上のすべてのオブジェクトレコードは、ジェネリックな SObject (Salesforce Object) 型として表現されます。Standard Object も Custom Object も、すべて SObject の一種です。しかし、開発時には、より具体的なオブジェクトタイプ(例: `Account` や `Project__c`)を使用することが推奨されます。これにより、コンパイル時の型チェックが有効になり、コードの可読性と保守性が向上します。

カスタムオブジェクトおよびカスタム項目には、必ず末尾に `__c` が付与された API Name (API 参照名) があります。例えば、UI 上で「プロジェクト」という表示ラベルのオブジェクトを作成した場合、その API 参照名は `Project__c` となります。コード内でこのオブジェクトや項目を参照する際は、必ずこの API 参照名を使用します。

// 具体的な SObject 型を使用する (推奨)
Project__c newProject = new Project__c();
newProject.Name = 'New Website Development';

// ジェネリックな SObject 型を使用する (動的な処理で有用)
SObject genericProject = Schema.getGlobalDescribe().get('Project__c').newSObject();
genericProject.put('Name', 'New Website Development');

リレーションシップ

オブジェクト間の関連付けは、ビジネスアプリケーションの核となります。カスタムオブジェクトでは主に2種類のリレーションが使用されます。

1. Lookup Relationship (参照関係): 2つのオブジェクトを緩やかに結合します。子オブジェクトは親オブジェクトとは独立して存在できます。例えば、「プロジェクト(Project__c)」オブジェクトに「取引先(Account)」への参照関係項目 `Account__c` を作成した場合、プロジェクトレコードは取引先レコードに紐づきますが、取引先が削除されてもプロジェクトは残ります(設定による)。コード上では、この項目には親レコードの ID が格納されます。

2. Master-Detail Relationship (主従関係): 2つのオブジェクトを強固に結合します。子(Detail)オブジェクトは、親(Master)オブジェクトなしでは存在できません。親レコードが削除されると、関連する子レコードも連動して削除されます。また、子レコードの所有者や共有設定は、親レコードに依存します。例えば、「タイムシート(Timesheet__c)」が「プロジェクト(Project__c)」の Detail である場合、プロジェクトがなければタイムシートは存在しえません。

SOQL クエリでは、これらのリレーションを辿って関連データを取得できます。子から親へは `__r` を付与したリレーション名(例: `Account__r`)を、親から子へは子のリレーション名(複数形)のサブクエリを使用します。


示例代码

ここでは、`Project__c` というカスタムオブジェクト(項目: `Name`, `Status__c` (Picklist), `StartDate__c` (Date), `Account__c` (Account への参照))を例に、具体的な Apex コードを見ていきます。

レコードの作成 (DML Insert)

新しいプロジェクトレコードを作成し、データベースに挿入する基本的なコードです。

// developer.salesforce.com の Apex Developer Guide 'Inserting and Updating Records' を参考にしています。

// 1. 新しい Project__c オブジェクトのインスタンスを生成
Project__c newProject = new Project__c();

// 2. 各項目に値を設定
// Name は標準項目
newProject.Name = 'Q3 Marketing Campaign';
// Status__c はカスタム選択リスト項目
newProject.Status__c = 'Planning';
// StartDate__c はカスタム日付項目
newProject.StartDate__c = Date.today();

// 3. 関連する取引先の ID を参照項目に設定
// この ID は事前にクエリなどで取得しておく必要があります
Id accountId = '0015j00000AbCdEf'; // サンプルの Account ID
newProject.Account__c = accountId;

try {
    // 4. DML (Data Manipulation Language) の insert ステートメントでレコードをデータベースに保存
    insert newProject;
    
    // 挿入成功後、newProject.Id には自動採番されたレコードIDが格納される
    System.debug('プロジェクトが正常に作成されました。ID: ' + newProject.Id);

} catch (DmlException e) {
    // 5. DML 処理でエラーが発生した場合の例外処理
    System.debug('プロジェクトの作成中にエラーが発生しました: ' + e.getMessage());
}

レコードの照会 (SOQL)

特定の条件に一致するプロジェクトレコードをデータベースから取得します。

// developer.salesforce.com の SOQL and SOSL Reference 'Relationship Queries' を参考にしています。

// 計画段階 (Planning) にあり、かつ取引先が紐づいているプロジェクトを検索
// 関連する取引先の名前 (Account.Name) も同時に取得
List<Project__c> planningProjects = [
    SELECT Id, Name, Status__c, StartDate__c, Account__r.Name 
    FROM Project__c 
    WHERE Status__c = 'Planning' AND Account__c != null
    ORDER BY StartDate__c ASC
    LIMIT 10
];

// 取得したレコードをループで処理
for (Project__c proj : planningProjects) {
    // Account__r.Name のように '__r' を使って親オブジェクトの項目にアクセスできる
    System.debug('プロジェクト名: ' + proj.Name + ', 取引先名: ' + proj.Account__r.Name);
}

レコードの更新 (DML Update)

既存のレコードを照会し、その内容を更新します。

// developer.salesforce.com の Apex Developer Guide 'Manipulating Records with DML' を参考にしています。

// 更新対象のレコードを取得 (ここでは1件のみと仮定)
Project__c projectToUpdate = [SELECT Id, Status__c FROM Project__c WHERE Name = 'Q3 Marketing Campaign' LIMIT 1];

if (projectToUpdate != null) {
    // 項目値を変更
    projectToUpdate.Status__c = 'In Progress';
    
    try {
        // DML の update ステートメントで変更を保存
        update projectToUpdate;
        System.debug('プロジェクトのステータスが更新されました。');
    } catch (DmlException e) {
        System.debug('プロジェクトの更新中にエラーが発生しました: ' + e.getMessage());
    }
}

一括処理 (Bulk DML Operation)

複数のレコードを効率的に処理し、Governor Limits (ガバナ制限) を回避するための必須テクニックです。

// developer.salesforce.com の Apex Best Practices 'Bulk Apex Triggers' を参考にしています。

// 処理対象のレコードIDリスト
List<Id> projectIds = new List<Id>{'a015j000001AbCdE','a015j000001AbCdF'};

// 1. まず更新対象のレコードを一度の SOQL で取得する
List<Project__c> projectsToUpdate = [SELECT Id, Status__c FROM Project__c WHERE Id IN :projectIds];

// 2. ループ内で DML を実行するのではなく、リスト内のレコードの値を変更する
for(Project__c proj : projectsToUpdate) {
    proj.Status__c = 'Completed';
}

// 3. ループの外で、リスト全体に対して一度だけ DML を実行する
try {
    if (!projectsToUpdate.isEmpty()) {
        update projectsToUpdate;
        System.debug(projectsToUpdate.size() + '件のプロジェクトが正常に完了しました。');
    }
} catch (DmlException e) {
    System.debug('一括更新中にエラーが発生しました: ' + e.getMessage());
}

注意事項

権限 (Permissions)

開発者は、常に権限を意識する必要があります。Object Permissions (オブジェクト権限) や Field-Level Security (FLS) (項目レベルセキュリティ) は、プロファイルや権限セットで設定され、ユーザーがどのオブジェクトや項目にアクセスできるかを制御します。 デフォルトでは、Apex はシステムコンテキストで実行されるため、これらの権限設定を無視してデータにアクセスします。しかし、ユーザーの権限を尊重すべき場合は、クラス定義に `with sharing` キーワードを付与して、共有ルールを適用させる必要があります。FLS をコードレベルで強制するには、`Schema` クラスのメソッド(例: `SObjectType.Project__c.fields.Status__c.isAccessible()`)を使用して、アクセス権を明示的にチェックする必要があります。

API 制限 (API Limits)

Salesforce はマルチテナント環境であるため、リソースの公平な利用を担保するためにガバナ制限が設けられています。開発者が特に注意すべきは以下の制限です。

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

これらの制限に抵触しないために、前述の「一括処理(Bulkification)」が極めて重要です。絶対にループ内で SOQL や DML を実行してはいけません。

エラー処理 (Error Handling)

DML 処理は、入力規則、必須項目違反、ユニーク制約違反など、様々な理由で失敗する可能性があります。堅牢なコードを記述するためには、必ず `try-catch` ブロックで `DmlException` を捕捉し、適切なエラーハンドリング(ログ記録、ユーザーへのフィードバックなど)を行うべきです。 また、一括処理において、一部のレコードのみがエラーになることを許容したい場合は、`Database` クラスのメソッドを使用します。

// Database.insert の第二引数 (allOrNone) を false に設定
Database.SaveResult[] saveResults = Database.insert(projectsToInsert, false);

// 結果をループで確認し、成功・失敗を個別にハンドリング
for (Database.SaveResult sr : saveResults) {
    if (sr.isSuccess()) {
        System.debug('正常に挿入されたレコード ID: ' + sr.getId());
    } else {
        // エラーが発生したレコードの処理
        for(Database.Error err : sr.getErrors()) {
            System.debug('以下のエラーが発生しました。');
            System.debug(err.getStatusCode() + ': ' + err.getMessage());
            System.debug('エラーが発生した項目: ' + err.getFields());
        }
    }
}

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

カスタムオブジェクトは、Salesforce をビジネスニーズに合わせて拡張するための根幹をなす機能です。開発者として、そのデータ構造を深く理解し、効率的かつ安全に操作するスキルは、高品質なアプリケーションを構築するための必須条件です。

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

  1. Bulkification (一括処理): コードは常に複数のレコードを処理できるように設計し、ループの外で一度に DML や SOQL を実行してください。
  2. Type Safety (型安全性): 可能な限り、ジェネリックな `SObject` 型ではなく、`Project__c` のような具体的な SObject 型を使用し、コードの安全性を高めてください。
  3. Security (セキュリティ): `with sharing` キーワードを適切に使用してユーザーのデータアクセス権を尊重し、必要に応じて FLS を手動でチェックしてください。
  4. Efficient Queries (効率的なクエリ): `SELECT *` のようなクエリは避け、必要な項目だけを明示的に指定してください。また、フィルタ条件を適切に設定し、不要なデータを取得しないようにしてください。
  5. Robust Error Handling (堅牢なエラー処理): すべての DML 操作を `try-catch` ブロックで囲むか、`Database` クラスメソッドを使用して、失敗時の挙動を明確に定義してください。

これらの原則を守ることで、スケーラブルで、セキュアで、保守性の高い Salesforce アプリケーションを開発することができるでしょう。

コメント