外部Webサービスへのコールアウト

# 外部Webサービスへのコールアウト
Apex RestAPI コールアウト を使用して外部Webサービスと通信を行います。
例として、[Google Geocoding API](https://developers.google.com/maps/documentation/geocoding/start)を使用して、住所から地理位置情報(緯度経度)を取得します。

## リモートサイト設定
Apex HTTP コールアウトを利用するためには、エンドポイントをリモートサイトの設定に登録する必要があります。
[設定] - [セキュリティ] - [リモートサイトの設定][^1]から新規リモートサイトボタンをクリックし、URLを設定します。

![HTTP_CALLOUT_001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125621/2361f86a-37d1-48a1-f3a7-d690323aca64.png)

### 参照
* [Apex を使用したコールアウトの呼び出し | Apex開発者ガイド](https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/apex_callouts.htm)
* [Apex インテグレーションの概要 | Trailhead](https://trailhead.salesforce.com/ja/content/learn/modules/apex_integration_services/apex_integration_callouts)

## Apexクラスの作成
コールアウトにはHttp、HttpRequest、HttpResponseなどのクラスを使用します。

店舗(`Store__c`)オブジェクトの住所(`Address__c`、テキスト)に入力された値から、Google Geocoding APIで地理位置情報を取得し、緯度経度(`Location__c`、地理位置情報)に設定するコールアウトの例↓。

```java
public with sharing class LocationHttpCallout {
    @future(callout=true)
    public static void putLocation(Id storeId) {
        Store__c obj = [SELECT Address__c FROM Store__c WHERE Id = :storeId];
        if (obj.Address__c == null) { return; }

        String address = EncodingUtil.urlEncode(obj.Address__c, 'UTF-8');
        String endpoint = 'https://maps.googleapis.com/maps/api/geocode/json?address=' + address + '&key=' + System.label.GoogleApiKey;

        HttpRequest req = new HttpRequest();
        req.setEndpoint(endpoint);
        req.setMethod('GET');
        req.setTimeout(2000); // タイムアウト(ms)。デフォルトは10秒。
        System.debug(LoggingLevel.INFO, '----request----');
        System.debug(LoggingLevel.INFO, 'ENDPOINT: ' + endpoint);

        try {
            HttpResponse res = new Http().send(req);
            System.debug(LoggingLevel.INFO, '----responce----');
            System.debug(LoggingLevel.INFO, 'STATUS: '      + res.getStatus());
            System.debug(LoggingLevel.INFO, 'STATUS_CODE: ' + res.getStatusCode());
            System.debug(LoggingLevel.INFO, 'BODY: '        + res.getBody());

            if (res.getStatusCode() == 200) {
                Location loc = toLocation(res.getBody());
                obj.Location__Latitude__s  = loc.getLatitude();
                obj.Location__Longitude__s = loc.getLongitude();
                update obj;
            }
        } catch (CalloutException e) {
            System.debug(LoggingLevel.ERROR, e);
        }
    }


    private static Location toLocation(String jsonBody) {
        Map<String, Object> resMap   = (Map<String, Object>) JSON.deserializeUntyped(jsonBody);
        List<Object>        results  = (List<Object>) resMap.get('results');
        Map<String, Object> result   = (Map<String, Object>) results[0];
        Map<String, Object> geometry = (Map<String, Object>) result.get('geometry');
        Map<String, Object> loc      = (Map<String, Object>) geometry.get('location');
        Decimal latitude = (Decimal) loc.get('lat');
        Decimal longtude = (Decimal) loc.get('lng');

        return Location.newInstance(latitude, longtude);
    }
}
```

### 留意事項
トリガからの呼び出しには非同期である必要があります。 
エラー: `System.CalloutException: Callout from triggers are currently not supported.`

非同期でコールアウトを行うには`@future(callout=true)`が必要です。
エラー: `System.CalloutException: Callout not allowed from this future method. Please enable callout by annotating the future method. eg: @Future(callout=true)`

一括処理からの呼び出しには、`Database.AllowsCallouts`インターフェースを実装する必要があります。
エラー: `Too many callouts: 1`

### 参考
* [Apex REST コールアウト | Trailhead](https://trailhead.salesforce.com/ja/content/learn/modules/apex_integration_services/apex_integration_rest_callouts)
* [新しいGoogleマップ「Google Maps Platformの使い方」まとめ](https://oxynotes.com/?p=12048)

<!-- 注釈 -->
[^1]: Classic → [設定] - [管理] - [セキュリティのコントロール] - [リモートサイトの設定]