Salesforce（他システムとの連携メモ）

Salesforceと他システムがリアルタイムでSOAP／REST APIの連携をする際の設定方法など、勉強のためまとめてみました。

##  :pushpin: Salesforce → リモートシステム（＝外部システム） 
Salesforceが、リモートシステム（＝外部システム）のSOAP/REST APIをCallOutする場合
Ex.　Salesforceが注文のキーとなる発注番号を渡し、外部システムが注文の詳細情報を返却するケース
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/450298/3b371896-ea7d-390c-7272-9a601e6ad9e6.png)

##### REST APIの場合（Httpコールアウト）
・Salesforce側にリモートサイトの登録が必須（通信可能なURLの登録）
・ApexClassを作成する。（HttpRequestを使い、認証＋CallOutを実施。）

##### SOAP APIの場合
・Salesforce側にリモートサイトの登録が必須（通信可能なURLの登録）
・リモートシステムからWSDLをもらい、インポートすることによりコールアウトするためのApexClassを自動生成。
・認証は上記ApexClassのメソッドを使い、セッションIDを取得する。

##### リモートサイトの登録
https://trailhead.salesforce.com/ja/content/learn/modules/apex_integration_services/apex_integration_callouts#apex_integration_callouts_authorizing

## :pushpin: Salesforce ← リモートシステム（＝外部システム）
リモートシステム（＝外部システム）が、SalesforceのSOAP/REST APIをCallする場合
Ex.　外部システムが顧客のキーとなる顧客番号を渡し、Salesforceが顧客の詳細情報を返却するケース
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/450298/ed72f355-0379-ecbf-9895-81ef70fb2d78.png)

##### REST APIの場合
・リモートシステムでSalesforceのRESTAPIをCallするClassを作成。
・認証は、Salesforceの接続アプリケーションで管理。（OAuth）

##### SOAP APIの場合
・SalesforceからWSDLを取得し、対向となるリモートシステムに渡す。
・認証は、対向システムはWSDLをインポートすることにより生成されるClassのメソッドを使い、セッションIDを取得。
・組織やプロファイルなどでリモートシステムのIPが登録されてない場合、PWに加えて、セキュリティトークンも渡すこと


## WSDL:page_facing_up:種類（SOAP APIの場合）
SOAP APIは、APIをCallされる側がWSDLを生成して対向システム側で読み込むことをするが、
WSDLに関してSalesforceの場合以下2方式がある。
　・Enterprise　→単一組織で利用するケースに最適
　・Partner　→　多数組織で利用するケースに最適
※SOAP APIを選択した場合、組織の設定を変えたりするたびにWSDLの再読み込みが必要になる。
※REST APIはWSDLが不要。

## :santa_tone3: 実機でSalesforceからのCallOutを試す（REST)
https://trailhead.salesforce.com/ja/content/learn/modules/apex_integration_services/apex_integration_rest_callouts

以下エンドポイントに対してRESTAPIでApexCallOutを行い、
majestic badgerなどの値を取得する。
https://th-apex-http-callout.herokuapp.com/animals?_ga=2.17284665.1052735684.1664665841-1174435908.1664505734
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/450298/e4b81a1b-e971-87a3-f81a-0647417e2ed2.png)


（１）リモートサイトへの登録
<table>
  <tr>
    <td> リモートサイト名 </td> <td>animals_http</td>
  </tr>
  <tr>
    <td> リモートサイトの URL </td> <td>https://th-apex-http-callout.herokuapp.com</td>
  </tr>
</table>

▼Salesforceの設定画面キャプチャ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/450298/0c3806fe-18b6-71cb-de41-c045350cc40c.png)



（２）開発者コンソールの「Open Execute Anonymous Window (実行匿名ウィンドウを開く)」を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/450298/8307e13d-f65b-cd4b-2440-aca64a5ff330.png)

（３）以下をCopy／ペースト、OpenLogのOptionにチェックの上、Execute（CallOutを実行）

```
Http http = new Http();
HttpRequest request = new HttpRequest();
request.setEndpoint('https://th-apex-http-callout.herokuapp.com/animals');
request.setMethod('GET'); 
HttpResponse response = http.send(request);
// If the request is successful, parse the JSON response.
if(response.getStatusCode() == 200) {
    // Deserialize the JSON string into collections of primitive data types.
    Map<String, Object> results = (Map<String, Object>) JSON.deserializeUntyped(response.getBody());
    // Cast the values in the 'animals' key as a list
    List<Object> animals = (List<Object>) results.get('animals');
    System.debug('Received the following animals:');
    for(Object animal: animals) {
        System.debug(animal);
    }
}
```


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/450298/6c98dee8-00c8-e1b7-54fb-cbe07c92f9df.png)
　
（４）返却結果が返ってくるので、Debug Onlyを選択し、確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/450298/5d8f4d83-7cd3-fb94-a276-8a7db69a3662.png)


## :santa_tone3: 実機でSalesforceからのCallOutを試す（SOAP)
https://trailhead.salesforce.com/ja/content/learn/modules/apex_integration_services/apex_integration_rest_callouts

以下エンドポイントに対して数字を渡し（1と2）、SOAPAPIでApexCallOutを行い、
加算した結果（3）を取得する。
https://th-apex-http-callout.herokuapp.com/animals?_ga=2.17284665.1052735684.1664665841-1174435908.1664505734
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/450298/e4b81a1b-e971-87a3-f81a-0647417e2ed2.png)


（１）リモートサイトへの登録
<table>
  <tr>
    <td> リモートサイト名 </td> <td>animals_soap</td>
  </tr>
  <tr>
    <td> リモートサイトの URL </td> <td>https://th-apex-soap-service.herokuapp.com</td>
  </tr>
</table>

（２）WSDLをXML形式で取得、保存
https://trailhead.salesforce.com/ja/content/learn/modules/apex_integration_services/apex_integration_soap_callouts

https://th-apex-soap-service.herokuapp.com/assets/calculator.xml?_ga=2.4683187.1052735684.1664665841-1174435908.1664505734
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/450298/09d32116-051c-e711-741c-436540b0e15c.png)

（３）Salesforceに上記XMLをインポートし、ApexClassを生成
WSDLを解析し、Apexコードの生成をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/450298/f910a833-fb2c-9102-fcf8-0acd65537b09.png)

calculatorServices クラスは、同期コールアウトに使用されます。AsyncCalculatorServices クラスは、非同期コールアウトに使用されます。

（４）開発者コンソールの「」から、OpenLogを選択し、Execute
```
calculatorServices.CalculatorImplPort calculator = new  calculatorServices.CalculatorImplPort();
Double x = 1.0;
Double y = 2.0;
Double result = calculator.doAdd(x,y);
System.debug(result);
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/450298/c154df39-ea2e-7e9c-8854-09b60a4eb7a6.png)

（５）Debug Onlyを選択すると、3.0と表示（1.0と2.0の加算結果）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/450298/4d9f4039-f00d-f968-57f3-b0bfe5ff224d.png)





## :santa_tone3: 実機でリモートシステムからSalesforceのAPIをCall
https://trailhead.salesforce.com/ja/content/learn/modules/apex_integration_services/apex_integration_webservices

リモートからSalesforceの標準APIをCallするのではなく、Salesforceで作ったカスタムのWebサービスを呼び出すことを想定。

（参考）Webサービスのソースコードの構成
```
@RestResource(urlMapping='/Account/*')
global with sharing class MyRestResource {
    @HttpGet
    global static Account getRecord() {
        // Add your code
    }
}
```
global ・・・メソッドをグローバルで定義
@HttpGet アノテーション・・・GET 要求で呼び出される
getRecord・・・カスタムRESTAPIコール

※REST エンドポイントは https://yourInstance.my.salesforce.com/services/apexrest/ です。

（１）以下、Apexクラスの作成
```
@RestResource(urlMapping='/Cases/*')
global with sharing class CaseManager {
    @HttpGet
    global static Case getCaseById() {
        RestRequest request = RestContext.request;
        // grab the caseId from the end of the URL
        String caseId = request.requestURI.substring(
          request.requestURI.lastIndexOf('/')+1);
        Case result =  [SELECT CaseNumber,Subject,Status,Origin,Priority
                        FROM Case
                        WHERE Id = :caseId];
        return result;
    }
    @HttpPost
    global static ID createCase(String subject, String status,
        String origin, String priority) {
        Case thisCase = new Case(
            Subject=subject,
            Status=status,
            Origin=origin,
            Priority=priority);
        insert thisCase;
        return thisCase.Id;
    }   
    @HttpDelete
    global static void deleteCase() {
        RestRequest request = RestContext.request;
        String caseId = request.requestURI.substring(
            request.requestURI.lastIndexOf('/')+1);
        Case thisCase = [SELECT Id FROM Case WHERE Id = :caseId];
        delete thisCase;
    }     
    @HttpPut
    global static ID upsertCase(String subject, String status,
        String origin, String priority, String id) {
        Case thisCase = new Case(
                Id=id,
                Subject=subject,
                Status=status,
                Origin=origin,
                Priority=priority);
        // Match case by Id, if present.
        // Otherwise, create new case.
        upsert thisCase;
        // Return the case ID.
        return thisCase.Id;
    }
    @HttpPatch
    global static ID updateCaseFields() {
        RestRequest request = RestContext.request;
        String caseId = request.requestURI.substring(
            request.requestURI.lastIndexOf('/')+1);
        Case thisCase = [SELECT Id FROM Case WHERE Id = :caseId];
        // Deserialize the JSON string into name-value pairs
        Map<String, Object> params = (Map<String, Object>)JSON.deserializeUntyped(request.requestbody.tostring());
        // Iterate through each parameter field and value
        for(String fieldName : params.keySet()) {
            // Set the field and value on the Case sObject
            thisCase.put(fieldName, params.get(fieldName));
        }
        update thisCase;
        return thisCase.Id;
    }    
}
```
（２）WorkbenchからSalesforceのWebサービスをCallするため、ログイン
https://workbench.developerforce.com/login.php?_ga=2.47013159.1052735684.1664665841-1174435908.1664505734
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/450298/2b61dab5-1a6d-a954-a2e6-98f11c811b94.png)

（３）REST Explorerを選択し、POST、エンドポイントの指定、Requestを書き換え、Execute
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/450298/41f73555-b117-ccd9-0d4e-235b7c31064d.png)


（４）ケースがCreate（POST）されている！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/450298/a405bbcb-30b4-25bc-141e-f46b0b9c2a4c.png)

（参考）明示的に接続アプリケーションの登録は行わなかったが（許可のみ）、自動でWorkbenchの接続アプリケーションがインストールされている。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/450298/92d428b3-72cd-a7c3-b434-5e7fa3176b5c.png)




## :santa_tone3: おまけ
・SOAPとREST、どっちがいい？
https://trailhead.salesforce.com/ja/content/learn/modules/apex_integration_services/apex_integration_callouts#apex_integration_callouts_authorizing
＞「どちらを使用すべきか?」と疑問に思っている方は、できる限り HTTP サービスを使用してください。一般的に、これらのサービスの方が操作が簡単で、必要なコードが大幅に少なく、読みやすい JSON を利用できます。ここ数年間で「クールな若者」はみな REST サービスに切り替えていますが、SOAP Web サービスが悪いというわけではありません。SOAP Web サービスには (インターネットイヤーでは) 非常に長い歴史があり、主にエンタープライズアプリケーションで使用されています。これらがすぐになくなるということはありません。SOAP は、主に従来のアプリケーションと統合する場合や、正規の交換形式またはステートフル操作を必要とするトランザクションで使用することになるでしょう。このモジュールでは SOAP についても軽く触れますが、大半を REST についての説明に費やします。
ByTrailhead 2022/10/8時点

・数千件以上の大量データはBulkAPI