Part 7: エラーハンドリングの設計パターン (エラーログ管理・リトライ機構)

## **Part 7: エラーハンドリングの設計パターン (エラーログ管理・リトライ機構)**  

前回は **テストクラスでのエラーハンドリング** と **カスタム例外の活用** について解説しました。  
今回は **実際の運用で役立つエラーハンドリングの設計パターン** として、  
**エラーログの管理** と **リトライ機構 (再試行) の実装** を解説します。  

---

## **1. エラーログ管理の重要性**  

### **1.1. なぜエラーログが必要か？**
Apex の処理がエラーで停止すると、**管理者や開発者に通知されない限り問題に気づきにくい** です。  
そのため、**エラーログを記録し、エラーを監視できる仕組みを作ることが重要** です。  

---

### **1.2. エラーログの作成**
Salesforce には **カスタムオブジェクト** を作成してエラーを記録するのが一般的です。  
以下のような **`ErrorLog__c` オブジェクト** を作成しましょう。  

| 項目名            | API名              | 型              | 説明 |
|------------------|------------------|----------------|-----|
| エラーメッセージ | `Message__c`       | 長いテキスト   | 発生したエラー内容 |
| クラス名         | `ClassName__c`     | テキスト       | 例外が発生したクラス名 |
| メソッド名       | `MethodName__c`    | テキスト       | 例外が発生したメソッド名 |
| 行番号           | `LineNumber__c`    | 数値           | エラー発生行 |
| スタックトレース | `StackTrace__c`    | 長いテキスト   | 詳細なエラー情報 |
| 発生日時         | `OccurredAt__c`    | 日時           | エラー発生時刻 |

---

### **1.3. エラーログを記録する共通メソッド**
エラーを `ErrorLog__c` に記録するメソッドを作成します。  
こうすることで、**どこでエラーが発生しても統一的にログを記録できます**。

#### **✅ OK 例: `ErrorLogService` クラス**
```apex
public class ErrorLogService {
    public static void logError(Exception e, String className, String methodName) {
        try {
            ErrorLog__c log = new ErrorLog__c();
            log.Message__c = e.getMessage();
            log.ClassName__c = className;
            log.MethodName__c = methodName;
            log.LineNumber__c = e.getLineNumber();
            log.StackTrace__c = e.getStackTraceString();
            log.OccurredAt__c = System.now();
            insert log;
        } catch (Exception ex) {
            System.debug('ログ記録中にエラー発生: ' + ex.getMessage());
        }
    }
}
```
---

### **1.4. 例外発生時にログを記録**
例外が発生したときに、**エラーログを記録するように `try-catch` を実装** します。

#### **✅ OK 例: `AccountService` クラス**
```apex
public class AccountService {
    public static void createAccount(Account acc) {
        try {
            insert acc;
        } catch (DmlException e) {
            ErrorLogService.logError(e, 'AccountService', 'createAccount');
            throw e;
        }
    }
}
```
---

### **1.5. エラーログを可視化**
エラーログを管理するには、**カスタムレポートやダッシュボードを作成** し、  
管理者がエラーを確認できるようにするとよいでしょう。  

また、**フローやプロセスビルダーでエラー発生時に通知を送る** のも効果的です。  

---

## **2. リトライ機構 (再試行) の実装**  

### **2.1. なぜリトライが必要か？**
Salesforce の API 制限や一時的なネットワークエラーのために、  
**DML 操作や外部システムとの通信が一時的に失敗することがあります**。  

特に以下のようなケースでは、**リトライ機構を実装することで処理の成功率を高められます**。
- **`DmlException`**: **ガバナ制限** による DML 失敗
- **`CalloutException`**: **外部 API の一時的な障害** によるコールアウト失敗
- **`QueryException`**: **SOQL クエリの一時的な制限** によるエラー  

---

### **2.2. 再試行の基本設計**
**一定回数までリトライし、それでも失敗したらエラーログに記録する** 形が一般的です。

---

### **2.3. DML 操作のリトライ**
#### **✅ OK 例: `DMLHelper` クラス**
```apex
public class DMLHelper {
    public static void insertWithRetry(SObject record, Integer maxRetries) {
        Integer attempts = 0;
        Boolean success = false;
        
        while (attempts < maxRetries && !success) {
            try {
                insert record;
                success = true;
            } catch (DmlException e) {
                attempts++;
                if (attempts == maxRetries) {
                    ErrorLogService.logError(e, 'DMLHelper', 'insertWithRetry');
                    throw e; // 最大リトライ回数に達した場合は例外をスロー
                }
                System.debug('DML 失敗、リトライ ' + attempts + ' 回目');
            }
        }
    }
}
```
このメソッドは **DML 操作が失敗した場合に最大 `maxRetries` 回までリトライ** します。  
それでも失敗した場合は、**エラーログを記録して例外をスロー** します。

---

### **2.4. 外部 API コールアウトのリトライ**
外部システムとの連携では、**ネットワーク障害やタイムアウト** が発生することがあります。  
**`CalloutException` が発生した場合に、一定回数リトライする** 実装をします。

#### **✅ OK 例: `CalloutHelper` クラス**
```apex
public class CalloutHelper {
    public static String makeCallout(String endpoint, Integer maxRetries) {
        Integer attempts = 0;
        Boolean success = false;
        String response = '';

        while (attempts < maxRetries && !success) {
            try {
                Http http = new Http();
                HttpRequest req = new HttpRequest();
                req.setEndpoint(endpoint);
                req.setMethod('GET');
                HttpResponse res = http.send(req);
                
                if (res.getStatusCode() == 200) {
                    response = res.getBody();
                    success = true;
                } else {
                    throw new CalloutException('HTTPエラー: ' + res.getStatusCode());
                }
            } catch (CalloutException e) {
                attempts++;
                if (attempts == maxRetries) {
                    ErrorLogService.logError(e, 'CalloutHelper', 'makeCallout');
                    throw e;
                }
                System.debug('Callout 失敗、リトライ ' + attempts + ' 回目');
            }
        }
        return response;
    }
}
```
このメソッドは **外部 API のコールアウトが失敗した場合に最大 `maxRetries` 回までリトライ** します。

---

## **まとめ**
今回は **エラーハンドリングの設計パターン** として、  
**エラーログ管理** と **リトライ機構** について解説しました。

- **`ErrorLog__c` カスタムオブジェクトでエラーを管理**
- **`ErrorLogService` で例外を一元的に記録**
- **DML 操作・API コールアウトのリトライ機構を実装**
- **リトライ回数を制限し、最大回数を超えたらログを記録**

> **次の Part 8** では、**トリガー (Trigger) におけるエラーハンドリングとリカバリ方法** を解説します！