GA後の Salesforce Functions を試してみよう
Salesforce に関わるみなさん( ノ゚Д゚)こんにちわ、Salesforce Advent Calendar 2021 の 23日目、みんなご存じ、かしゆかの誕生日イェ━━━━━ヽ( ゚Д゚)人(゚Д゚ )ノ━━━━━━イ!!
ゆかちゃんの誕生日を祝うべく、本年も Salesforce 小ネタ行きます。今日は、アレです。Salesforce Functions です。
すでに齋藤さんがSalesforce Functionsについてという内容で19日目に記載いただいていて、ありがたいことに、わいのWebinarも紹介下さっています。
ほぼ現時点の最強バイブル
とか書かれてしまうと、わいも Functions ちゃんと書かなきゃと本日に至ります。
今日は何するんですか?と問われたら、そのWebinar内で、わかりやすいだろと思ってイメージ図にしたら「実際の画面が良い」って方がいらっしゃったので、実際のソースコードと実行結果満載でお送りすることにしました。
齋藤さんと同じ会社の白石さんがSalesforce Functions (Pilot版) 環境構築をやってみたという小ネタを準備下さっているのもあります。こちらもご参考に。コマンドなどずいぶん変わってしまったので、雰囲気を感じていただければ幸いです。
基本原則として、現時点での唯一にして必要な情報が詰まっている公式ガイド の Get Started with Salesforce Functions をなぞっていきます。
前提条件
Set Up Your Development Environmentに詳しいですが、ここにも載せておきます。
- git コマンド
- sfdx コマンド
- sf コマンド
- JavaScript/TypeScript を使う場合: Node.js 14以降, npm 6.13.4以降
- Java を使う場合: OpenJDK 8以降, Apaceh Maven 3.6.3以降
- Functions を Local で実行する場合: Docker Desktop
- GitHub あると便利
- RFC5424 に対応した Logging as a Services あるとログ保管できて便利
Salesforce 環境の準備
こちらは白石さんのサイトやGet Started with Salesforce Functions をご覧ください。
ごめんね、わいの力ではセットアップするための権限がないのだよ(ノД`)シクシク
ということで、この後の処理に関しては、次の点が前提条件となります。
- DevHub が Enable されていること
- Functions が Enable されていること
- 適切な権限セットが開発者のプロファイルに適用されていること
詳細はConfigure Your Salesforce Orgをご覧ください。以後 Quick Start: Salesforce Functions にならって進めていきますね。
実行環境の準備
まずは、ソースコードや、Scratch Org、Functions を実行する環境などを準備していきましょう。
Salesforce DX プロジェクトの準備
まずは、手元のローカル環境からです。ソースコードを準備しましょう。適当な Working Directory へいって、次のコマンドです。今回作成するプロジェクトは YukaFunctionProject
にしましょう。ゆかちゃんの誕生日だし。
sf generate project -n YukaFunctionProject
わいの偉いところは、実行結果もそのまま載せてしまうことです。どうだ
$ sf generate project -n YukaFunctionProject
target dir = /Users/sho/working/sfdx/work
create YukaFunctionProject/config/project-scratch-def.json
create YukaFunctionProject/README.md
create YukaFunctionProject/sfdx-project.json
create YukaFunctionProject/.husky/pre-commit
create YukaFunctionProject/.vscode/extensions.json
create YukaFunctionProject/.vscode/launch.json
create YukaFunctionProject/.vscode/settings.json
create YukaFunctionProject/force-app/main/default/lwc/.eslintrc.json
create YukaFunctionProject/force-app/main/default/aura/.eslintrc.json
create YukaFunctionProject/scripts/soql/account.soql
create YukaFunctionProject/scripts/apex/hello.apex
create YukaFunctionProject/.eslintignore
create YukaFunctionProject/.forceignore
create YukaFunctionProject/.gitignore
create YukaFunctionProject/.prettierignore
create YukaFunctionProject/.prettierrc
create YukaFunctionProject/jest.config.js
create YukaFunctionProject/package.json
ということで、わいの Local Directory の構成を惜しみなく出していくパターン。恥じらいなどない。
参考: Create a Salesforce DX Project
DevHub への Login
まずは今回利用する Salesforce 環境を認証しておきましょう。sf login
します。ご丁寧に Org かい? Functions かい?と聞いてきてくれるので、まずは Org
です。
$ sf login
? What would you like to log into? (Use arrow keys)
❯ Salesforce Org
Salesforce Functions
したらば、いつもの Salesforce Login 画面が出てくるので、精一杯ログインしてください。
次にログイン先のOrgのAliasを決められますので、YukaDevHub
と名付けましょう。sfdx
とは違い、sf
コマンドはインタラクティブ要素高め。ちなみにsfdx
コマンド慣れしてるならそれでもいいですが、sf
にしとくといい気がしますよ。
? Set an alias for the org (leave blank for no alias) YukaDevHub
次は、Default Org にするかどうかです。DevHub側かOrg側かを選択できます。今回は(私に権限がないので)ストーリー通りに DevHub(Scratch Org)に環境を準備していきます。スペースを押して選択してね。忘れずに。私はいつも忘れます。忘れたら、コマンドやり直しましょう。
? Set the org as your default org? (Press <space> to select, <a> to toggle all, <i> to invert selection, and <enter> to proceed)
❯◉ target-dev-hub
◯ target-org
ということで、結果、こうなりました。
$ sf login
? What would you like to log into? Salesforce Org
? Set an alias for the org (leave blank for no alias) YukaDevHub
? Set the org as your default org? target-dev-hub
You're now logged in to tabe@functions-xxxx.xxx.
sf env list
コマンドで状況も確認できますよ。
sf env list
Salesforce Orgs
===============================================================================================================================================================
| Aliases Username Org ID Instance Url Auth Method Config
| ───────────────── ───────────────────────────────── ────────────────── ─────────────────────────────────────────────────────────── ─────────── ──────────────
(中略)
| YukaDevHub tabe@xxxxxxxxx-xxxx-xxx.xxx 00xx0000000xxxxXXX https://bdc40732-20210913-xxxxxxxxx-xxxxx.my.salesforce.com web target-dev-hub
今度は Functions 環境へのログインです。今度は functions
もつけてログインしましょう。一気にいけます。
$ sf login functions
Opening browser to https://cli-auth.heroku.com/sfdx/auth/cli/browser/(中略)?requestor=(中略)
Waiting for login... done
Saving credentials... done
このような Salesforce Functions へようこそ感のある画面が出てくるので、素直にlog In to Salesforce
してください。そこで、先ほどの DevHubと同じSalesforce環境へログインして紐付けます。
ログインがうまくいけばこのように、やったぜ(๑•̀ㅂ•́)و✧みたいな画面になります。
画面まで貼り付けて、わい偉い。
Scratch Org と Function Compute 環境の作成
次は実際に動かすための環境を準備します。Salesforce側は Scratch Org。Functions の呼び出し側ですね。それと、Functions が実際に動く Platform 環境を作成します。これは Compute Environment と呼ばれています。
まずは忘れないように Change Directory して、Working directory をプロジェクトルートディレクトリにしておきましょう。その後、みなさん Visual Studio Code 使っている気がするので、そういった方は code .
とかで起動しましょう。
$ cd YukaFunctionProject/
~/working/sfdx/work/YukaFunctionProject >
まず、このプロジェクトではFunctions
を使いますよ、と明言します。config/project-scratch-def.json
ファイル内のfeatures
に "Functions"
を追加します。ファイル全体だとこんな感じ。default にそのまま追記してます。にしても、sho compnay
ってなんやねん。
{
"orgName": "sho company",
"edition": "Developer",
"features": ["Functions", "EnableSetPasswordInApi"],
"settings": {
"lightningExperienceSettings": {
"enableS1DesktopEnabled": true
},
"mobileSettings": {
"enableS1EncryptedStoragePref2": false
}
}
}
次に、この Projectファイルをベースに Scratch org を作成します。
Scratch org なので、どうせ環境なくなるぜとユーザ名も公開していくスタイル。この Scratch org の名前は YukaScratchOrg
にしてます。Default の Org を設定しているので、default の DevHub内に Scratch org作ります。defaultしてしいていない場合は、-v
で該当する DevHub のユーザ名を指定しましょう。
$ sfdx force:org:create -s -f config/project-scratch-def.json -a YukaScratchOrg
(node:56403) [DEP0147] DeprecationWarning: In future versions of Node.js, fs.rmdir(path, { recursive: true }) will be removed. Use fs.rm(path, { recursive: true }) instead
(Use `node --trace-deprecation ...` to show where the warning was created)
Successfully created scratch org: 00D9A0000005HVMUA2, username: test-rc2ac7uzxq9b@example.com
今度は Functions の Compute環境を作りましょう。今作成した YukaScratchOrg
に関連する Compute環境を作成します。Compute 名をYukaComputeEnv
にしましたよ。
$ sf env create compute -o YukaScratchOrg -a YukaComputeEnv
Creating compute environment for org ID 00D9A0000005HVMUA2... done
New compute environment created with ID yukafun-00d9a0000005hvmua2-527
Connecting environments... done
Your compute environment with local alias YukaComputeEnv is ready.
参考: Create a Scratch Org and Compute Environment
ソースコードの準備とデプロイ
さぁ、実行するための環境を準備しました。ここまでの内容をまとめますね。
Salesforce 組織(DevHub): YukaDevHub (Default DevHub)
Scratch org: YukaScratchOrg (Default Org)
Functions Compute 環境: YukaComputeEnv
ちな、環境はこのように。
sf env list
Salesforce Orgs
===============================================================================================================================================================
| Aliases Username Org ID Instance Url Auth Method Config
| ───────────────── ───────────────────────────────── ────────────────── ─────────────────────────────────────────────────────────── ─────────── ──────────────
| YukaDevHub tabe@xxxxxxxxx-xxxx-xxx.xxx 00xx0000000xxxxXXX https://bdc40732-20210913-xxxxxxxxx-xxxxx.my.salesforce.com web target-dev-hub
Scratch Orgs
==================================================================================================================================================
| Aliases Username Org ID Instance Url Auth Method Config
| ────────────── ───────────────────────────── ────────────────── ───────────────────────────────────────────────────────── ─────────── ──────────
| YukaScratchOrg test-rc2ac7uzxq9b@example.com 00D9A0000005HVMUA2 https://site-platform-2388-dev-ed.cs46.my.salesforce.com web target-org
では、サンプルコードを準備していきましょう。
Function ソースコードの開発
まず、Functions 用のテンプレートを準備します。sf generate function
ですね。今回実行する関数の Function名をyukafunction
、利用する言語を javascript
とします。言語は、現時点でjavascript
,typescript
,java
が利用できます。
$ sf generate function -n yukafunction -l javascript
Created javascript function yukafunction in /Users/sho/working/sfdx/work/YukaFunctionProject/functions/yukafunction.
Before creating Scratch Orgs for development, please ensure that:
1. Enable Functions in your DevHub org
2. Add Functions to the "features" list in your scratch org definition JSON file, e.g. "features": ["Functions"]
プロジェクトルートの下に functions/yukafunction
ができたことがわかります。ここに Functions で実行するべきファイル(index.js)を実装します。作成されたファイル構成はこんな感じ。npm用の package.json
や、mocha
のテスト用ファイルなども準備されてます。.eslintrc
も準備があって気が利いてます。
functions/yukafunction/
├── .eslintrc
├── .mocharc.json
├── README.md
├── index.js
├── package.json
├── project.toml
└── test
└── index.test.js
なお、ここにあるproject.toml
ファイルは、Functions 用のメタデータを定義するファイルです。詳細は Manage FUnction Metadata TOML Filesをどうぞ。
今回準備するソースコードは、こちら。外部ライブラリを試していただきたいので、yahoo-stock-prices
パッケージを使って株価を取得するという、ただそれだけのコードです。とは言え、外部のシステムとの通信が発生するので、同期や非同期の考慮が必要なパターンです。
import { getCurrentData } from "yahoo-stock-prices";
export default async function (event, context, logger) {
logger.info(`${JSON.stringify(event.data || {})}`);
const result = await getCurrentData(event.data.id);
logger.info(JSON.stringify(result));
return result;
};
ペイロードに id
を指定して、特定の株価情報を取得できます。また logger.info
で logdrain 経由でログが出力されます。うまく活用しましょう。
それから今回は、外部のパッケージを利用するので、package.json
も更新しておきましょう。
次の行を追加しておきます。
"dependencies": {
"yahoo-stock-prices": "^1.1.0"
},
参考: Create a Salesforce Function
権限セットの更新とデプロイ
実は今回のケースでは権限セットをいじる必要がありません。なぜなら、Functions から Salesforce 内部へアクセスが発生しないからです!(ババン)
面倒事は後回しという気持ちが少なからずあったことは認めましょう。ともかく、functions のcontext
を利用して Salesforceへアクセスする場合には、該当する権限セットの情報を準備して、それをデプロイする必要がある。ことを覚えましょう。
参考: Update and Deploy Permissions
ローカルでテストしてみよう
ローカルでテストを実行するには、次の前提条件が必要です。
- Docker Desktop が稼働していること
- Docker Desktop が Docker Service にログインしていること
- Function のルートディレクトリで実行すること
ローカルでは、Docker のイメージを動かして、その Listen port へ特定のペイロード情報を送って実行結果の確認ができます。いずれも sf
コマンドで行います。
Docker イメージの作成と起動
まず、ディレクトリをプロジェクトルートから、Function のルートへ移ります。今回の場合は、yukafunction
という名前を付けていますから...
$ cd functions/yukafunction/
そのあとは、そのディレクトリで sf run function start
するだけです。
長いけど、ログがあると嬉しい人がいると思うので、そのまま載せておきます。試せない人もいると思うので。
$ sf run function start
Building yukafunction
20: Pulling from heroku/buildpacks
0012d49a4463: Pull complete
b21ff80e29f9: Pull complete
22f8656dcc5d: Pull complete
17c352941ff7: Pull complete
54fb12506777: Pull complete
357fefdf9bc9: Pull complete
d9d5783cd0f5: Pull complete
ae860f4e3e08: Pull complete
6018af265953: Pull complete
5035dafe4b03: Pull complete
c10fd1b9aa75: Pull complete
03fb7dba7baa: Pull complete
8f7ab136a2c9: Pull complete
08d54b036422: Pull complete
0494ca24cd11: Pull complete
a0a5647c84a6: Pull complete
6a19c4a9cc6d: Pull complete
bcbb9964778f: Pull complete
96466d5c0dc7: Pull complete
f18d74887f79: Pull complete
d2a2659301d3: Pull complete
04ee91025a13: Pull complete
088b851c25af: Pull complete
a24d744b6321: Pull complete
bcd31301c840: Pull complete
4bfdb67d04f5: Pull complete
918e3d4177c3: Pull complete
9215aa0839f1: Pull complete
4f4fb700ef54: Pull complete
Digest: sha256:35b866c6ce0c5bdc55f0e888bad420f537cfedb4544b80b049ad60ca021e4501
Status: Downloaded newer image for heroku/buildpacks:20
20: Pulling from heroku/pack
7b1a6ab2e44d: Already exists
525e7e2b2cee: Already exists
0012d49a4463: Already exists
a4a4b0767176: Pull complete
5f2534680b11: Pull complete
Digest: sha256:41e80b174e8b4fe932f8c2b752198af2523b752783db7e27be941fe88387c1dd
Status: Downloaded newer image for heroku/pack:20
===> DETECTING
3 of 4 buildpacks participating
heroku/nodejs-engine 0.7.4
heroku/nodejs-npm 0.4.4
heroku/nodejs-function-invoker 0.2.7
===> ANALYZING
Previous image with name "yukafunction" not found
===> RESTORING
===> BUILDING
[INFO] Node.js Buildpack
[INFO] Setting NODE_ENV to production
[INFO] Installing toolbox
[INFO] - yj
[Installing Node]
[INFO] Getting Node version
[INFO] Resolving Node version
[INFO] Downloading and extracting Node v16.13.1
[Parsing package.json]
[INFO] Parsing package.json
[INFO] Using npm v8.1.2 from Node
[INFO] Installing node modules
added 224 packages, and audited 225 packages in 12s
26 packages are looking for funding
run `npm fund` for details
5 moderate severity vulnerabilities
To address all issues (including breaking changes), run:
npm audit fix --force
Run `npm audit` for details.
npm notice
npm notice
New minor version of npm available! 8.1.2 -> 8.3.0
npm
notice Changelog: <https://github.com/npm/cli/releases/tag/v8.3.0>
npm notice Run `npm install -g npm@8.3.0` to update!
npm
notice
[Warning: Skip pruning because NODE_ENV is not 'production'.]
[Installing Node.js function runtime]
[INFO] Installing @heroku/sf-fx-runtime-nodejs@0.9.1...
npm WARN deprecated har-validator@5.1.5: this library is no longer supported
npm WARN deprecated uuid@3.4.0: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.
npm WARN deprecated request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142
added 245 packages, and audited 246 packages in 16s
14 packages are looking for funding
run `npm fund` for details
3 moderate severity vulnerabilities
To address all issues, run:
npm audit fix
Run `npm audit` for details.
[INFO] @heroku/sf-fx-runtime-nodejs@0.9.1 installation successful
===> EXPORTING
Adding layer 'heroku/nodejs-engine:nodejs'
Adding layer 'heroku/nodejs-function-invoker:opt'
Adding layer 'heroku/nodejs-function-invoker:sf-fx-runtime-nodejs'
Adding 1/1 app layer(s)
Adding layer 'launcher'
Adding layer 'config'
Adding layer 'process-types'
Adding label 'io.buildpacks.lifecycle.metadata'
Adding label 'io.buildpacks.build.metadata'
Adding label 'io.buildpacks.project.metadata'
Setting default process type 'web'
Saving yukafunction...
*** Images (a52d9e8b9519):
yukafunction
Adding cache layer 'heroku/nodejs-engine:nodejs'
Adding cache layer 'heroku/nodejs-engine:toolbox'
Starting yukafunction
Running on port :8080
Debugger running on port :9229
Debugger listening on ws://0.0.0.0:9229/8a40ce5f-1df1-401e-958e-26045307974b
For help, see: https://nodejs.org/en/docs/inspector
Running on port
などがでてくればローカルでうまく動いてます。停止するには Ctrl+c
ですね。なんかそこら中に heroku
って書かれていることが気になるかもしれませんが、気にしないことが正義です。
実際に function を実行してみよう
今度は別のコマンドコンソールから実行します。sf run function
コマンドを使います。-p
オプションで、渡したいペイロードの内容を指定します。今回は "CRM" ですね。Salesforce です。日本時間の2021/12/23の株価を取得できました。-o
で該当する Salesforce組織も指定できます。今回のように指定しないと、default の組織が利用されます。
$ sf run function -l http://localhost:8080 -p '{"id": "CRM"}'
› Warning: No -o connected org or target-org found, context will be partially initialized
Using target-org undefined login credential to initialize context
POST http://localhost:8080... 200
{
"currency": "USD",
"price": 252.8
}
ちゃんと取得できているようですね。嬉しい。
なお、我らがゆかちゃんの所属するAmuseも取得できます。2021/12/23 の終値です。
sf run function -l http://localhost:8080 -p '{"id": "4301.T"}'
› Warning: No -o connected org or target-org found, context will be partially initialized
Using target-org undefined login credential to initialize context
POST http://localhost:8080... 200
{
"currency": "JPY",
"price": 2035
}
え、ちゃんと動いてるじゃん、スゴイ、を体験できます。Functions 環境を持てない方でもこのようにローカルでお試しできます。
Function をデプローイ
さあ、ついにデプロイの時間です。まずは Function からデプロイしていきます。ローカルのテストをされた方は Working directory が変わっているので、DXプロジェクトのルートディレクトリへ戻ってください。たいてい cd -
で戻れそうですけれども。ともかく、ディレクトリは間違わないように。
さて、Functions はデプロイにあたり、git で commit されている必要があります。まだ git 準備していないので、git の準備からです。次の三種の神器で git初期化です。
git init
git add .
git commit
実際に試してみると、こう。
$ git init
Initialized empty Git repository in /Users/sho/working/sfdx/work/YukaFunctionProject/.git/
$ git add .
$ git commit -m 'first commit'
[main (root-commit) fa3ccfb] first commit
25 files changed, 395 insertions(+)
create mode 100644 .eslintignore
create mode 100755 .forceignore
create mode 100644 .gitignore
create mode 100755 .husky/pre-commit
create mode 100755 .prettierignore
create mode 100755 .prettierrc
create mode 100644 .vscode/extensions.json
create mode 100644 .vscode/launch.json
create mode 100644 .vscode/settings.json
create mode 100644 README.md
create mode 100644 config/project-scratch-def.json
create mode 100644 force-app/main/default/aura/.eslintrc.json
create mode 100644 force-app/main/default/lwc/.eslintrc.json
create mode 100644 functions/yukafunction/.eslintrc
create mode 100644 functions/yukafunction/.mocharc.json
create mode 100644 functions/yukafunction/README.md
create mode 100644 functions/yukafunction/index.js
create mode 100644 functions/yukafunction/package.json
create mode 100644 functions/yukafunction/project.toml
create mode 100644 functions/yukafunction/test/index.test.js
create mode 100644 jest.config.js
create mode 100644 package.json
create mode 100644 scripts/apex/hello.apex
create mode 100644 scripts/soql/account.soql
create mode 100644 sfdx-project.json
よし、git commit できたら sf deploy functions
コマンドでデプロイ行います。-o
でデプロイ先の組織を指定できます。今回は Scratch orgなので、YukaScratchOrg
ですね。default なので指定しなくても良さそうだけど。
sf deploy functions -o YukaScratchOrg
remote: Compressing source files... done.
remote: Building source:
remote: [yukafunction] 3 of 4 buildpacks participating
remote: [yukafunction] heroku/nodejs-engine 0.7.4
remote: [yukafunction] heroku/nodejs-npm 0.4.4
remote: [yukafunction] heroku/nodejs-function-invoker 0.2.7
remote: [yukafunction] Previous image with name "*****/7e4dd32c-88b5-4b30-b196-d4fd77ebfdea/yukafunction" not found
remote: [yukafunction] Layer cache not found
remote: [yukafunction] Layer cache not found
remote: [yukafunction] [INFO] Node.js Buildpack
remote: [yukafunction] [INFO] Setting NODE_ENV to production
remote: [yukafunction] [INFO] Installing toolbox
remote: [yukafunction] [INFO] - yj
remote: [yukafunction]
remote: [yukafunction] [Installing Node]
remote: [yukafunction] [INFO] Getting Node version
remote: [yukafunction] [INFO] Resolving Node version
remote: [yukafunction] [INFO] Downloading and extracting Node v16.13.1
remote: [yukafunction]
remote: [yukafunction] [Parsing package.json]
remote: [yukafunction] [INFO] Parsing package.json
remote: [yukafunction] [INFO] Using npm v8.1.2 from Node
remote: [yukafunction] [INFO] Installing node modules
remote: [yukafunction] npm WARN deprecated har-validator@5.1.5: this library is no longer supported
remote: [yukafunction] npm WARN deprecated uuid@3.4.0: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.
remote: [yukafunction] npm WARN deprecated request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142
remote: [yukafunction]
remote: [yukafunction] added 264 packages, and audited 265 packages in 7s
remote: [yukafunction]
remote: [yukafunction] 26 packages are looking for funding
remote: [yukafunction] run `npm fund` for details
remote: [yukafunction]
remote: [yukafunction] 5 moderate severity vulnerabilities
remote: [yukafunction]
remote: [yukafunction] To address all issues (including breaking changes), run:
remote: [yukafunction] npm audit fix --force
remote: [yukafunction]
remote: [yukafunction] Run `npm audit` for details.
remote: [yukafunction] npm notice
remote: [yukafunction] npm notice New minor version of npm available! 8.1.2 -> 8.3.0
remote: [yukafunction] npm notice Changelog: <https://github.com/npm/cli/releases/tag/v8.3.0>
remote: [yukafunction] npm notice Run `npm install -g npm@8.3.0` to update!
remote: [yukafunction] npm notice
remote: [yukafunction]
remote: [yukafunction] [Warning: Skip pruning because NODE_ENV is not 'production'.]
remote: [yukafunction]
remote: [yukafunction]
remote: [yukafunction] [Installing Node.js function runtime]
remote: [yukafunction] [INFO] Installing @heroku/sf-fx-runtime-nodejs@0.9.1...
remote: [yukafunction] npm WARN deprecated har-validator@5.1.5: this library is no longer supported
remote: [yukafunction] npm WARN deprecated uuid@3.4.0: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.
remote: [yukafunction] npm WARN deprecated request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142
remote: [yukafunction]
remote: [yukafunction] added 245 packages, and audited 246 packages in 6s
remote: [yukafunction]
remote: [yukafunction] 14 packages are looking for funding
remote: [yukafunction] run `npm fund` for details
remote: [yukafunction]
remote: [yukafunction] 3 moderate severity vulnerabilities
remote: [yukafunction]
remote: [yukafunction] To address all issues, run:
remote: [yukafunction] npm audit fix
remote: [yukafunction]
remote: [yukafunction] Run `npm audit` for details.
remote: [yukafunction] [INFO] @heroku/sf-fx-runtime-nodejs@0.9.1 installation successful
remote: [yukafunction] Adding layer 'heroku/nodejs-engine:nodejs'
remote: [yukafunction] Adding layer 'heroku/nodejs-function-invoker:opt'
remote: [yukafunction] Adding layer 'heroku/nodejs-function-invoker:sf-fx-runtime-nodejs'
remote: [yukafunction] Adding 1/1 app layer(s)
remote: [yukafunction] Adding layer 'launcher'
remote: [yukafunction] Adding layer 'config'
remote: [yukafunction] Adding layer 'process-types'
remote: [yukafunction] Adding label 'io.buildpacks.lifecycle.metadata'
remote: [yukafunction] Adding label 'io.buildpacks.build.metadata'
remote: [yukafunction] Adding label 'io.buildpacks.project.metadata'
remote: [yukafunction] Setting default process type 'web'
remote: [yukafunction] Saving *****/7e4dd32c-88b5-4b30-b196-d4fd77ebfdea/yukafunction...
remote: [yukafunction] *** Images (sha256:22de57e28ea2252be9ba6807b71d447bd22889caf8f77b1bb0339a27a5b1c813):
remote: [yukafunction] *****/7e4dd32c-88b5-4b30-b196-d4fd77ebfdea/yukafunction:071851ff-9659-4c29-abbc-867eec6381e4
remote: [yukafunction] Layer cache not found
remote: [yukafunction] Adding cache layer 'heroku/nodejs-engine:nodejs'
remote: [yukafunction] Adding cache layer 'heroku/nodejs-engine:toolbox'
remote: Verifying deploy... done.
To https://git.heroku.com/yukafun-00d9a0000005hvmua2-527.git
* [new branch] main -> master
Reference for YukaFunctionProject-yukafunction created
Pushing changes to functions... done
はい、ということでなんか見覚えのあるようなないような感じですね。git 使ってるのは、そうかデプロイ先が git で受け付ける仕組みでソースコードが pushされてるんだな。ははーんみたいな気持ちになりますね。なんですかね。見覚えのある単語もたくさんあります。へぇ。
Apex をデプローイ
ついにやってきました。最後です。Webinar でも紹介しましたが、現時点では Salesforce Functions は Apex からコールして実行する方法をとります。従って、Apex からの実行が必要です。では、Scratch org 側に function をコールする Apex を準備しましょう。
久方ぶりに登場、まずは sfdx force:apex:class:create
を使って、Apex Class のひな形を準備しましょう。せっかくなので、Apex の名前も YukaFunctionApex
としてみます。
$ sfdx force:apex:class:create -n YukaFunctionApex -d force-app/main/default/classes
target dir = /Users/sho/working/sfdx/work/YukaFunctionProject/force-app/main/default/classes
create force-app/main/default/classes/YukaFunctionApex.cls
create force-app/main/default/classes/YukaFunctionApex.cls-meta.xml
あと、ちょっと Callback(非同期) のコードもサンプルお見せしたいので、Callback 用のClassも準備します。こっちは急に日和って testCallback
とかにしてます。
$ sfdx force:apex:class:create -n testCallback -d force-app/main/default/classes
target dir = /Users/sho/working/sfdx/work/YukaFunctionProject/force-app/main/default/classes
create force-app/main/default/classes/testCallback.cls
create force-app/main/default/classes/testCallback.cls-meta.xml
ということでソースコードです。
functions.Function.get()
で、呼び出したい function を指定します。指定方法は プロジェクト名
.function名
ですから、今回は YukaFunctionProject.yukafunction
となります。
コールする時は .invoke
します。ペイロードのJSONを、そのまま引数として入れます。id
のみを今回は受け付けるので、{"id": "CRM"}
のようにしています。
コールした時のオブジェクトに .getResponse()
すると function の実行結果を取得できます。
なお、このソースコードは testSync()
メソッドが同期呼び出しで、testAsync()
が非同期呼び出しとなります。
非同期の場合には、invoke
する時に Callback の Class が必要です。Callback Class を引数に入れるだけで、自動的に非同期コールになります。また、testAsync()
には @InvocableMethod
を付与していますので、フローなどからの呼び出しにも対応してます。
public with sharing class YukaFunctionApex {
public static void testSync() {
System.debug('Invoking myfunction');
functions.Function myFunction = functions.Function.get('YukaFunctionProject.yukafunction');
functions.FunctionInvocation invocation = myFunction.invoke('{"id": "CRM"}');
String jsonResponse = invocation.getResponse();
System.debug('Response from myfunction ' + jsonResponse);
}
@InvocableMethod(label='Invoke yukafunctionFunctions(asyncnronous)')
public static void testAsync() {
System.debug('Invoking asyncronous function');
functions.Function myFunction = functions.Function.get('YukaFunctionProject.yukafunction');
functions.FunctionInvocation invocation = myFunction.invoke('{"id": "4301.T"}', new testCallback());
String jsonResponse = invocation.getResponse();
System.debug('Response from myfunction ' + jsonResponse);
}
}
そしてもう一つ。Callback 側のソースコードです。
public class testCallback implements functions.FunctionCallback {
public void handleResponse(functions.FunctionInvocation result) {
System.debug('Response from callback ' + result);
}
}
functions.FunctionCallback
を implements
した Class 内に handleResponse
メソッドを上書きすることで、コールバック関数として対処されます。ここでは、結果を debug log に吐き出しているだけですね。
あ、それと、今だけだと思いますが、テンプレートのメタデータでは APIバージョンが低いので変更の必要があります。YukaFunctionApex.cls-meta.xml
と testCallback.cls-meta.xml
の <apiVersion>52.0</apiVersion>
を <apiVersion>53.0</apiVersion>
にします。Functions は Api53.0以降での提供になるので、ここでエラーになってしまうという罠があります。気をつけましょう。
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>53.0</apiVersion>
<status>Active</status>
</ApexClass>
さて、それでは、Scratch org にこれらのApex Classをpushして実装しましょう。sfdx force:sorce:push
コマンドで -u
で対象の Scratch org を指定します。YukaScratchOrg
ですね。では、実行。
$ sfdx force:source:push -u YukaScratchOrg
*** Deploying with SOAP ***
Job ID | 0Af9A00000XP8ArSAL
SOURCE PROGRESS | ████████████████████████████████████████ | 2/2 Components
=== Pushed Source
STATE FULL NAME TYPE PROJECT PATH
───── ──────────────── ───────── ────────────────────────────────────────────────────────────
Add YukaFunctionApex ApexClass force-app/main/default/classes/YukaFunctionApex.cls
Add YukaFunctionApex ApexClass force-app/main/default/classes/YukaFunctionApex.cls-meta.xml
Add testCallback ApexClass force-app/main/default/classes/testCallback.cls
Add testCallback ApexClass force-app/main/default/classes/testCallback.cls-meta.xml
デプロイうまく行きましたね!!! では、ちょっと試してみましょう。sfdx force:apex:execute
を使ってたたくのが楽です。echo
で Class 名を指定して実行です。USER_DEBUG
に今日の株価情報のJSONが帰ってきていることが分かります。やったぜ(๑•̀ㅂ•́)و✧
$ echo "YukaFunctionApex.testSync();" | sfdx force:apex:execute -f /dev/stdin
Compiled successfully.
Executed successfully.
54.0 APEX_CODE,DEBUG;APEX_PROFILING,INFO
Execute Anonymous: YukaFunctionApex.testSync();
00:35:27.385 (385355369)|USER_INFO|[EXTERNAL]|0059A000004swgT|test-rc2ac7uzxq9b@example.com|(GMT-08:00) Pacific Standard Time (America/Los_Angeles)|GMT-08:00
00:35:27.385 (385425026)|EXECUTION_STARTED
00:35:27.385 (385449089)|CODE_UNIT_STARTED|[EXTERNAL]|execute_anonymous_apex
00:35:27.385 (409544662)|USER_DEBUG|[3]|DEBUG|Invoking myfunction
00:35:30.478 (3478610625)|USER_DEBUG|[9]|DEBUG|Response from myfunction {"currency":"USD","price":252.8}
00:35:30.481 (3481485985)|CUMULATIVE_LIMIT_USAGE
00:35:30.481 (3481485985)|LIMIT_USAGE_FOR_NS|(default)|
Number of SOQL queries: 0 out of 100
Number of query rows: 0 out of 50000
Number of SOSL queries: 0 out of 20
Number of DML statements: 0 out of 150
Number of Publish Immediate DML: 0 out of 150
Number of DML rows: 0 out of 10000
Maximum CPU time: 0 out of 10000
Maximum heap size: 0 out of 6000000
Number of callouts: 0 out of 100
Number of Email Invocations: 0 out of 10
Number of future calls: 0 out of 50
Number of queueable jobs added to the queue: 0 out of 50
Number of Mobile Apex push calls: 0 out of 10
00:35:30.481 (3481485985)|CUMULATIVE_LIMIT_USAGE_END
00:35:30.478 (3481636943)|CODE_UNIT_FINISHED|execute_anonymous_apex
00:35:30.478 (3481659914)|EXECUTION_FINISHED
なお、非同期で実行すると、こう。
echo "YukaFunctionApex.testAsync();" | sfdx force:apex:execute -f /dev/stdin
Compiled successfully.
Executed successfully.
54.0 APEX_CODE,DEBUG;APEX_PROFILING,INFO
Execute Anonymous: YukaFunctionApex.testAsync();
00:40:40.190 (190047301)|USER_INFO|[EXTERNAL]|0059A000004swgT|test-rc2ac7uzxq9b@example.com|(GMT-08:00) Pacific Standard Time (America/Los_Angeles)|GMT-08:00
00:40:40.190 (190094631)|EXECUTION_STARTED
00:40:40.190 (190106483)|CODE_UNIT_STARTED|[EXTERNAL]|execute_anonymous_apex
00:40:40.190 (192176364)|USER_DEBUG|[14]|DEBUG|Invoking asyncronous function
00:40:42.252 (2252991248)|USER_DEBUG|[20]|DEBUG|Response from myfunction null
00:40:42.409 (2409363885)|CUMULATIVE_LIMIT_USAGE
00:40:42.409 (2409363885)|LIMIT_USAGE_FOR_NS|(default)|
Number of SOQL queries: 0 out of 100
Number of query rows: 0 out of 50000
Number of SOSL queries: 0 out of 20
Number of DML statements: 0 out of 150
Number of Publish Immediate DML: 0 out of 150
Number of DML rows: 0 out of 10000
Maximum CPU time: 0 out of 10000
Maximum heap size: 0 out of 6000000
Number of callouts: 0 out of 100
Number of Email Invocations: 0 out of 10
Number of future calls: 0 out of 50
Number of queueable jobs added to the queue: 0 out of 50
Number of Mobile Apex push calls: 0 out of 10
00:40:42.409 (2409363885)|CUMULATIVE_LIMIT_USAGE_END
00:40:42.252 (2409514393)|CODE_UNIT_FINISHED|execute_anonymous_apex
00:40:42.252 (2409539244)|EXECUTION_FINISHED
こちらは結果が帰ってきていませんね、当然です。非同期コールの後、testCallback
が実行され、Scratch org側の Debug logに出力されます。こんな感じで、なんかJPY
っぽいアミューズらしいデータが返ってる気配ありますね。
参考: Invoke a Function From Apex
ということで、GA された環境での実行方法について流してみました。デバッグログの見方や、Loggin as a Service との連携なども行うことで、本番としての利用方法が見えてくるでしょう。次回以降は、パッケージやロギングについてもひとつずつ紹介します。今回みたいに長いのは、私も苦痛だし、呼んでるみなさまも苦痛でしょう。申し訳ない。
ところで「いいな、あたいも試したいずらよ!」という方、現在、トライアルはサインナップが必要でかつ制限があります。お金があるよと言う場合は購入も検討ください。購入に当たっては、私(と仲間たち)で精一杯ご支援します。まずは、みなさまのご担当の営業にご相談ください。
Discussion