Embulkを使ってSalesforceにデータを連携する

# はじめに

Embulkを使ってSalesforceにデータ連携をします。
使用するプラグインは`embulk-output-salesforce`です。

https://github.com/tzmfreedom/embulk-output-salesforce

# 今回書かないこと

- Embulkのセットアップ
- Embulkとは何か？の説明


環境がない方は、公式を参考にインストールしてみてください

https://www.embulk.org/

# どんな定義ファイルになるの？

https://github.com/tzmfreedom/embulk-output-salesforce

in側で、Salesforceの項目のAPI参照名を
out側で対象のオブジェクト・接続情報・アクション(insert, upsert)等を設定する

```yml
in:
  type: file
  path_prefix: /path/to/salesforce_
  parser:
    charset: UTF-8
    newline: CRLF
    type: csv
    delimiter: ','
    quote: '"'
    escape: ''
    skip_header_lines: 1
    comment_line_marker: null
    allow_extra_columns: true
    allow_optional_columns: false
    columns:
    - {name: Name, type: string}
    - {name: Date__c, type: timestamp, format: '%Y-%m-%d'}
    - {name: DateTime__c, type: timestamp, format: '%Y-%m-%d %H:%M:%S%Z'}
    - {name: Number__c, type: double}
    - {name: Reference__c, type: string}
out:
  type: salesforce
  username: hoge@example.com
  password: fuga
  sobject: Account
  action: insert
```

# 今回やること

外部システムのデータを外部IDをキーにして連携させることを想定し以下のパターンをします。

- Account オブジェクトへのupsert
- Contact オブジェクトへのupsert & Accountへの親子関係の生成

# 事前準備

## データ

idはわかりやすいようにテキストにしておきます。

accountsテーブルのIDが、contactsテーブルの中にあってリレーションが結べる状態

```csvs: accounts.csv
id, Name
account_1, 取引先テスト_1
account_2, 取引先テスト_2
account_3, 取引先テスト_3
```

```csvs: contacts.csv
id, account_id LastName, FirstName
contact_1, account_1, テスト姓_1, 名
contact_2, account_1, テスト姓_2, 名
contact_3, account_2, テスト姓_3, 名
```

## 外部ID項目

`ExternalId__c`というAPI参照名でデータ型`テキスト`として外部ID項目を
AccountとContactに作成しました。

![](https://user-images.githubusercontent.com/39574826/208291780-af54bee9-f2d1-4b68-9177-76ebf9e3a1e3.png)

## セキュリティトークンの取得

セキュリティトークンをまだ持ってない場合は、
`個人設定>私のセキュリティトークンのリセット`で取得してください。

設定に存在しない場合は、[公式のヘルプ](https://help.salesforce.com/s/articleView?id=000386179&type=1)を参照してください

![私のセキュリティトークンのリセット _ Salesforce](https://user-images.githubusercontent.com/39574826/208293907-62a1293b-a5a7-438f-93b8-fb4867f0d2a6.png)


# 実装


ディレクトリ構造はこんな感じ

```
.
├── accounts.yml.liquid
├── contacts.yml.liquid
└── csv
    ├── log
    └── source
        └── accounts.csv

```

## embulk-output-salesforceのインストール

```bash
$ embulk gem install embulk-output-salesforce
```

### embulk-output-salesforceの補足

upsertする場合の構成の定義は基本以下の通りで

```yml
out:
  type: salesforce
  username: {{env.SALESFORCE_USERNAME}}
  password: {{env.SALESFORCE_PASSWORD}}{{env.SALESFORCE_TOKEN}}
  sobject: Account
  action: upsert
  upsert_key: ExternalId__c
  result_dir: ./csv/log/
```

`{{env.SALESFORCE_PASSWORD}}{{env.SALESFORCE_TOKEN}}`には、パスワードの末尾にセキュリティトークンを組み合わせたものに置換してください。

パスワード： 1234
セキュリティトークン：abcd
とすると
例) `password: 1234abcd`

|項目|説明|
|---|---|
|username|対象salesforce組織のユーザ名|
|password|対象salesforce組織のユーザパスワード + セキュリティトークンを組み合わせたもの|
|sobject|対象オブジェクト|
|action|insert, upsert, update, delete|
|upsert_key|upsertする場合の外部ID項目|
|result_dir|実行結果のCSV保存先、成功と失敗でそれぞれ出力される|

## Accountオブジェクトにupsertする

### 定義

```yml: accounts.yml.liquid
in:
  type: file
  path_prefix: ./csv/source/accounts.csv
  parser:
    charset: UTF-8
    newline: CRLF
    type: csv
    delimiter: ','
    quote: '"'
    escape: ''
    skip_header_lines: 1
    comment_line_marker: null
    allow_extra_columns: true
    allow_optional_columns: false
    columns:
    - {name: ExternalId__c, type: string}
    - {name: Name, type: string}
out:
  type: salesforce
  username: {{env.SALESFORCE_USERNAME}}
  password: {{env.SALESFORCE_PASSWORD}}{{env.SALESFORCE_TOKEN}}
  sobject: Account
  action: upsert
  upsert_key: ExternalId__c
  result_dir: ./csv/log/
```

### 実行

```bash
$ embulk run accounts.yml.liquid
Next config diff: {"in":{"last_path":"./csv/source/accounts.csv"},"out":{}}
```

実行完了しました。

#### result_dirの確認

`result_dir`の出力を確認します。

エラーなくデータが入ったみたいです。

```csvs: error_20221218075936943.csv
ExternalId__c,Name,Error
```

```csvs: success_20221218075936943.csv
Id,ExternalId__c,Name
0012w00001AyDD5AAN,account_1, 取引先テスト_1
0012w00001AyDD6AAN,account_2, 取引先テスト_2
0012w00001AyDD7AAN,account_3, 取引先テスト_3
```

#### Salesforce上での確認

Salesforce上でも確認できました。

![](https://user-images.githubusercontent.com/39574826/208294831-c424587b-4f67-4184-b157-8b556785ad18.png)

![取引先テスト_1 _ Salesforce](https://user-images.githubusercontent.com/39574826/208294816-81db2470-4669-492c-ad79-560732bd07f6.png)

## Contactオブジェクトにupsertする

ContactオブジェクトとAccountオブジェクトを紐づける際は特殊なので、再度CSVを確認してみる

```csvs: contacts.csv
id, account_id LastName, FirstName
contact_1, account_1, テスト姓_1, 名
contact_2, account_1, テスト姓_2, 名
contact_3, account_2, テスト姓_3, 名
```

account_idが参照関係を結ぶためのキーとなるので
SOQLを書く感覚で以下のようになる

ExternalId__cが`account_1`のAccountとContactを結びつけることになる。

```yml
    columns:
    - {name: ExternalId__c, type: string}
    - {name: Account.ExternalId__c, type: string}
    - {name: LastName, type: string}
    - {name: FirstName, type: string}
```

### 定義

```yml: contacts.yml.liquid
in:
  type: file
  path_prefix: ./csv/source/contacts.csv
  parser:
    charset: UTF-8
    newline: CRLF
    type: csv
    delimiter: ','
    quote: '"'
    escape: ''
    skip_header_lines: 1
    comment_line_marker: null
    allow_extra_columns: true
    allow_optional_columns: false
    columns:
    - {name: ExternalId__c, type: string}
    - {name: Account.ExternalId__c, type: string}
    - {name: LastName, type: string}
    - {name: FirstName, type: string}
out:
  type: salesforce
  username: {{env.SALESFORCE_USERNAME}}
  password: {{env.SALESFORCE_PASSWORD}}{{env.SALESFORCE_TOKEN}}
  sobject: Contact
  action: upsert
  upsert_key: ExternalId__c
  result_dir: ./csv/log/
```

### 実行

```bash
$ embulk run contacts.yml.liquid
Next config diff: {"in":{"last_path":"./csv/source/contacts.csv"},"out":{}}
```

実行完了しました。

#### result_dirの確認

`result_dir`の出力を確認します。

エラーなくデータが入ったみたいです。

```csvs: error_20221218083022091.csv
ExternalId__c,Account.ExternalId__c,LastName,FirstName,Error
```

```csvs: success_20221218083022091.csv
Id,ExternalId__c,Account.ExternalId__c,LastName,FirstName
0032w00000qG2RaAAK,contact_1,, テスト姓_1, 名
0032w00000qG2RbAAK,contact_2,, テスト姓_2, 名
0032w00000qG2TbAAK,contact_3,, テスト姓_3, 名
```

#### Salesforce上での確認

Salesforce上でも確認できました。親子関係も再現できてます！

![取引先テスト_1 _ Salesforce (1)](https://user-images.githubusercontent.com/39574826/208296008-734d441e-afd8-417a-9aba-e0cfdac59dec.png)
![取引先テスト_2 _ Salesforce](https://user-images.githubusercontent.com/39574826/208296002-6552f799-b08f-4369-b796-66dbd1c7e3ac.png)

# さいごに

Salesforceに参照関係を再現した状態でupsertできましたね。
実際に運用するのであれば、AccountとContactの実行順序を定義しやすく管理しやすくできるように、
argoやdigdagと組み合わせるといいですね。
あと、バッチ的な使い道ではなく短い感覚（15分とか10分とか）で実行する場合は個人的には
エラーのCSVを処理して通知する仕組みがあったほうがいいと思います。

# 参考

https://github.com/tzmfreedom/embulk-output-salesforce

https://www.embulk.org/