例外（AuraHandledException）をスローしつつDML操作を行う


## 未処理の例外があるとDMLは行われない

LWCから呼び出されたApexでエラーが発生した時、AuraHandledExceptionをスローしてJavaScriptでエラーを処理することがあります。例外をスローするので、当然ロールバックが起きます。そのため、たとえばエラーログをレコードとして残したい時でも、通常のDML操作ではレコードが作成されません。また、futureメソッドも実行されません。

```apex:DMLが実行されない例（通常のDML）
public class SampleController {
    @AuraEnabled
    public static void doSomething() {
        try {
            // 処理
        } catch (Exception e) {
            // ロールバックされるためレコードが作成されない。
            insert createLog(e);
            throw new AuraHandledException(e.getMessage());
        }
    }

    private static Log__c createLog(Exception e) {
        return new Log__c(
            Message__c = e.getMessage(),
            DetailMessage__c = e.getStackTraceString()
        );
    }
}
```

```apex:DMLが実行されない例（futureメソッドでのDML）
public class SampleController {
    @AuraEnabled
    public static void doSomething() {
        try {
            // 処理
        } catch (Exception e) {
            // トランザクションがロールバックされるとFutureメソッドは処理されない。
            writeLogAsync(e.getMessage(), e.getStackTraceString());
            throw new AuraHandledException(e.getMessage());
        }
    }

    @Future
    private static void writeLogAsync(String message, String detail) {
        insert new Log__c(
            Message__c = message,
            DetailMessage__c = detail
        );
    }
}
```

## 例外を投げつつレコードを作成する場合はプラットフォームイベントを使用する

プラットフォームイベントは、すぐに公開するか、トランザクションが正常に終了した後に公開するかを選択することができます。すぐに公開を選択することで、処理されない例外が発生してロールバックが起きてもイベントトリガーを介してレコードを保存することができます。**ただし、プラットフォームイベントには1日あたりのリミットがあるため、大量のイベントを発生させる場合は注意が必要です。**

[プラットフォームイベントの制限について](https://developer.salesforce.com/docs/atlas.ja-jp.platform_events.meta/platform_events/platform_event_limits.htm)

### 1. プラットフォームイベントを作成する

「設定 > プラットフォームイベント > 新規プラットフォームイベント」からプラットフォームイベントを作成します。このとき、公開動作は「すぐに公開」を選択します。作成後にカスタム項目も作成します。

![スクリーンショット 2025-01-20 23.39.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125621/e088efe6-fefc-ebdc-d7b8-ead27f04df1a.png)

### 2. プラットフォームイベントトリガーフローを作成する

「設定 > フロー > 新規フロー」でプラットフォームイベントトリガーフローを選択して、プラットフォームイベントが登録された時にDML操作を行うフローを作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125621/6cea0f11-029f-72ae-a23d-cbcf0fb61764.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125621/bff70811-5b64-b15e-5482-f5387dda7a32.png)

### 3. Apexでプラットフォームイベントを公開する

```apex
public class SampleController {
    @AuraEnabled
    public static void doSomething() {
        try {
            // 処理
        } catch (Exception e) {
            // ロールバックしてもイベントが公開される
            EventBus.publish(createLogEntryEvent(e));
            throw new AuraHandledException(e.getMessage());
        }
    }

    private static LogEntryEvent__e createLogEntryEvent(Exception e) {
        return new LogEntryEvent__e(
            Message__c = e.getMessage(),
            DetailMessage__c = e.getStackTraceString()
        );
    }
}
```

## Appendix

### プラットフォームイベントを公開するApexのテスト

下記の二通りあります。

- `Test.stopTest()`の後にAssertメソッドを実行する
- `Test.getEventBus().deliver();`の後にAssertメソッドを実行する

```Apex
@IsTest
public class SampleControllerTest {
    @IsTest
    static void testDoSomething() {
        try {
            Test.startTest();
            SampleController.doSomething();
            Test.stopTest();
        } catch (Exception e) {
			Test.getEventBus().deliver();
            Assert.isInstanceOfType(e, AuraHandledException.class);
            List<Log__c> result = [SELECT Id FROM Log__c];
            Assert.areEqual(1, result.size(), 'ログサイズ');
        }
    }
}
```

[Deliver Test Event Messages | Salesforce Developers](https://developer.salesforce.com/docs/atlas.en-us.platform_events.meta/platform_events/platform_events_test_deliver.htm)

## 参考
- [Apex サーバ側コントローラからエラーを返す | Salesforce Developers](https://developer.salesforce.com/docs/atlas.ja-jp.lightning.meta/lightning/controllers_server_apex_custom_errors.htm)
- [future のメソッド | Salesforce Developers](https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/apex_invoking_future_methods.htm)
- [プラットフォームイベント開発者ガイド | Salesforce Developers](https://developer.salesforce.com/docs/atlas.ja-jp.platform_events.meta/platform_events/platform_events_intro.htm)