指定ログイン情報を使用してSalesforce ApexからAWS Lambdaを実行する

## 概要

Salesforce.com(以下 SFDC)からAWS LambdaをInvokeします。
具体的には、SFDCの指定ログイン情報(Named Credential)へAWSのIAMユーザークレデンシャルを保存し、Apexからコールアウト、Lambdaを実行します。
自前で実装するとなると面倒であろうAWS署名バージョン4にSFDCが対応しているため、シンプルな記述で実行可能になっています。

:::note
指定ログイン情報の設定が変更になっています。末尾の参考リンクも参照ください。
:::

## 前提

- Salesforceについては無償で誰でも利用可能な [Salesforce Developer Edition](https://developer.salesforce.com/ja) で確認します。
- AWS Lambdaは東京リージョン(ap-northeast-1) を利用します

## 手順

### AWS Lambda関数を作成

実行するLambda関数 `MyFunction` を作成します。
今回は Pythonで記述しました。

```python
def lambda_handler(event, context):
    name = event.get('name')
    return {
        'statusCode': 200,
        'body': f'Hello {name} !'
    }
```

### IAMポリシーを作成

`MyFunctionInvokePolicy` を作成します。
東京リージョンのLambda関数 `MyFunction` を実行できるように指定しました。

```json:MyFunctionInvokePolicy
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "lambda:InvokeFunction",
                "lambda:InvokeAsync"
            ],
            "Resource": "arn:aws:lambda:ap-northeast-1:123456789012:function:MyFunction"
        }
    ]
}
```

### IAMユーザーを作成

`MyFunctionInvokeUser` という名前のIAMユーザーを作成しました。
AWS認証情報タイプは `アクセスキー・プログラムによるアクセス` を選択、ポリシーへ上記手順で作成したポリシーを指定します。
作成完了時にアクセスキーID、シークレットアクセスキーを控えておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/53115/1f079646-e7a6-fff6-1af9-642c6cd72021.png)


### 指定ログイン情報(Named Credentials)[^namedcredentials]  を作成

[^namedcredentials]: 英語だと `Named Credentials` なのですが、日本語訳の `指定ログイン情報` と関連付けて記憶しにくいなと感じています。SFDCの設定ページでいつも 「名前・・・」なんだっけ？みたいになり、`named credentials` でググって、あー、、となっています。

次のように指定しました。

|項目| 値 |
| -- | -- |
|表示ラベル|InvokeLambda|
|名前|InvokeLambda|
|URL| https://lambda.ap-northeast-1.amazonaws.com/2015-03-31/functions |
|ID種別| 指定ユーザ |
|認証プロトコル| AWS 署名バージョン 4|
|AWSアクセスキーID | 上記手順で作成したIAMユーザーのアクセスキーID |
|AWSシークレットアクセスキー |上記手順で作成したIAMユーザーのシークレットアクセスキー |
|AWSリージョン| ap-northeast-1 |
|AWSサービス| lambda |

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/53115/634ca680-407a-5506-19f0-b5721149e5e3.png)


## 動作確認

デベロッパーコンソールの Anonymous WindowからApexコードを実行して確認します。

```apex
HttpRequest req = new HttpRequest();
req.setEndpoint('callout:InvokeLambda/MyFunction/invocations');
req.setMethod('POST');
req.setBody('{"name": "Justin"}');

Http http = new Http();
HttpResponse res = http.send(req);
System.debug(res.getBody());
```

### 実行ログ

コード2行目の `callout:InvokeLambda` が 指定ログイン情報で設定したURL `https://lambda.ap-northeast-1.amazonaws.com/2015-03-31/functions` に置き換わり、`https://lambda.ap-northeast-1.amazonaws.com/2015-03-31/functions/MyFunction/invocations` というURLが生成されているのがわかりますね。
HttpResponse::getBody() でLambdaの実行結果が取得できています。

```
CALLOUT_REQUEST|[7]|System.HttpRequest[Endpoint=callout:InvokeLambda/MyFunction/invocations, Method=POST]
NAMED_CREDENTIAL_REQUEST|NamedCallout[Named Credential Id=0XA5h0000004DDd, Named Credential Name=InvokeLambda, Endpoint=https://lambda.ap-northeast-1.amazonaws.com/2015-03-31/functions/MyFunction/invocations, Method=POST, External Credential Type=EXTERNAL, HTTP Header Authorization=Not Set, Content-Type=null, Request Size bytes=18, Retry on 401=True]
NAMED_CREDENTIAL_RESPONSE|NamedCallout[Named Credential Id=0XA5h0000004DDd, Named Credential Name=InvokeLambda, Status Code=200, Content-Type=application/json, Response Size bytes=45, Overall Callout Time ms=647, Connect Time ms=3
CALLOUT_RESPONSE|[7]|System.HttpResponse[Status=OK, StatusCode=200]
USER_DEBUG|[8]|DEBUG|{"statusCode": 200, "body": "Hello Justin !"}
```

### その他

#### Lambdaを非同期実行したい

リクエストヘッダ `X-Amz-Invocation-Type: Event` を指定します。

```apex
req.setHeader('X-Amz-Invocation-Type', 'Event');
```

#### 実行ログを取得したい

リクエストヘッダ `X-Amz-Log-Type: Tail` を指定します。

```apex
req.setHeader('X-Amz-Log-Type', 'Tail');
```

レスポンスヘッダ `X-Amz-Log-Result` に最後の4KBがBASE64エンコードされてセットされます。
利用する場合はデコードが必要です。


```apex
System.debug(
    EncodingUtil.base64Decode(
        res.getHeader('X-Amz-Log-Result')
    ).toString());
```


## 最後に

SFDCからAWS Lambdaをコールするシーンとしては、企業の業務システム連携で検討する場面が多いだろうと推察します。

この方法はAWS CLIでLambda実行するのと同等ということになると思います。
API Gatewayで公開エンドポイントにしても同じことは実現できますが、IAMユーザーを固定することは、権限ポリシーを厳密に設定できたり、実行監査もしやすいのではないでしょうか。

**参考リンク**

- [AWS Lambda:API reference:Actions:Invoke](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/API_Invoke.html)
- [指定ログイン情報を使用するApexコールアウト](https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/apex_callouts_named_credentials_merge_fields.htm)
- [指定ログイン情報(Named Credentials)を使用してSalesforce ApexからAWS APIをコールする](https://qiita.com/shinsaka/items/a1e6dce886697f175af9)
- [Salesforceヘルプ:指定ログイン情報と外部ログイン情報
](https://help.salesforce.com/s/articleView?language=en_US&id=sf.nc_named_creds_and_ext_creds.htm&type=5)