SFDX x Node.js でテストデータをサクッと登録するスクリプトを作成した話とハマった話、そして約5年ぶりにSFDC開発をするエンジニアによるSFDXやLwcなどに対する所感
最近数年ぶりに Salesforce を触る機会がありました。というかあります。
そこら辺については下記をご参照のほど、何卒よろしくお願いいたします。
みなさんテストデータってどうやって作ってますか?
アルプの Salesforce 開発におけるセットとしましては、下記あたりを使ってます。
- ApexCode
- Aura Component
- Lightning Web Component
- 他、既存機能色々
環境としましては、
- Scratch組織
- SFDX
などを使っており、Scratch組織を作成したタイミングでSFDXを利用してプロビジョニング(プロビジョニングと言っていいの?)しているのですが、動作環境などに使うテストデータを毎回手でポチポチと登録するのがめんどくさかったんですよね。
例えば、Account と Contact をセットとしたとき、色々なバリエーションが必要で毎回手で入力していたのですが、これがもう腱鞘炎になるレベル。
こういう特に何も考えずに行う作業はやはり自動化したいということで、 force:data:tree:import を使いました。
これはおそらく誰でもやっていることだと思いますし、今さら使い方を書いてもしょうがない(上記のリンクからドキュメント読んだほうが早いし良い)ので、詳しい使い方は割愛します。
スクリプト化しよう
作成するテストデータによりバリエーションを持たせたいという欲求が生まれてくるのも自然でして、そこで Node.js を使ってサクッとスクリプトを作成したろうという話です。
一応仕事で作ったスクリプトなので全容は控えるのですが、ざっくり書いておくと
const fs = require('fs') const dateFormat = require('dateformat') const { execSync } = require('child_process') const justNow = dateFormat(new Date(), 'hhMMss') fs.writeFileSync('./scripts/tmp/Accounts.json', fs.readFileSync('./scripts/testDataSources/Accounts.json', 'utf8').replace(/hhMMss/gi, justNow)) fs.writeFileSync('./scripts/tmp/Contacts.json', fs.readFileSync('./scripts/testDataSources/Contacts.json', 'utf8').replace(/hhMMss/gi, justNow)) execSync('cp -p scripts/testDataSources/Account-Contact-plan.json scripts/tmp/Account-Contact-plan.json') execSync(`sfdx force:data:tree:import -p scripts/tmp/Account-Contact-plan.json -u ${options.org}`)
みたいな感じです。
解説
下記はテストデータの元として ./scripts/testDataSources/Accounts.json
を読み込み、 hhMMss
を現在時間に置換した後に ./scripts/tmp/Accounts.json
に書き込みを行ってます。
fs.writeFileSync('./scripts/tmp/Accounts.json', fs.readFileSync('./scripts/testDataSources/Accounts.json', 'utf8').replace(/hhMMss/gi, justNow))
Account.json
の中身はざっくりですが
{ "records": [ { "attributes": { "type": "Account", "referenceId": "hhMMss01_A" }, "Name": "hhMMss01_アカウント名" }, { "attributes": { "type": "Account", "referenceId": "hhMMss02_A" }, "Name": "hhMMss02_アカウント名" } ] }
となっておりまして、 .replace(/hhMMss/gi, justNow)
でID値などを現在時分秒に置換することでユニークな値を作成しています。
Contact.json
は下記のような感じです。こちらも Account.json
同様、ID値を置換してユニークな値を作成しています。
{ "records": [ { "attributes": { "type": "Contact", "referenceId": "hhMMss01_C" }, "LastName": "hhMMss01_名字", "FirstName": "太郎", "Email": "foo_hhMMss01@omiend.com", "AccountId": "@hhMMss01_A" }, { "attributes": { "type": "Contact", "referenceId": "hhMMss02_C" }, "LastName": "hhMMss02_名字", "FirstName": "次郎", "Email": "baa_hhMMss02@omiend.com", "AccountId": "@hhMMss02_A" }, ] }
次に、
execSync('cp -p scripts/testDataSources/Account-Contact-plan.json scripts/tmp/Account-Contact-plan.json')
を実行してplanファイルをコピーし、
execSync(`sfdx force:data:tree:import -p scripts/tmp/Account-Contact-plan.json -u ${options.org}`)
にてimportを実行しています。
本当はもっと色々してますが、まあこんな感じですかねえ。
ハマったところ
Accountなどの referenceId
に指定している hhMMss01_A
ですが、初めは yyyymmddhhMMss01_A
のようにして年月日時分秒を指定してました。
しかし、2回目以降どうしても下記エラーが出てしまいます。
{ "hasErrors": true, "results": [ { "referenceId": "2020032707565901_A", "errors": [ { "statusCode": "DUPLICATES_DETECTED", "message": "Duplicate Alert", "fields": [] } ] } ] }
年月日時分秒なので秒単位でユニークになるハズなのと、エラーメッセージが DUPLICATES_DETECTED
のみなので何も言っていない(本当に何も言っていない)に等しく、まったく原因が分から無いままおそらく丸1日ほどハマっていました。
が、ふと原因に気づきました。
おそらくですが、ID値が長すぎてどこかで末尾が欠落し、例えば 2020032707565901_A
が 20200327
の様になってしまったのではないかなと思います。
そのため、 yyyymmddhhMMss01_A
ではなく hhMMss01_A
にしている次第です。
また、 Name
属性もユニークにする必要があるようです。
とまあ、Node.js が使えるので基本的になんでも出来るのは良いなあと思いました。
DUPLICATES_DETECTED
は文献を漁っても原因が分からなかっただけに、気づいた時は心底安心するとともに、もし同様のエラーでハマっている方のお力に少しでもなればと思います。
その文字列を観た感じ重複しているというのはワカルのですが、そんなハズ無いという思い込みが捨てられたらというのと、もう少し丁寧なエラーメッセージがほしいなと言ったところですかね笑
SFDX所感
僕がSalesforceの開発案件に携わっていたのがもうかれこれ5年前だったりするのですが、当時は Eclipse にプラグインを入れて開発していました。
しかし、コードの補完が効きづらかったり、ソースコードのデプロイ速度が遅かったためローカルで書いたソースをブラウザにコピペして開発していたりと、お世辞にも良い開発体験ではなかったと思います。
SFDXはその頃の教訓をかなり活かせている気がします。
Visual Studio Code を基本として開発しているのですが、プラグインは良く出来ていますし、デプロイ速度も当時に比べて格段に向上しています。
コマンドも一通りそろってますし、開発する上でのストレスはかなり低減されていました。
Lightning Web Component
Lightning Web Component は特にすんなり入りやすかったです。
HTML, Script, CSS が別れている点が Vue に少し似ていると思いました。
JavaScript は ECMAScript フレンドリーに記述できますし、HTML と JavaScript の連携もとても楽です。
CSS はあまり書かないです。理由としては Lightning Design System が提供するコンポーネントを使えば(今の所)事足りるからです。
ただ、 Lwc について一つ思うのが @wire
を利用する際に fields
にフィールド名を指定するのは嫌な気がします。
ココ( Understand the Wire Service )にある↓こういうやつ。
@wire(getRecord, { recordId: '$recordId', fields: [ACCOUNT_NAME_FIELD] }) record;
実装上楽になるかもしれませんが、これだとフロントがSOQLを書いているという意味にもなりますので、そこはフロントの責務から剥がしたい気持ちがありました。
そのため、下記のように画面で必要なデータは connectedCallback
で ApexCode を呼びだして、SOQL は ApexCode 側に寄せるように実装してます。
↓下記のイメージ
import getAccount from "@salesforce/apex/AccountController.getAccount" @api recordId @track account async connectedCallback() { this.account = await getAccount({ recordId: this.recordId }) .catch(error => { console.error(error) }) }
しかし、そうするとやはり ApexCode はカオス化しやすい気がしますね。その点は設計思想などでカバーできればよいかな。
ただ、 @wire
の存在を考えると、今後は Lwc の js 層が ApexCode に取って代わる未来にしたいのかなという考えもわかります(実際Salesforce社がどう考えているかまでは追っていません)。
おしまいに
久しぶりにSalesforceの開発へと戻ってきたのですが、かなり浦島太郎状態で、ちょっと辛かった当時と比べると思ったよりも楽しいです。
というわけで、アルプ株式会社では、絶賛 Salesforce エンジニアを探しております。
気になったそこのあなた、お気軽にお問い合わせください。Salesforce エンジニアに限らず採用は行っておりますので、併せてご確認ください!