LoginSignup
6
6

More than 1 year has passed since last update.

Salesforceの外部サービスを使ってAWSのLambdaを実行してみる

Last updated at Posted at 2021-12-06

Ateam Brides Inc. Advent Calendar 2021の6日目は
株式会社エイチームブライズ エンジニア
新卒2年目になった@takaHALが担当します!

これは何?

Lambdaで作ったAPIをSalesforceの外部サービスに登録し
SalesforceのフローからLambdaを実行してみます。

使うもの

  • AWS

    • Lambda
    • IAM
    • APIGateway
    • CloudWatch
      • 意識して使うのはこの4つ
  • ServerlessFramework

  • Salesforce

    • 外部サービス
    • 指定ログイン情報

利用するSalesforce環境はtrailheadのplayground環境です。
trailheadに登録すれば誰でも利用できます。

外部サービスとは

image.png
image.png

OpenAPIのJSONファイルを登録することで、フロー内でOpenAPIに書かれた定義でREST APIを実行できます。
ノーコードでAPIを実行するフローアクションが作成できるので非常に便利なものになってます

レスポンスも受け取ることができるため複雑なロジックもSalesforceの外部に置けるのもいいポイントです。

先にtrailheadをやっておくとイメージがしやすいと思います。

では早速やっていきましょう

AWS側の準備

ServerlessFrameworkを利用します。

詳しい説明はServerless Framework の使い方を初心者にも分かりやすく説明するで書いてくださっているのでお勧めです。

デプロイに必要なIAMやAWSは準備してある前提で進めます あくまでもメインはSalesforceなのでAWSとServerlessFramework部分はサクッと済ませます。

1. プロジェクトを作成

開発環境

$ serverless --version
Framework Core: 2.68.0 (local)
Plugin: 5.5.1
SDK: 4.3.0
Components: 3.18.1

$ node -v
v17.1.0
$ mkdir salesforce-lambda-test
$ cd salesforce-lambda-test
$ serverless create --template aws-nodejs-typescript --name salesforce-lambda-test

スクリーンショット 2021-12-05 11.07.39.png

2. AWSにデプロイ

今回利用したプロジェクトのテンプレートバージョンは以下になります

$ npm install
$ sls deploy
service: salesforce-lambda-test
stage: dev
region: us-east-1
stack: salesforce-lambda-test-dev
resources: 13
api keys:
  None
endpoints:
  POST - https://{hoge}.us-east-1.amazonaws.com/dev/hello
functions:
  hello: salesforce-lambda-test-dev-hello
layers:
  None

3. 外部サービスに利用する openapiのjsonファイルを用意する

SalesforceのOpenAPI 2.0 スキーマ定義は少しクセがあるので、今回はサクッと検証するためにtrailheadのサンプルを少しいじって利用させていただきます。
実際はAPIGatewayの画面からエクスポートしたりdocumentationとして登録するといいと思います。

{
  "swagger": "2.0",
  "info": {
    "version": "1.0",
    "title": "Salesforce test"
  },
  "paths": {
    "/hello": {
      "post": {
        "operationId": "apiHello",
        "consumes": ["application/json"],
        "produces": ["application/json"],
        "parameters": [
          {
            "name": "accountName",
            "in": "formData",
            "required": true,
            "type": "string",
            "description": "Name of the account"
          },
          {
            "name": "accountType",
            "in": "formData",
            "required": true,
            "type": "string",
            "description": "The type of account"
          }
        ],
        "responses": {
          "201": {
            "description": "The response when the account does not already exist and we can create one",
            "schema": { "$ref": "#/definitions/accountDetails" }
          },
          "409": {
            "description": "The response when the account already exists and we cannot create one",
            "schema": { "$ref": "#/definitions/accountDetails" }
          },
          "400": {
            "description": "Error response if the account name parameter is less than minimum characters",
            "schema": { "$ref": "#/definitions/errorModel" }
          },
          "404": {
            "description": "Error response if the account is not supported by service or account is not found",
            "schema": { "$ref": "#/definitions/errorModel" }
          }
        }
      }
    }
  },
  "definitions": {
    "accountDetails": {
      "required": ["id", "name", "type", "availableBal"],
      "properties": {
        "id": { "type": "string", "description": "id" },
        "name": { "type": "string", "description": "name" },
        "type": { "type": "string", "description": "type" },
        "availableBal": { "type": "string", "description": "availableBal" }
      }
    },
    "errorModel": {
      "required": ["errorCode", "errorMessage"],
      "properties": {
        "errorCode": {
          "type": "string",
          "description": "A service-specific error code."
        },
        "errorMessage": {
          "type": "string",
          "description": "A service-specific error code."
        }
      }
    }
  }
}

4. IAMを作成する

SalesforceからAPIGatewayを通じでLambdaを実行するために使用するIAMを作成します。

スクリーンショット 2021-12-05 13.00.48.png

スクリーンショット 2021-12-05 13.01.27.png

AmazonAPIGatewayInvokeFullAccessを今回はとりあえず付与しました。

スクリーンショット 2021-12-05 13.03.09.png

作成したらアクセスキーとシークレットアクセスキーをメモしておきましょうあとで使います。

これでAWS側の設定等の操作は終了です。

Salesforceで外部サービスを登録する

1. 指定ログイン情報の作成

スクリーンショット 2021-12-05 13.10.23.png

項目 内容
URL デプロイしたAPIのエンドポイントを記入
ID種別 AWS 署名バージョン4 を選択
AWSアクセスキーID 作成したIAMのアクセスID
AWSシークレットキー 作成したIAMのシークレットキー
AWSリージョン デプロイしたAPIのリージョン 例) us-east-1
AWS サービス 今回はAPIGatewayにアクセスするので apigatewayと記載します
コールアウトオプション 全部オフです

そのまま好きな名前で保存でOKです。
しかし、リージョンとサービスにそのまま 文字列入れるの微妙にいけてないですね。。。。

そもそもここら辺は早くHyperforceでデフォルトでLambdaとかと繋げれるようにしてくれると楽なので今後に期待ですね。

2. 外部サービスの登録

外部サービスを新規作成でAPI仕様からを選択

スクリーンショット 2021-12-05 13.25.40.png

 外部サービスを設定

指定ログイン情報に先程登録したものを選択し、JSON形式の完全なサービススキーマを選択

AWS側の準備で準備したものを使います

スクリーンショット 2021-12-05 13.26.04.png
スクリーンショット 2021-12-05 14.11.13.png

そのまま登録でOK

フローで外部サービスを使ってみる

1. フローを新規作成

今回はデバッグ実行で動作確認するのみなのでどれでもいいです。今回はスケジュールトリガーフローにしてみます。

スクリーンショット 2021-12-05 13.32.21.png

2. アクションを追加

外部サービスとして登録したものがフローのアクションとして選択できるので選択します。

入力値で選択できるのはparametersに記載したものになってます。

"parameters": [
          {
            "name": "accountName",
            "in": "formData",
            "required": true,
            "type": "string",
            "description": "Name of the account"
          },
          {
            "name": "accountType",
            "in": "formData",
            "required": true,
            "type": "string",
            "description": "The type of account"
          }
        ],

スクリーンショット 2021-12-05 14.14.00.png

ちなみに詳細の部分ではresponsesに記載した内容が取得できることがわかります。これでフローを200の時の値を使用したりエラーハンドリングもできますね

"responses": {
          "201": {
            "description": "The response when the account does not already exist and we can create one",
            "schema": { "$ref": "#/definitions/accountDetails" }
          },
          "409": {
            "description": "The response when the account already exists and we cannot create one",
            "schema": { "$ref": "#/definitions/accountDetails" }
          },
          "400": {
            "description": "Error response if the account name parameter is less than minimum characters",
            "schema": { "$ref": "#/definitions/errorModel" }
          },
          "404": {
            "description": "Error response if the account is not supported by service or account is not found",
            "schema": { "$ref": "#/definitions/errorModel" }
          }
        }

3. デバッグして確認してみる

68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3434353630302f30643761656534612d643338332d373739622d353062622d3536373932663331383531382e706e67.png

実行できていますね

CloudWatch側でも確認が取れました。

スクリーンショット 2021-12-05 15.25.01.png

今回は"in": "formData"で送信しているので accountName=hoge&accountType=fugaみたいな形式でデータが送られます。

"parameters": [
          {
            "name": "accountName",
            "in": "formData",
            "required": true,
            "type": "string",
            "description": "Name of the account"
          },
          {
            "name": "accountType",
            "in": "formData",
            "required": true,
            "type": "string",
            "description": "The type of account"
          }
        ],

リクエストボディパラメータがメソッド POST、PUT、PATCH について定義されていない場合、フォームデータリクエストパラメータがリクエストボディで application/x-www-form-urlencoded として送信されます

`application/json`でデータを送信したい場合は下記のように"in": "body",で記載すると送信できます。 その際はフロー内で変数を作成する際に種別でApex定義を選択しましょう"type": "object",の中身が個別設定できる変数になってくれます。


        "consumes": ["application/json"],
        "produces": ["application/json"],
        "parameters": [
          {
            "name": "requests",
            "in": "body",
            "required": true,
            "schema": { "$ref": "#/definitions/Requests" },
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "405": {
            "description": "Invalid input"
          }
        }
      }
    }
  },
  "definitions": {
    "Requests": {
      "type": "object",
      "required": ["requestId", "name"],
      "properties": {
        "requestId": {
          "type":"integer"
        },
        "name": {
          "type": "string"
        },
      }
    }
  }
}

テストで作成したAWSのリソースを削除

削除する場合は以下のコマンドです

$ sls remove

参考にさせてもらった記事

最後に

Salesforce関係の開発を触り始めて日が浅いですが、フローを使って外部APIと連携してみたりデータ連携をコードをほぼ書かなくてもいいようにできないかな?と思っていたところ外部サービスというものを見つけたので試してみました。

通常のopenapiの記述よりも癖はありますが、使いこなすと非常に便利な機能だと思いますので自分でもマスターしようと思います。

外部サービスをうまく使えば以下のようなことがSalesforce内でApexを書くことなく実現できます。

  • 複雑な計算ロジックをSalesforceに持ち込むことなく利用できる
  • Salesforce外の自社サービスのデータとコラボレーションしやすい

などいろいろできそうです。

外部サービスとは違いますが、Apexで@InvocableMethodを使うとフローアクションを作成できるのでそちらもお勧めです。

sObject型を受け取ることができるので、レコードの更新をトリガーにしてBigQueryにオブジェクトのデータを送るとかさまざまな用途で使えると思います。

もう少し記事を更新していくつもりではいますが、質問やこっちの方がいいよ!みたいな意見があれば是非コメントにていただきたいです。

Ateam Brides Inc. Advent Calendar 2021の7日目は、@rf_pがお送りします!!どんなネタを用意してくるのか楽しみです!!

6
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
6