Salesforce レコードデータの自動マイグレーションを行う sfdx-migration-automatic

## Salesforceにおける開発時のデータマイグレーションについて

Salesforceの開発においてScratch組織の利用は安定的かつ継続的な開発サイクルを実現するのに大変有効です。
[Salesforce CLI](https://developer.salesforce.com/ja/tools/sfdxcli)を利用することで、開発コードおよびメタデータのプロビジョニングの自動化はかなり簡単になっています。

しかしながら（特にE2EテストやPullRequestの受入確認においては）必要なデータをあらかじめScratch組織に用意しておくことが求められます。

従来の（使い捨てでない）開発組織を利用する限りにおいては、初回セットアップ時にデータローダーなどでデータ移入を手作業で行ったり、Full/Partial Sandboxを利用したり、またSalesforceのパートナーであればTrialforceの仕組みを利用することもできましたが、使い捨てのScratch組織が前提となったことでこれらはあまり意味がなくなりました。つまり、開発時のデータのマイグレーションを自動化することが求められています。

## いままでのデータマイグレーション手法

Salesforceでもある程度大きな開発プロジェクトになってくると、[データローダーをコマンドラインで利用](https://help.salesforce.com/articleView?id=command_line_intro.htm&type=5)してデータ移入を自動化させているプロジェクトもあります。しかしながら、そもそもデータローダーのインターフェースはあまりコマンドラインでの利用を想定したものとは言い難く、さらに後述の依存関係の解決はプロジェクト依存で行わねばならないところがほとんどなのも辛いところです。

テストデータに限定した場合、プロジェクトによってはApexクラスなどで作成および移入を行っているケースもままありますが、コードベースでのデータ生成はメンテナンス性に乏しく、かつ汎用性に欠けるものとなりがちです。さらにガバナなどの制限を考えるとなかなか選択が難しいというのが実際のところではないかと思います。

## sfdx の data:tree コマンド

さて、実は Salesforce CLI の標準コマンドに `force:data:tree:export`および`force:data:tree:import` というものがありますが、このコマンドはデータのマイグレーションに利用できます。しかしながら、一度に移入可能なのはSOQLクエリで取得できる範囲に限られるため、複雑なリレーションシップを持ったデータ構造については難しい（3段階以上の関連を持つオブジェクトなど）という弱点がありました。これは内部で移入のためのAPIとして[SObject Tree API](https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_composite_sobject_tree.htm)を利用しているためなのですが、なかなか使い所が難しいと感じます。

## 温故知新: 「開発用デモデータ集」パッケージ

ここで、Salesforceのデモデータのセットをそのまま組織にロードできるような機構というものはいままでなかったのか、少し疑問に思っている方もいるかも知れません。もしかしたら使ったことがある方もいるかも知れませんが、「開発用デモデータ集」というAppExchangeパッケージが古に(2007年〜)存在していました（現在は非公開ですがまだページは[ある](https://appexchangejp.salesforce.com/listingDetail?listingId=a0N300000016YwSEAU)）。

このパッケージはなんとSコントロール（ご存知でしょうか？）の上で[Ajax Toolkit](https://developer.salesforce.com/docs/atlas.en-us.ajax.meta/ajax/sforce_api_ajax_introducing.htm)を利用してデータロードを行うパッケージだったのですが、特筆すべき点として、レコードの依存関係を自動的に解決して順番にロードしてくれるという仕組みがありました。

つまり、以下のようなデータ群があるとして

*Account*

| Id | Name | ParentId |
|:--|:--|:--|
| a00001  | 株式会社初芝電機 | |
| a00002  | 株式会社初芝テクノロジー | a00001 |
| a00003  | サニー株式会社 | |

*Contact*

| Id | AccountId | FirstName | LastName |
|:--|:--|:--|:--|
| c00001  | a00001 | 山田 | 太郎 |
| c00002  | a00001 | 鈴木 | 花子 |
| c00003  | a00002 | 佐藤 | 一郎 |
| c00004  | a00003 | 田中 | 学 |

第1フェーズでは、他のレコードへの参照依存のない `a00001` および `a00003` レコードを挿入し、新たなIDとしてそれぞれ `A00001` および `A00003` が割り当てられるとします。

第2フェーズでは、依存の解決した`c00001`、`c00002` および `c00004` レコードと `a00002`レコードに対して、`a00001` および`a00003` の参照IDをそれぞれ得られた`A00001` および `A00003`に置き換えて挿入します。（ここで`a00002`のレコードの挿入結果として割り当てられたIDを `A00002`とします）

最後のフェーズで、残った `c00003`レコードに対して `a00002` の参照IDを`A00002`に置き換えて挿入する、という形です。

当時、外部IDを利用した手作業が主体だったデータロード作業において、1クリックでさくっとデータロードが可能なのはなかなか便利だったと考えています。

その後、Visualforce環境でも利用できるように独自カスタマイズしたものを弊社内では利用していましたが、しかしながらさすがにそろそろメンテナンスがめんどくさくなってきたのと、やはりUIを介したものよりも自動化を念頭に置いたものがほしいなということで、移行を考えていました。

## sfdx-migration-automatic の紹介

せっかくなのでSalesforce CLIのプラグインとして提供するのが一番ツールチェイン的に使いやすいと思い、作成したのがこちらです。

[sfdx-migration-automatic](https://github.com/stomita/sfdx-migration-automatic)

元となった「開発用デモデータ集」パッケージ自体、処理はJavaScriptで書かれていますので、ある程度のコードは再利用できたのですが、まあまあ書き直しました(TypeScript)。今回、レコードデータをロードする処理だけでなく、既存の組織内のレコードをCSVにダウンロードする処理も加えています。

※ なお類似の取り組みに [ETCopyData](https://medium.com/@ElToroIT/etcopydata-dd190f4e85f0) というのがあります。ある程度開発をすすめてから気づいたのですが、あまり詳しく中身を見てませんが多分似たようなことをやっています。

## インストール方法

```sh
$ sfdx plugins:install sfdx-migration-automatic
```

未署名のため確認メッセージが出ますが、y (yes) と入力することでインストールできます。

```
$ sfdx plugins
salesforcedx 45.5.0
sfdx-migration-automatic 1.4.2
```

で上記のように表示されればOKです。

## 利用方法

データのダンプ用コマンド `automig:dump` と、ダンプしたデータのロード用コマンド `automig:load` があります。

### データのダンプ

`--objects` (`-o`) オプションで指定したオブジェクトのデータをCSVファイルでダウンロードします。`--outputdir` (`-d`) オプションでCSVファイルを保存するディレクトリを指定します。

```
$ sfdx automig:dump --objects Account,Contact,Opportunity,Task,Event --outputdir ./path/to/dir --targetusername yourname@yourdomain.example.org
```

オブジェクトAPI参照名の後に`:related`をつけると、対象のオブジェクトから抽出するレコードを他のレコードに関連しているものだけに限定できます。

* 取引先の所有ユーザのみをダウンロード対象にする

```
$ sfdx automig:dump --objects Account,User:related -d ./path/to/dir -u yourOrgAlias
```

* 商談に関連したTodoのみをダウンロード対象にする

```
$ sfdx automig:dump --objects Opportunity,Task:related -d ./path/to/dir -u yourOrgAlias
```

抽出したCSVファイルはBOM (Byte Order Mark)付きのUTF-8となります。これでExcelでダブルクリックで開いたときにちゃんと文字コードを認識してくれるのですが、場合によってはあまり好ましくない場合もありますので、その場合は `--excludebom` をオプションで付けるとBOMをつけずに保存してくれます。

### データのロード

`--inputdir` (`-d`)オプションで、ロードされているCSVファイルのディレクトリを指定します。なお、すべてのCSVファイル名は`オブジェクトのAPI参照名`+`.csv`の形式である必要があります。

```
$ sfdx automig:load --inputdir ./path/to/dir --targetusername yourname@yourdomain.example.org
```

移入するデータについて、新規に作成するのではなく、すでにその組織にある既存のレコードを参照させたいケース（例：ユーザ、レコードタイプ）は多いかと思います。その場合は `--mappingobjects`(`-m`) オプションに既存のレコードにマップさせたいオブジェクト名とキーとなる項目名のペアを`:`でつなげて指定することで、既存のレコードに置き換えて参照させることが可能です。
(`--mappingobjects`に指定したオブジェクトのレコードについてもCSVファイルとして対象のディレクトリにおいておく必要があることに注意して下さい）

```
$ sfdx automig:load -d ./path/to/dir -m User:FederationIdentifier,RecordType:DeveloperName -u yourOrgAlias
```

また、`--deletebeforeload` オプションを指定すると、ロード対象になっている該当のオブジェクトのレコードデータを組織内からすべてクリーンアップ（削除）してからロードを開始します。見ての通り**危険**なのでこちらは**かなり注意してご利用下さい**。

```
$ sfdx automig:load -d ./path/to/dir --deletebeforeload -u yourOrgAlias
```

## まとめ

まあそれなりに役に立つと思います。まだロードがうまくいかないときなどのデバッグが難しいので、もう少しそのへんをフレンドリーにしたいところです。なにか問題ありましたら https://github.com/stomita/sfdx-migration-automatic/issues までご報告お願いいたします。

## 余談: Salesforce CLIプラグインの開発について

なお今回Salesforce CLIのプラグイン開発初めてやりましたが、そのまま[JSforce](https://jsforce.github.io)のConnectionが使えてなかなか便利ですね。ただバージョンが少し古いので、自前でConnection作り直すのがいいと思います。

```typescript
import { Connection } from 'jsforce';

// ...

const conn1 = this.org.getConnection(); // `--targetusername` で指定したユーザでの接続が取得できる
await conn1.request('/'); // トークンが失効している場合自動的にリフレッシュされる
const { accessToken, instanceUrl } = conn1;
const conn2 = new Connection({ accessToken, instanceUrl });
// 以下JSforceを利用して様々な処理
const { totalSize, records } = await conn2.query("SELECT Id FROM Account");
// ...
```

## 追記 (2019/3/27 0:36)

ついこの前、オフィシャルブログでこんなのでてましたね。みんなやりたいこと同じなのな。

[Open Sourcing Salesforce Intra Org Data Migrator Tool (Datafall)](https://developer.salesforce.com/blogs/2019/03/open-sourcing-salesforce-intra-org-data-migrator-tool-datafall.html)

中間ファイルをもたせるか持たせないかの違いで、参照依存の解決については似たような感じだと思われます。
ただ、いまさらJavaでこのへんやるのつらい（個人的感想）し、マッピングファイルとか正直できるだけ書きたくないですよね…