dackdive's blog

新米webエンジニアによる技術ブログ。JavaScript(React), Salesforce, Python など

[Salesforce]Apexコールアウトを利用して、ケース登録時にGitHubのIssueにも登録する

Apexから外部サービスを利用するための方法としてApexコールアウトというのがあります。
今回はこれを使って、GitHubと連携するサンプルを作ってみたという話。
Salesforceでケースが新規作成された時に、指定したGitHubリポジトリにIssueも登録されるというもの。

使用したGitHub APIのバージョンはv3です。

(2014/06/23追記)
すごく基本的なところを勘違いしていたんですが、
HttpRequestで外部サービスを利用するだけならApexコールアウトじゃなくていい んですね。
以下は、トリガから呼ばれる場合など、非同期処理として実行する場合 だと考えてください。
(参考)

リストにカスタムボタンを設置しておいて、押したら外部サービスのREST APIを実行して...とかであれば
普通のクラス内でHttpRequest使えばいいです。

ポイント

  • 連携する外部サービスのURLは「リモートサイトの設定」で設定しておく
  • (追記:トリガの場合は) Apexコールアウトを実装するメソッドには
    @future (callout=true)アノテーションをつけ、メソッドはstaticにする

  • @futureアノテーションをつけたメソッドは引数にプリミティブ型(またはプリミティブ型の配列やコレクション)しか渡せない
    SObjectObjectを渡すことができない)

ソースコード

GitHubに公開してあります。

https://github.com/zaki-yama/force_com/tree/master/case2issue

実装してみる

リモートサイトの設定をする

Apexコールアウトでアクセスする外部WebサービスのURLは事前にSalesforce上で設定しておく必要がある。

管理 > セキュリティのコントロール > リモートサイトの設定

から、「新規リモートサイト」を選び、「リモートサイトのURL」に

https://api.github.com

を指定する。
(リモートサイト名は分かりやすい名前をつければOK)

f:id:dackdive:20140609000426p:plain

トリガクラス

before insertトリガを実装する。
トリガ本体にはロジックを書かず、ハンドラで処理する。

CaseTrigger.trigger

trigger CaseTrigger on Case (before insert) {

    if (Trigger.isBefore && Trigger.isInsert) {
        CaseTriggerHandler.handleBeforeInsert(Trigger.new);
    }
}

CaseTriggerHandler.cls

public class CaseTriggerHandler {

    public static void handleBeforeInsert(List<Case> caseList) {
        for (Case c : caseList) {
            System.debug(LoggingLevel.INFO, 'target case:' + c);

            Map<String, String> request = new Map<String, String> {
                'title' => '[' + c.priority + ']' + c.subject,
                'body'  => c.description
            };
            Case2IssueController.createIssue(request);
        }
    }
}

Case2IssueControllerがApexコールアウトを行う今回のメインのクラス。
また、引数にそのままcaseListを渡さずにわざわざMapを作成している理由は後述。

Apexコールアウトを行うクラス

まず、コードがこちら。

public class Case2IssueController {

    private static String USER_NAME = '*****Your GitHub username here*****';
    private static String PASSWORD  = '*****Your GitHub password here*****';
    private static String REPO_NAME = '*****Your GitHub repository name here*****';

    @future (callout=true)
    public static void createIssue(Map<String, String> request) {

        HttpRequest req = new HttpRequest();

        req.setMethod('POST');

        // Set Callout timeout
        // default: 10 secs(that often causes "System.CalloutException: Read timed out")
        req.setTimeout(60000);

        // Set HTTPRequest header properties
        req.setHeader('Connection','keep-alive');
        req.setEndpoint('https://api.github.com/repos/' + USER_NAME + '/' + REPO_NAME + '/issues');
        // Basic Authentification
        Blob headerValue = Blob.valueOf(USER_NAME + ':' + PASSWORD);
        String authorizationHeader = 'Basic ' + EncodingUtil.base64Encode(headerValue);
        req.setHeader('Authorization', authorizationHeader);

        // Set HTTPRequest body
        req.setBody(JSON.serialize(request));

        Http http = new Http();

        try {
            // Execute Http Callout
            HTTPResponse res = http.send(req);

            System.debug(LoggingLevel.INFO, res.toString());
            System.debug(LoggingLevel.INFO, 'STATUS:' + res.getStatus());
            System.debug(LoggingLevel.INFO, 'STATUS_CODE:' + res.getStatusCode());
            System.debug(LoggingLevel.INFO, 'BODY:' + res.getBody());

        } catch(System.CalloutException e) {
            // Exception handling goes here....
            System.debug(LoggingLevel.INFO, e);
        }
    }
}

GitHubの認証はとりあえず簡単なものをということでBasic認証を使ってます。
それ以外の認証方式についてはここを参考に。
GitHub REST API - GitHub Docs

以下、実装上気をつける点がいくつか。

まず、Apexコールアウトを行うクラスは@future (callout=true)アノテーションが必要。
アノテーションをつけずにトリガハンドラ内で直接実行したりすると、以下のエラーが発生する。

System.CalloutException: Callout from triggers are currently not supported

また、@futureアノテーションをつけたメソッドの引数に List<Case> caseListとかをそのまま渡そうとすると、コンパイルエラーになります。

classes/Case2IssueController.cls -- Error: Unsupported parameter type LIST<Case>

これについては

Salesforce Developers

ここを見ると、次のように書いてある。

The specified parameters must be primitive data types, arrays of primitive data types, or collections of primitive data types. Methods with the future annotation cannot take sObjects or objects as arguments.

つまり、プリミティブ型またはその配列やコレクションしか認めていないということですね。

試してみる

Salesforceからケースを作成してみる。

f:id:dackdive:20140609003515p:plain

さて、GitHubの指定したリポジトリを見てみると...

f:id:dackdive:20140609003533p:plain

登録されてます!
というわけで、実装方法で注意しないといけない部分はあるものの、比較的簡単に外部サービスと連携できました。

リファレンス

HttpRequestクラス http://www.salesforce.com/us/developer/docs/dbcom_apex250/Content/apex_classes_restful_http_httprequest.htm

HttpResponseクラス Salesforce Developers

Caseクラス Salesforce Developers

GitHub API v3 Issues - GitHub Docs

公式ドキュメントなんだけど、あまり情報が載ってない
https://developer.salesforce.com/page/JP:Apex_Web_Services_and_Callouts#HTTP_.28RESTful.29_Services

以下は技術ブログ
deferloader – 株式会社ウフル技術ブログ

http://www30304u.sakura.ne.jp/blog/?p=580