Salesforceの開発用パラメータの管理方法あれこれ（個社開発・AppExchange対応）

こんにちは、親方です。Qiita初投稿となります。今回のAdvent Calendarを機にQiitaで書いてみます。
(普段書いてるのはてブロです https://hatamoto-t.hateblo.jp/)

## この記事を見てほしい人
* Salesforce開発してる人
    * AppExchange開発している人・興味がある人
    * パラメータ管理のベストプラクティスを知りたい人
    * ユーザに見られたら困るものをSalesforce組織に隠したい人

## はじめに
皆さん、Salesforce環境でどのようなパラメータ管理してますか？
（まさか、ハードコーディングしてないですよね・・・）
パッと思いつくだけでこんなのがあると思います。
* ある隠し機能のアンロックフラグ
* バッチ処理の対象データ取得基準日
* APIのURL・秘密鍵
実現方法いろいろあると思いますが、それぞれメリデメがあるのでしっかり使い分けていきましょう。

### 1.カスタムオブジェクト
データを置くならまずオブジェクトのレコード。ただ安易に選択して良いものでもありません。

|  要素  |  評価  |  備考  |
| :--- | :---: | :--- |
|  アクセス性  |  △  | 数式で使えない (LOOKUPとかやる？) |
|  データ型  |  ◎  | 項目の取れるデータ型なら何でも |
|  メンテナンス性  | 〇 | 標準画面でもApexでもAPIでもOK |
|  組織の制限  |  〇  | データストレージ量による|
|  ガバナ制限  |  ×  | SOQL×1 |
|  秘匿性  |  ×  | 個社開発では権限設定次第で一般ユーザから隠せる<br>組織で最低1名は触れてしまうのでAppExcahngeでは× |


アクセス方法
```Javascript
// SOQLで検索して項目を取ってくる
Param__c params = [select field1__c from Param__c limit 1];
Integer param1 = params.field1__c;
```

### 2.Apexクラスの定数
これもやりがち。Apex開発者としては非常に手軽で身近なやり方ではあります。

|  要素  |  評価  |  備考  |
| :--- | :---: | :--- |
|  アクセス性  |  △  | Apex使い以外には無いも同然 |
|  データ型  |  ◎  | Apex変数の取れるデータ型なら何でも |
|  メンテナンス性  | × | いちいちDeployしないと更新できない<br>管理パッケージで個社別管理× |
|  組織の制限  |  ◎  | Apexが追記できなくなるまで(6MB) |
|  ガバナ制限  |  ◎  | なし |
|  秘匿性  |  ○  | 設定画面を見る権限がないと閲覧できない<br>管理パッケージ内のコードは閲覧保護される|

アクセス方法
```Javascript
// 定数クラス内でpublicとして宣言
public class clsConstant{
    public String param1;
    public Integer param21;
}

// 任意のクラスから呼び出し
String str = clsConstant.param1;
```

### 3.カスタム表示ラベル
これはちょっと裏ワザっぽいです。本来は出力メッセージ用の機能ですが、割と使いようはあります。

|  要素  |  評価  |  備考  |
| :--- | :---: | :--- |
|  アクセス性  |  ◎  | 実は数式に差し込めます |
|  データ型  |  △ | 文字列1000字、呼び出し後にデータ型変換はできる |
|  メンテナンス性  | △ | 管理画面から値を更新<br>管理パッケージで個社別管理× |
|  組織の制限  |  ◎  | 組織全体で5000個 |
|  ガバナ制限  |  ◎  | なし |
|  秘匿性  |  △  | 設定画面を見る権限がないと閲覧できない<br>管理パッケージ内のラベルも見られる|

アクセス方法
```Javascript
// ApexではSystem.Labelから呼び出し
String str = Label.param1;

// 数式では「項目の挿入」で差し込み
$Label.param1
```


### 4.カスタム設定
パラメータ管理のために作られた機能。ただ階層カスタム設定は現在では古いため非推奨。
https://help.salesforce.com/s/articleView?id=sf.cs_about.htm&type=5&language=ja

|  要素  |  評価  |  備考  |
| :--- | :---: | :--- |
|  アクセス性  |  ◎  | 数式に差し込めます |
|  データ型  |  △ | テキストなら255字以下 |
|  メンテナンス性  | ○ | 管理画面から値を更新<br>管理パッケージでは個社で違う値を持てる |
|  組織の制限  |  × | <b>組織全体で5MB</b> |
|  ガバナ制限  |  ◎  | なし（回避可能） |
|  秘匿性  |  ◎  | 設定画面を見る権限がないと閲覧できない<br>「保護」すると管理パッケージ内の設定は秘匿される|

アクセス方法
```Javascript
// ×SOQLでアクセスするとガバナ制限を浪費するので、標準メソッドで呼び出す
// 階層カスタム設定(ユーザ・プロファイル毎に設定し、最適な1件を呼び出し)
HierarchySetting__c settingsOrg = HierarchySetting__c.getOrgdefault();    // 組織のデフォルト設定を呼び出し
String str1 = settingOrg.param1__c;
HierarchySetting__c settingsIns = HierarchySetting__c.getInstance();    // 自分用に設定された設定を呼び出し
String str2 = settingOrg.param2__c;


// 数式で階層カスタム設定にアクセス
$Setup.HierarchySetting__c.param2__c;

// リストカスタム設定(設定レコードを複数登録)
List<ListSetting__c> settingAll = ListSetting__c.getAll();    // 組織の全レコードを呼び出し
ListSetting__c settingOne = ListSetting__c.getInstance('One');    // 任意の1レコードを呼び出し

```

### 5.カスタムメタデータ
パラメータ管理のために作られた機能。後発のためちょっと洗練されています。
https://help.salesforce.com/s/articleView?id=sf.custommetadatatypes_overview.htm&type=5&language=ja

|  要素  |  評価  |  備考  |
| :--- | :---: | :--- |
|  アクセス性  |  ◎  | 数式に差し込めます |
|  データ型  |  ◎ | ロングテキスト可 |
|  メンテナンス性  | ◎ | デプロイ時に初期値を持たせられる<br>管理パッケージではインストール後に個社で違う値を持てる |
|  組織の制限  |  ◎ | 組織全体で1,000万文字、管理パッケージあたり1,000万文字 |
|  ガバナ制限  |  ◎  | なし（回避可能） |
|  秘匿性  |  ◎  | 設定画面を見る権限がないと閲覧できない<br>「保護」すると管理パッケージ内の設定は秘匿される|

アクセス方法
```Javascript
// SOQLを使ってもSOQL Queriesに計上されない(Query Rowsには形状される？) 
CustomMetadata__mdt metaData1 = [select Id, Param1__c from CustomMetadata__mdt where DeveloperName = 'mydata'];
String str1 = metaData1.Param1__c;
// カスタム設定のように呼び出すこともできる
String str2 = CustomMetadata__mdt.mydata.Param1__c;

// 数式に差し込み
$CustomMetadata.CustomMetadata__mdt.mydata.Param1__c;
```

#### 補足：カスタム設定とカスタムメタデータの違い

* 共通点
    * 「保護」設定すると管理パッケージ内のリソースが秘匿される
        * 登録者の代理ログインを行うと可視化される
        * カスタムメタデータは2GPの分割パッケージに対応(名前空間縛り/パッケージ縛りを選択可能)
    * 数式に差し込みできる
    * Apexで呼び出す際にガバナ制限を回避できる
* 違い
    * カスタム設定は組織キャッシュ
        * デプロイ・インストール時にレコードは含まれない
        * 組織全体での総容量が少ない(5MB)
        * 専用メソッドでアクセスする
        * 最初からキャッシュ化している
    * カスタムメタデータはメタデータ扱い
        * デプロイ・インストール時にレコードを含む
        * 組織全体で大容量を保持できる
        * SOQL・専用メソッドでアクセスする
        * 一度呼び出すとキャッシュ化する
        * 入力規則・ページレイアウトを定義できる

### 6.機能パラメータ（FMA）
※管理パッケージのうち、AppExchange製品のみが使える機能
License Management Application(LMA)経由で導入個社別にリモート操作できる優れモノです
テキストは格納できないので、フラグやタイムスタンプとして使います。
https://developer.salesforce.com/docs/atlas.ja-jp.packagingGuide.meta/packagingGuide/fma_manage_features.htm

|  要素  |  評価  |  備考  |
| :--- | :---: | :--- |
|  アクセス性  |  ○  | Apex経由でどこからでも参照可能 |
|  データ型  |  △ | テキスト不可(Boolean、Date, Integer) |
|  メンテナンス性  | ◎ | （管理パッケージ限定）<br><b>代理ログイン不要で個社別データ操作できる</b> |
|  組織の制限  |  ◎ | パッケージあたり200項目 |
|  ガバナ制限  |  ◎  | なし（回避可能） |
|  秘匿性  |  ◎  | 設定画面を見る権限がないと閲覧できない<br>「保護」すると管理パッケージ内の設定は秘匿される|

アクセス方法
```Javascript
// 専用メソッドで自組織用パラメータにアクセス
Boolean bool1 =System.FeatureManagement.checkPackageBooleanValue('Boolean_L2S');
Date date1 = System.FeatureManagement.checkPackageDateValue('Date_L2S');
Integer int1 Integer System.FeatureManagement.checkPackageIntegerValue('Integer_L2S');

// 逆に値を返すこともできるパラメータもある
System.FeatureManagement.setPackageBooleanValue('Boolean_S2L', true);
System.FeatureManagement.setPackageDateValue('Date_S2L', Date.today());
System.FeatureManagement.setPackageIntegerValue('Integer_S2L', 100);
```

#### 補足：機能パラメータのメンテナンス方法
FMAで送受信している値は各顧客の「Feature Parameter」の子レコードとして管理されています。（データ型毎に3種類）
このレコードの値を操作すると顧客組織に同期されます。
組織から返された値もここに返るので、レポート集計も可能です。

![FMA関連オブジェクトE-R図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2963304/8353bff3-c230-b0e1-a865-228c3a28d6dc.png)

## 結論、どの機能を使えば良いのか？
用途別でベストプラクティスを考えてみましょう。

| 方針 |  個社開発 |  管理パッケージ(AppExchange)  | 
| :--- | :--- | :--- | 
|  拡張性重視  |  カスタムオブジェクト<br>カスタムメタデータ | カスタムメタデータ |
|  メンテナンス性重視  |  カスタム表示ラベル<br>カスタムメタデータ  | 機能パラメータ |
|  秘匿性重視 |  カスタムメタデータ  | カスタム設定<br>カスタムメタデータ<br>Apex定数 |

個社開発では運用方針を厳密に定義できるため、カスタムオブジェクトやカスタム表示ラベルといったカスタマイズが容易な機能を使うリスクは低いです。
とはいえ、今から実装するならカスタムオブジェクト件数やディスク使用量に抵触しないカスタムメタデータを活用していきましょう。

逆にAppExchangeにおいては、顧客に想定外の運用をされるリスクをなるべく避けなければいけません。
顧客に閲覧・改竄される可能性のあるカスタムオブジェクトでのパラメータ管理を避け、顧客の操作から保護できるカスタムメタデータを使うべきです。その意味ではApexクラスにパラメータを隠すのも悪くありません。
また、個社別のパラメータ操作には基本的に代理ログイン操作が必要となります。最初は良いもののビジネスが拡大すると対象顧客が増えて手厚い個社対応は難しくなります。
更新が必要な値は機能パラメータ化し、個社ごとにログイン操作が必要な運用をなるべく減らしましょう。