Salesforce開発入門 Vol.1 ～テーブル設計編～

こんにちは！
LIFULLエンジニアの吉永です。

私は普段の業務では主にバックエンドをGo言語で開発し、時々フロントエンドをNuxt.jsで開発しています。
そんな私ですが、10月から参画した新しいプロジェクトではSalesforce Service Cloud（以降SFSCと略称します）を用いた開発を一部担当することになり、10年以上ソフトウェアエンジニアやってますがSalesforce関連の開発に携わるのは初めてなので、業務を進める上でインプットしたことをアウトプットして知識を定着させたいと思います。

今回はタイトルにあるように、Salesforceを使ってRDBのようなテーブル設計からSalesforceで実際にそのテーブルを作成してテストクエリを発行するところまでを紹介したいと思います。

## SFSCにおけるテーブル設計を行う前に知っておいた方が良いこと

まず前提としてSFSCをRDBのようにとらえてテーブルを設計するとはまります。
理由は様々あるのですが、RDBではできていたことができない（その逆も然りですが）などがSFSCでは往々にしてあります。
例えば複合主キーはSFSCでは疑似的にしか実現できません。

そして、とても重要なことですが、SFSCでは`テーブル`という言葉を意図的に使っておらず、`オブジェクト`と呼称します。※今回は分かりやすく序盤はテーブルという表記で解説していきます。何故オブジェクトなのかはおいおい分かっていきます。

更にSFSCではSQLライクな問合せ言語である[SOQL（Salesforce Object Query Language）](https://developer.salesforce.com/docs/atlas.ja-jp.soql_sosl.meta/soql_sosl/sforce_api_calls_soql.htm)という言語を使うのですが、SOQLには<font color="Red">JOIN句がありません</font>。
RDBに慣れた人にとっては「JOINがないってことは結合できない？ってことは正規化は第一正規形までしかしない？」と心配になると思いますが、結論としては結合はできますし、正規化もできれば一般的にRDBでも行う、第三正規形まで行った方が良いと思います。

## 今回のお題にするテーブル設計

今回はDB系の教科書などでもよく出てくる商品、注文、注文明細の3つのテーブルを例にして解説していきます。

ER図
![drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690018/5c759433-9639-1347-98b8-c2be2fa4d39a.png)
※黒丸は主キーを表現してます。

## SFSCでのテーブル設計の考え方

SFSCでは先述したようにJOIN句がありません。
ではどのようにして複数テーブルを結合するのか？ですが、テーブル同士の親子関係にて実現します。
親子関係というと、オブジェクト指向プログラミングに馴染みのある方が真っ先に思い浮かべるのは継承関係（is-a関係）だと思いますが、継承ではなくぱっとわかりやすい表現だと、包含関係（has-a関係）の方があてはまっているように思います。
※正確には違うと思いますが、ここではSFSCにおけるテーブル間の関係性のイメージを持ってもらいたいのであえて包含関係を用いさせてもらってます。

SFSCではテーブル間に各々を参照する為の設定をすることで結合を実現しますが、結合する際に親から子、子から親で結合する場合で手法や考え方が若干変わります。

まず、SFSCでの親子関係を構築すると、上記ER図では注文明細が子、商品と注文が親という関係になります。

### 子から親

Go言語のコードで表すと下記のようなコードの構造体で表現できると思います。

```go
// 商品
type Item struct {
	Id    string // 商品ID
	Name  string // 商品名
	Price int    // 単価
}

// 注文
type Order struct {
	Id   string // 注文ID
	Date string // 注文日時
}

// 注文明細
type OrderDetail struct {
	ItemRef  *Item  // 商品オブジェクトへの参照
	OrderRef *Order // 注文オブジェクトへの参照
	Count    int    // 注文個数
}
```

注文明細オブジェクトであるOrderDetailのメンバ変数として商品オブジェクトと注文オブジェクトが存在している状態です。
注文明細オブジェクトを得る為のSOQLは下記のようになります。

```sql
SELECT Item__r.Id, Order__r.Id, Count FROM OrderDetail__c
```

`Item__r`は参照するオブジェクトの名称で、ドット記法にて参照先のオブジェクトのメンバ変数をSELECTで指定することができます。

### 親から子

続いて親から子を参照する場合のコードイメージです。

```go
// 商品
type Item struct {
	Id             string        // 商品ID
	Name           string        // 商品名
	Price          int           // 単価
	OrderDetailRef []OrderDetail // 注文明細
}

// 注文
type Order struct {
	Id             string        // 注文ID
	Date           string        // 注文日時
	OrderDetailRef []OrderDetail // 注文明細
}

// 注文明細
type OrderDetail struct {
	ItemRef  *Item  // 商品オブジェクトへの参照
	OrderRef *Order // 注文オブジェクトへの参照
	Count    int    // 注文個数
}
```

商品、注文オブジェクトのメンバ変数に配列型で注文明細オブジェクトがいるような形になります。
上記の注文オブジェクトを求めるSOQLは下記になります。

```sql
SELECT Id, Date, (SELECT Item__r.Id, Count FROM OrderDetail__r) FROM Order__c
```

親から子の参照はサブクエリにて実現します。
サブクエリ部分のフィールドはJSONの配列形式で返却されます。

まとめると、SFSCにおけるテーブル設計はオブジェクト指向プログラミングにおけるオブジェクト設計に近い手法の方がしっくりくるということがお分かりいただけたのではないかと思います。
序盤で紹介しましたが、SFSCでは`テーブル`とは呼称せず、`オブジェクト`と呼称しているというのはこのような理由なのではないかなと個人的に思いました。

## 実際にSFSCでカスタムオブジェクトを作成してテストクエリを発行してみるまでの流れ

### 検証用のSFSC環境を準備

まずは検証用のSFSC環境を準備しましょう。
https://developer.salesforce.com/signup
にアクセスして開発者用の無料アカウントを取得します。
アカウント作成後、諸々の案内が届きますので、その流れに従ってSFSCにログインできるところまで進めます。

### カスタムオブジェクト作成

![step1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690018/ffc41837-bdb0-118f-5309-1e3d44949ebb.png)
まずはログインした後のホーム画面から上図赤枠の「オブジェクトマネージャ」をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690018/6742aabe-74eb-b2ad-fe08-56dddbd11d5e.png)
続いて、オブジェクト一覧画面から「作成」ボタンを押し、表示されるドロワーメニューから「カスタムオブジェクト」を選択します。

※ちなみに今回作成するオブジェクトは標準オブジェクトとしてSFSCに存在していますので、そういう場合はわざわざカスタムオブジェクトを作成することはしないのですが、手っ取り早くハンズオンを通じて理解を深める為とご理解ください。

#### 商品オブジェクト

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690018/96d726ce-0888-e072-d1fa-525a5383f4b1.png)
上図赤枠内を下記表の内容で入力し、保存を押します。

| 表示ラベル  | オブジェクト名  | 説明  | レコード名 | データ型 |
|---|---|---|---|---|
| MY商品 | MyItem | 商品管理オブジェクト  | 商品ID | テキスト |

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690018/555a973d-2877-e6e0-ffad-02ca9cd111f1.png)
保存を押すと上図のような画面になるので、「項目とリレーション」を押します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690018/9b09bd22-f87b-0f3c-7c06-3db3611540a9.png)
「新規」を押します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690018/c50e661e-3455-4e36-655f-b708f76b4e2a.png)
データ型からテキストを選択して、「次へ」を押します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690018/04081855-7600-ae67-b8c4-69a3e76b3c9f.png)
上図赤枠内を下記表の内容で入力し、「次へ」を押します。

| 項目の表示ラベル  | 文字数  | 項目名  | 
|---|---|---|
| 商品名 | 255 | ItemName |

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690018/b1527f0e-9fb6-a083-1fc6-5358c141a888.png)
項目レベルとセキュリティの設計画面はひとまずデフォルトのまま、「次へ」を押します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690018/72146900-9713-34ff-9319-ed396acd6566.png)
ページレイアウトの追加もひとまずデフォルトのまま、「保存」を押します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690018/5147cd41-70a1-c48c-b0de-0212679fa8d4.png)
上図のようになっていれば項目の追加完了です。

続けて、「新規」ボタンを押します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690018/38acbf9d-d95f-64cb-4a79-735512033e93.png)
今度はデータ型から通貨を選択して、「次へ」を押します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690018/e89eaaaa-fd7c-a9f8-ed2a-e8bc231489b9.png)
上図赤枠内を下記表の内容で入力し、「次へ」を押します。

| 項目の表示ラベル  | 項目名  | 
|---|---|
| 価格 | Price |

項目レベルセキュリティの設定、ページレイアウトへの追加はデフォルトのまま保存します。

最終的にこんな感じになっていれば良いです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690018/694e7ca4-855a-17f7-bc8a-b04ea822b7f2.png)

#### 注文オブジェクト

続いて注文オブジェクトも下記の表と同じ状態になるように商品オブジェクト手順を参考にしながら追加します。

カスタムオブジェクト作成時の設定

| 表示ラベル  | オブジェクト名  | 説明  | レコード名 | データ型 |
|---|---|---|---|---|
| MY注文 | MyOrder | 注文管理オブジェクト  | 注文ID | テキスト |

注文オブジェクトはER図では注文IDと注文日時情報をフィールドとして保持していましたが、注文IDはカスタムオブジェクト作成時に自動で作成されるName項目で代替し、注文日時は業務要件にもよるかと思うのですが、今回は注文オブジェクトにレコードが作成された日を注文日時とすることで行きたいと思いますので、項目は追加しなくても大丈夫です。
ちなみにSFSCでは[システム項目](https://developer.salesforce.com/docs/atlas.ja-jp.api.meta/api/system_fields.htm)というオブジェクトに標準で付与される参照専用の項目がありますので、注文日時はこの`CreatedDate`を用います。

#### 注文明細オブジェクト

続いて、注文明細オブジェクトを作ります。

まず注文オブジェクトですが、冒頭のER図通りに実装すると、注文IDと商品IDの複合主キーになっていますが、SFSCでは複合主キーは対応しておらず、実現する場合二つのフィールドを合体させたユニークなフィールドを作成し、疑似的に実現する必要があります。
今回はシンプルな構成になるように、明細オブジェクト自体に主キーを別途持たせることで以降の操作を行っていきます。
よって今回の動作検証用オブジェクト作成操作手順では、注文IDと商品IDが同じレコードも登録できてしまうので、ご容赦くださいませ。

カスタムオブジェクト作成時の設定

| 表示ラベル  | オブジェクト名  | 説明  | レコード名 | データ型 |
|---|---|---|---|---|
| MY注文明細 | MyOrderDetail | 注文明細オブジェクト  | 注文明細ID | テキスト |

上記表のカスタムオブジェクトを作成したら、項目とリレーションから項目を新規追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690018/1b81e4ee-7797-42a5-326d-3d024f2b85aa.png)
データ型から
参照関係を選択して、「次へ」を押します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690018/889da9a1-2df9-3c65-ab77-b2db6b655eef.png)
関連先からMY注文を選択して、「次へ」を押します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690018/f9885cca-b64f-1004-9627-564993586354.png)
上図の赤枠内を下記表に従って入力し、「次へ」を押します。

| 項目の表示ラベル  | 項目名 | 子リレーション名 | 必須項目 |
|---|---|---|---|
| MY注文 | MyOrder | MyOrderDetails | 値の入力を必須にする |

参照項目に対する項目レベルセキュリティの設定、ページレイアウトへの参照項目の追加、カスタム関連リストの追加はデフォルトのまま、保存します。

同じ要領で、MY商品とも参照関係を下記表に従って作成します。

| 項目の表示ラベル  | 項目名 | 子リレーション名 | 必須項目 |
|---|---|---|---|
| MY商品 | MyItem | MyOrderDetails | 値の入力を必須にする |

続けて、注文個数を下記表の内容で、データ型数値で作成します。

| 項目の表示ラベル | 項目名  | 
|---|---|
| 注文個数 | Count |

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690018/917e078f-01d6-dd93-ec8c-c32516492f9b.png)
最終的に上図のような項目になっていれば大丈夫です。

### カスタムオブジェクトにテストデータ登録

自分で定義したカスタムオブジェクトにテストデータを登録するにはまずホーム画面やランチャーの中にカスタムオブジェクトが表示されるように設定する必要があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690018/0849a9fd-949d-3989-c9af-e1536df351e5.png)
ホーム画面のクイック検索窓にタブと入力して、検索結果の中からタブを選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690018/31f23f85-f9a3-0357-f4ad-582121704c8f.png)
カスタムオブジェクトタブの新規を押します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690018/1e1a4e4f-a46e-e860-a3b3-b4bae94abf16.png)
オブジェクトから自分で作成したカスタムオブジェクトを選択し、タブスタイルは任意の物を選択します。
プロファイルに追加とカスタムアプリケーションに追加はデフォルトのままで、保存します。

同じ要領で、作成したカスタムオブジェクトを全てカスタムオブジェクトをタブに登録します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690018/6fa69adc-aa83-7d59-e7dc-040e3f45e83a.png)
登録が終わったらアプリケーションランチャーからサービスコンソールを選択します。

#### 商品

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690018/4c2dd222-cfd1-b845-ec09-e4d5ab983a62.png)
サービスコンソールからMY商品を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690018/154f951f-dfb3-ef91-5273-d8d0464c1a51.png)
新規ボタンを押します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690018/06999901-f9f6-e54e-8cff-32c80f249ea7.png)
上図のような形でテスト用の商品データを入力し、保存を押します。

同じ要領で下記表のデータも一緒に登録してください。

| 商品ID  | 商品名 | 価格 |
|---|---|---|
| ITEM0002 | ばなな | 150 |
| ITEM0003 | いちご | 498 |

#### 注文

続けて注文データも登録します。
下記表の内容を登録してください。

| 注文ID |
|---|
| Order0001 |
| Order0002 |

#### 注文明細

続けて注文明細です。
下記表の内容を登録してください。

| 注文明細ID  | MY注文 | MY商品 | 注文個数 |
|---|---|---|---|
| OrderDetail0001 | Order0001 | ITEM0001 | 1 |
| OrderDetail0002 | Order0002 | ITEM0002 | 1 |
| OrderDetail0003 | Order0002 | ITEM0003 | 2 |

### SOQLでテストクエリを発行して動作確認

続いて開発者コンソールを使ってテストデータを参照するテストクエリをSOQLで発行してみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690018/4d198b4c-207d-d03c-51a9-7efc179416ef.png)
画面右上のギアアイコンをクリックし、ドロワーメニューの「開発者コンソール」を押します。

すると下図のようなウィンドウが表示されるので、Query Editorタブに切り替えます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690018/0644603d-0b3e-4d01-b2ac-d6eb4b9c5b13.png)
上図の赤枠内にテスト用のクエリを記述していきます。

#### 商品一覧を表示してみる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690018/32fd0d46-c538-1770-a9fe-684fb0b48b1a.png)
クエリ入力欄に下記のクエリを入力して、Executeを押します。

```sql
SELECT Name, ItemName__c, Price__c FROM MyItem__c
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690018/840396f3-28bf-bbcd-358b-94b95a2c1f83.png)
テストデータが想定通りうまく登録されていれば、上図と同じような結果が表示されます。

結果を昇順、降順でソートしたい場合はSQL同様にORDER BY句が使えますので、例えばNameの降順にする場合は下記クエリで実現可能です。

```sql
SELECT Name, ItemName__c, Price__c FROM MyItem__c ORDER BY Name DESC
```

#### 注文明細一覧と商品を表示してみる

注文明細とそれに紐づく商品の情報を一緒に表示する下記クエリを実行してみます。

```sql
SELECT Name, MyOrder__r.Name, MyItem__r.Name, MyItem__r.ItemName__c, MyItem__r.Price__c, Count__c FROM MyOrderDetail__c
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690018/cad02fa1-b038-ac6f-41c8-b9397f79cb33.png)
すると上図のような出力を確認できます。
上記クエリは冒頭で紹介した、子から親への参照を行う際のSOQL例です。
ポイントとしては、参照関係を作る際に入力した項目名の末尾を`r`に変えることで、あとはドット記法で親オブジェクトの不フィールドを参照することができます。

#### 明細込みの注文一覧を表示してみる

最後に明細情報を含んだ注文一覧を表示してみます。

```sql
SELECT Name, (SELECT Name, MyItem__r.ItemName__c, Count__c FROM MyOrderDetails__r) FROM MyOrder__c ORDER BY Name
```

冒頭で、親から子を参照する際はサブクエリを使うと紹介しましたが、上記クエリで親（注文）から子（注文明細）を参照する例が示されています。
ポイントとしては、親から子を参照する場合、子オブジェクト側で参照関係を作成する際に入力した子リレーション名をサブクエリのFROM句に指定するところです。

実行すると結果は下図のようになっていると思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690018/3412f7d1-bac7-6d80-342b-3b758e9417ad.png)
`MyOrderDetails__r`カラムが子オブジェクトのサブクエリの結果で、JSONの配列で格納されていることが分かります。
開発者コンソールでは`"MyItem__r":"[object Object]"`と商品オブジェクトへの参照が展開されずに表示されていませんが、REST APIやApexでSOQLを実行して戻り値を見ると、上記のクエリでは下記のような結果が得られます。

```json
"MyItem__r" : {
  "attributes" : {
    "type" : "MyItem__c",
    "url" : "/services/data/v53.0/sobjects/MyItem__c/a095h000006BSKzAAO"
  },
  "Name" : "ITEM0002",
  "ItemName__c" : "ばなな",
  "Price__c" : 150.0
}
```

## まとめ

SFSCに入門するにあたり、まずは基本となるSFSCでのオブジェクト作成手順、テストクエリ発行までを一連の流れで確認していきました。
RDBに慣れている人にとっては色々と躓く点があり、RDBで言うと・・・というようにベースにRDBを置いて思考しない方がむしろ良いのでは？と思いました。
オブジェクト指向プログラミングに慣れている人にとってはSFSCにおけるオブジェクトの概念がすっと入ってくると思うので、SFSCではオブジェクト指向ベースで考えた方が良さそうです。

いずれにしても新しいことを始める際にはまずその環境に慣れることが先決だと思うので、SFSCのテスト環境を色々触ってみて動作確認をしながら自分なりに理解を深めていくと良いのではないかと思います。
SFSCでは[TrailHead](https://trailhead.salesforce.com/ja/)と呼ばれるオンライン学習コンテンツも用意されており、ハンズオン形式で楽しくSFSCの基礎を学べますので、まずはこちらで基本操作に慣れてから、応用的な知識を身に付けていくと良いかと思いますので、また実務を進めながら躓いた点をアウトプットしながら整理、世の中のSalesforce開発入門者に役立つような記事を執筆できればなと思います。

最後までご覧いただき、ありがとうございました。
それではまた次の記事でお会いしましょう。