Part 8: トリガー のエラーハンドリングとリカバリ方法

## **Part 8: トリガー (Trigger) のエラーハンドリングとリカバリ方法**  

前回は **エラーログ管理** と **リトライ機構** を解説しました。  
今回は **Apex トリガーにおけるエラーハンドリング** と、**エラー発生時のリカバリ方法** について解説します。  

---

## **1. トリガーのエラーハンドリングの基本原則**  

### **1.1. トリガーでのエラーの特徴**  
Apex トリガーでは、**エラーが発生するとその処理全体がロールバック** されます。  
つまり、**エラーが発生したレコードだけでなく、処理中のすべてのレコードが保存されません**。  

例えば、`before insert` トリガーで 10 件のレコードを処理中に 1 件がエラーになった場合、  
**すべてのレコードがロールバック** されます。  

---

### **1.2. トリガーのエラーハンドリング戦略**
トリガー内でのエラーハンドリングの考え方は以下の 3 つです。

| **戦略** | **概要** | **適用ケース** |
|---------|---------|---------------|
| **全体をロールバック** | 例外をスローして処理を中断 | データ整合性を最優先する場合 |
| **部分的にエラーを記録** | エラーのあるレコードのみ処理対象から除外 | エラーを許容できる場合 |
| **エラーレポートを作成** | `addError()` を使用して、エラーをレコード単位で通知 | ユーザーにエラーをフィードバックしたい場合 |

---

## **2. 例外をスローして処理を中断する (全体をロールバック)**

### **✅ NG 例: トリガー内で直接 `try-catch`**
```apex
trigger AccountTrigger on Account (before insert) {
    try {
        for (Account acc : Trigger.new) {
            acc.Name = acc.Name.toUpperCase(); // 例: アカウント名を大文字にする
        }
    } catch (Exception e) {
        System.debug('エラー発生: ' + e.getMessage()); 
        // → ただのデバッグログなので、処理は継続してしまう
    }
}
```
このコードでは **エラーがキャッチされても処理が継続するため、問題のあるデータがそのまま保存されるリスクがあります**。  

---

### **✅ OK 例: `TriggerHandler` クラスでエラーを適切に処理**
トリガー内ではなく **別のクラスでエラーハンドリングを行い、適切にスローする** のがベストプラクティスです。

#### **🔹 `AccountTriggerHandler` クラス**
```apex
public class AccountTriggerHandler {
    public static void beforeInsert(List<Account> newAccounts) {
        for (Account acc : newAccounts) {
            if (String.isEmpty(acc.Name)) {
                throw new CustomTriggerException('アカウント名が空です');
            }
            acc.Name = acc.Name.toUpperCase();
        }
    }
}
```

#### **🔹 `CustomTriggerException` クラス**
```apex
public class CustomTriggerException extends Exception {}
```

#### **🔹 `AccountTrigger` (トリガー)**
```apex
trigger AccountTrigger on Account (before insert) {
    try {
        AccountTriggerHandler.beforeInsert(Trigger.new);
    } catch (CustomTriggerException e) {
        System.debug('エラー発生: ' + e.getMessage());
        throw e; // 例外をスローしてロールバック
    }
}
```
この実装では、  
**エラーが発生した場合に `throw e;` で明示的にスローし、全体をロールバック** できます。  
> **適用ケース:** データの整合性を重視する場合  

---

## **3. エラーのあるレコードのみ除外して処理を続行 (部分的エラー処理)**

### **3.1. try-catch を使用して問題のあるレコードだけ処理をスキップ**
**エラーが発生したレコードだけスキップし、他のレコードは処理を続ける** 方法です。

#### **✅ OK 例: `ErrorLogService` を利用したエラーハンドリング**
```apex
public class AccountTriggerHandler {
    public static void beforeInsert(List<Account> newAccounts) {
        List<Account> validAccounts = new List<Account>();

        for (Account acc : newAccounts) {
            try {
                if (String.isEmpty(acc.Name)) {
                    throw new CustomTriggerException('アカウント名が空です');
                }
                acc.Name = acc.Name.toUpperCase();
                validAccounts.add(acc);
            } catch (CustomTriggerException e) {
                ErrorLogService.logError(e, 'AccountTriggerHandler', 'beforeInsert');
            }
        }

        if (!validAccounts.isEmpty()) {
            insert validAccounts;
        }
    }
}
```
このコードでは、  
- **正常なレコードだけ `insert` する**
- **エラーのあるレコードはスキップし、エラーログに記録**  

> **適用ケース:**  
> **エラーを許容しつつ、可能な限り処理を進めたい場合**  

---

## **4. `addError()` でユーザーにエラーを通知する**

`addError()` を使うと、**エラーメッセージを直接レコードに追加し、処理を中断** できます。  
**ロールバックされるが、どのレコードに問題があったか UI で確認できるのが利点** です。

#### **✅ OK 例: `addError()` を使用**
```apex
public class AccountTriggerHandler {
    public static void beforeInsert(List<Account> newAccounts) {
        for (Account acc : newAccounts) {
            if (String.isEmpty(acc.Name)) {
                acc.addError('アカウント名は必須です'); // エラーメッセージをレコードに追加
            }
        }
    }
}
```
これにより、  
> **エラー発生時に Salesforce の UI でエラーメッセージが表示されるため、ユーザーが問題を認識しやすくなります。**  

> **適用ケース:**  
> **エラーの詳細をユーザーに伝えたい場合**

---

## **5. エラーリカバリ戦略**  
エラーが発生した際の対応方法を整理すると、以下のようになります。

| **リカバリ方法** | **適用ケース** | **実装方法** |
|----------------|--------------|------------|
| **ロールバック** | **データ整合性が最優先** | `throw new Exception();` |
| **エラーを記録しつつ続行** | **一部エラーは許容し、できるだけ処理を進める** | `ErrorLogService.logError()` を利用 |
| **ユーザーにエラーメッセージを通知** | **データ入力者にフィードバックが必要** | `addError()` を使用 |

---

## **まとめ**
今回は **Apex トリガーのエラーハンドリングとリカバリ方法** を解説しました。

- **トリガー内で try-catch を使わず、ハンドラクラスで処理**
- **`throw e;` を使い、エラー発生時に全体をロールバック**
- **エラーのあるレコードだけ除外し、可能な限り処理を続行**
- **`addError()` を使ってユーザーにフィードバック**

> **次の Part 9** では、**バッチ処理 (Batch Apex) におけるエラーハンドリング** について解説します！