SalesforceのLightning Componentから長時間コールアウトを実行する

Summer '19リリース(APIバージョン46.0)で、Lightning Component(Aura/LWC)から直接[長時間コールアウト(非同期コールアウト)](https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/apex_continuation_overview.htm)が実行できるようになりました。Salesforceプラットフォームでは、[5秒を超えるApexトランザクション数が同時に10までという制限](https://developer.salesforce.com/docs/atlas.ja-jp.salesforce_app_limits_cheatsheet.meta/salesforce_app_limits_cheatsheet/salesforce_app_limits_platform_apexgov.htm)があり、非機能要件を検討する際にしばしば必要となる観点ですが、サーバサイドの処理が必要でかつ外部システムとの連携が多いシナリオにおいては、長時間コールアウトはこの制限を回避する方法の1つとなります。[^1]
[^1]: シンプルなコールアウトであればクライアントサイドでFetchやXHRで実装すれば良いですが、ここでは扱いません。
これまで、Lightning Componentから長時間コールアウトを実行するには、[Visualforceページをラップする](https://developer.salesforce.com/blogs/2017/09/invoking-apex-continuations-lightning-components.html)必要がありましたが、この方法は煩雑かつあまりセキュアではありませんでした。ここでは、Lightning Componentから直接長時間コールアウトを実行する方法をサンプルコードとともに紹介します。

## Apexメソッドの定義
長時間コールアウトを行うApexのメソッドに対しては、``@AuraEnabled(continuation=true)``のアノテーションを付与します。``Continuation``クラスをインスタンス化して、コールバックメソッドを指定し、通常の``HttpRequest``を渡せば良いです。

```Java
@AuraEnabled(continuation=true cacheable=true)
public static Continuation exampleRequest() {
    Continuation con = new Continuation(60); //引数はタイムアウト時間(最大120秒)
    con.continuationMethod = 'continuationCallback';
    
    HttpRequest req = new HttpRequest();
    req.setMethod('GET');
    req.setEndpoint('callout:Example');
    con.addHttpRequest(req);
    return con;
}

@AuraEnabled(cacheable=true)
public static String continuationCallback(List<String> labels, Object state) {
    HttpRespons res = Continuation.getResponse(labels.get(0));
    return res.getBody();
}
```
Continuationを返すメソッド内ではDMLが実行できませんので注意してください。コールバックメソッド内では許可されています。また、余談ですが、[エンドポイントの設定には指定ログイン情報(Named Credential)を用いる](https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/apex_callouts_named_credentials.htm)と便利です。直接指定する場合はリモートサイト設定を忘れずに行いましょう。

## Aura Componentから実行
通常のApex呼び出しと同様です。

```JavaScript
({
    handleContinuation : function(component, event, helper) {
        const action = component.get("c.exampleRequest");
        action.setCallback(this, response => {
             const state = response.getState();
             if(state === "SUCCESS"){
                  //成功時の処理
             } else if(state === "ERROR"){
                  //エラー時の処理
             }
        });
        $A.enqueueAction(action);
    }
})
```

## Lightning Web Componentから実行
こちらも通常のApex呼び出しと同様ですが、import時のパスが`@salesforce/apex`ではなく、`@salesforce/apexContinuation`となることに注意してください。

```JavaScript
import { LightningElement, track } from 'lwc';
import exampleRequest from '@salesforce/apexContinuation/ContinuationExampleController.exampleRequest';

export default class ContinuationExample extends LightningElement {
    @track result;
    @track error;
    //例えばボタン呼び出しの場合
    handleContinuation() {
        exampleRequest()
            .then(result => {
                this.result = result;
            })
            .catch(error => {
                this.error = error;
            });
    }
}
```

## 参考
* [Apex開発者ガイド - 非同期コールアウトを使用するプロセス](https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/apex_continuation_process.htm)
* [Summer '19 Release Note - Make Long-Running Callouts with Continuations](https://releasenotes.docs.salesforce.com/en-us/summer19/release-notes/rn_lc_continuations.htm)
    * このリリースノートは2019年4月末時点でプレビューであり、当機能は予告なく本番適用が延期となる場合があります。ご注意ください。