SOAP APIのサーバとしてAPIを公開し他システムへ利用させる(コールイン)ことも、
外部のSOAP APIをApexクラスから呼び出すこと(コールアウト)もできる。
しかし
RESTサービスについてはコールアウト(外部RESTを呼び出す)のみ実装可能で、
残念ながらRESTサービスサーバにすることはできない。
おそらく、
ガバナ制約という足かせプラットフォーム環境で、
よくAjaxで活用されるRESTコールを受け付けるのは
どう考えても向いていないから
なのだろう。
以下のサンプルは、
Force.com開発者コースDEV541の演習8-3で使用したコードである。
このコードはRESTのコールアウトのサンプルでもあるが、
非同期Apexのサンプルでもある。
非同期Apexは、
Javaでいう別Theadのrun()のような非同期メソッドを含む
Apexのことである。
非同期Apexメソッドを呼び出すと、
その結果を待たずに次の行へ処理が進み、
非同期Apexメソッド上の処理は別途スレッドで実行される。
(スレッド、という名前なのかはわからないけど)
ここではApexトリガからRESTサービスを呼び出しているが、
処理時間がインターネットや相手側の負荷でよめないので
非同期メソッド上に処理を記述している。
トリガというアプリ屋泣かせの足かせに
非同期というさらに重石を載せるような
コードをかかなくてはならず、
結果紹介するコードはサンプルのくせに
実装にフラグを使った保守しにくいコードになっている。
さらにこのコードは受け取ったXMLをDOMを使って
要素内の情報を取り出しているサンプルにもなっている。
注意
XMLDom クラスは
Force.comにあらかじめ定義されているクラスではありません。
[developerforce]XML Dom parser(英語)
のViewSourceボタンを押してソースを入手できます。
テストクラスも一緒に置いてあるので、
本番環境にもあげられます。
// Apex Code 演習8-3 非SOAP HTTPコールアウト(1) // // trigger CandidateAddressValidation on Candidate__c (after insert, after update, before update) { // トリガ対象が1件のみの場合 if (Trigger.new.size() == 1){ // 新規追加の場合 if (Trigger.isInsert) { // 新規の場合は非同期妥当性検査フラグの状態にかかわらず // 住所情報妥当性チェック(非同期処理)を行い結果を // レコード上に書き込み更新する非同期処理を実行する // なお、非同期実行は1回のApex呼び出しで最大10回まで AddressValidator.validateAddress(Trigger.new[0].id); // 上記メソッド内のロジックは非同期で処理されており // コールしたら結果を待たずに次の処理へ進む } // 更新の場合 if (Trigger.isUpdate){ // 更新前処理の場合 if (Trigger.isBefore) { // 住所情報更新の場合 if ((Trigger.new[0].Street_Address_1__c != Trigger.old[0].Street_Address_1__c) || (Trigger.new[0].Street_Address_2__c != Trigger.old[0].Street_Address_2__c) || (Trigger.new[0].City__c != Trigger.old[0].City__c) || (Trigger.new[0].State_Province__c != Trigger.old[0].State_Province__c)) { // 非同期妥当性検査フラグを偽にする Trigger.new[0].AsyncValidationFlag__c = false; // 更新時妥当性検査処理は // after updateでのみ行うのでフラグを // 偽にすることでいちいち呼び出さない // ようにしている } // デバッグログ System.debug('\ncandidate=' + Trigger.new[0] + '\n'); // 更新後処理の場合 } else { // 非同期妥当性検査フラグが真の場合 if (!Trigger.new[0].asyncvalidationflag__c){ // 住所情報妥当性チェック // (非同期処理)を実行し結果を // 応募者レコード上に書き込む(更新) // 非同期実行は1回のApex呼び出しで // 最大10回まで AddressValidator.validateAddress( Trigger.new[0].id); // 上記メソッド内のロジックは非同期で // 処理されておりコールしたら結果を待 // たずに次の処理へ進む } } } // 更新の場合 } // 1件のみの場合 // 複数県の場合≒バッチ実行で複数件が来た場合は何もしない // おそらく、大量投入されたらコールアウトのガバナ制約を食いつぶすから }
通常トリガクラスで
呼び出し元となるレコードの更新を行う場合
DML処理(insert/update/delete)はご法度である。
しかしREST結果取得に時間がかかりそうなので
非同期処理化して
update
ステートメントを使っている。
// Apex Code 演習8-3 非SOAP HTTPコールアウト(1) // // StrikeIronのサービスの1つである住所妥当性検査を使用して // 応募者(Candidate)レコード上の住所情報の妥当性をチェックして // 結果を同レコード上に格納する // public class AddressValidator { // StrikeIronサービスへログインするための情報 static final StrikeIronInfo__c info = [select userid__c, password__c from strikeironinfo__c where active__c = true limit 1]; // StrikeIronInfoはカスタムオブジェクトであり、 // あらかじめStrikeIronサイトで登録したユーザID、 // パスワード(平文)が1行だけ格納されている // 応募者の住所情報妥当性をチェックして結果を // 応募者レコード上に更新書き込みを行う // // ただしこの処理は米国住所のみ対応しており // 日本の住所妥当性は検査できない // 引数: id 応募者レコードID // 戻り値: なし(非同期メソッドはvoid必須) // // 非同期コールアウトメソッドとして指定しているので // このメソッドを呼び出した後は呼び出し元の次の処理へ // 進み、この中の処理は非同期に実行される @Future(callout=true) public static void validateAddress(String id) { // カスタムオブジェクトError_Logがあらかじめ // 定義されておりレコードとして書き込むための // メッセージ用変数 String msg = ''; try { // 引数で渡されたレコードIDから // レコード情報を取得 Candidate__c cand = [select // 非同期妥当性検査フラグ // StrikeIronを通したかどうかであり // 検査結果の合否ではない AsyncValidationFlag__c, // 住所1 Street_Address_1__c, // 住所2 Street_Address_2__c, // 市 city__c, // 州 state_Province__c, // 郵便番号 zip_postal_code__c, // 国 country__c, // 住所妥当性フラグ valid_us_address__c, // 行政区画 county__c, // 議会地区(選挙区?) congress_district__c, // 緯度(99.9999) latitude__c, // 経度(999.9999) longitude__c, // 住所エラーメッセージ // StrikeIronサービスのエラーメッセージ // を格納するための項目 address_error__c // 応募者オブジェクト from candidate__c // 引数で渡されたIDと一致する where id=:id]; // 応募者情報・StrikeIronログイン情報をメッセージに追加 msg += 'candidate=' + cand + '\n\n'; msg += 'info=' + info + '\n\n'; // 送信情報(リクエスト)を生成 HttpRequest req = new HttpRequest(); // エンドポイント(接続するサービスのURL)文字列を // 作成する // GETパラメータもURLの後ろに付加させるので // 適宜EncodingUtilを使用してUTF-8エンコード化して // 文字列を完成させる // エンドポイント用変数 String endpoint = 'http://ws.strikeiron.com/' + 'StrikeIron/USAddressVerification4_0/' + 'USAddressVerification/VerifyAddressUSA?'; // GETパラメータ文字列:ユーザID String uId = 'LicenseInfo.RegisteredUser.UserID=' + // EncodingUtilクラスを使ってエンコード EncodingUtil.urlEncode(info.userid__c,'UTF-8'); // GETパラメータ文字列:パスワード String pw = '&LicenseInfo.RegisteredUser.Password=' + info.password__c; // GETパラメータ文字列:住所情報1 String street1 = '&VerifyAddressUSA.addressLine1='; street1 += cand.Street_Address_1__c != null ? // 存在する場合はEncodingUtilでエンコード化 EncodingUtil.urlEncode( cand.Street_Address_1__c,'UTF-8') : // nullの場合は空文字にする ''; // GETパラメータ文字列:住所情報2 String street2 = '&VerifyAddressUSA.addressLine2='; street2 += cand.Street_Address_2__c != null ? // 存在する場合はEncodingUtilでエンコード化 EncodingUtil.urlEncode( cand.Street_Address_2__c,'UTF-8') : // nullの場合は空文字にする ''; // GETパラメータ文字列:市、州、郵便番号 String citystatezip = '&VerifyAddressUSA.city_state_zip='; citystatezip += cand.City__c != null ? // 存在する場合はEncodingUtilでエンコード化 // して次のデータのための空白1個追加 EncodingUtil.urlEncode( cand.City__c + ' ','UTF-8') : // nullの場合は空文字にする ''; citystatezip += cand.State_Province__c != null ? // 存在する場合はEncodingUtilでエンコード化 // して次のデータのための空白1個追加 EncodingUtil.urlEncode( cand.State_Province__c + ' ','UTF-8') : // nullの場合は空文字にする ''; citystatezip += cand.Zip_Postal_Code__c != null ? // 存在する場合はEncodingUtilでエンコード化 // して次のデータのための空白1個追加 EncodingUtil.urlEncode( cand.Zip_Postal_Code__c,'UTF-8') : // nullの場合は空文字にする ''; // エンドポイント文字列作成 endpoint += uId + pw + street1 + street2 + citystatezip + // 米国住所妥当性検査 '&VerifyAddressUSA.casing=Proper'; // メッセージにエンドポイント追加 msg += 'endpoint=' + endpoint + '\n'; // リクエストにエンドポイント登録 req.setEndpoint(endpoint); // HTTP MethodをGETにする req.setMethod('GET'); // HTTP通信インスタンス生成 Http http = new Http(); // リクエスト送信しレスポンスを受領 HttpResponse res = http.send(req); // HTTP状態コードが200(通信成功)の場合 if (res.getStatusCode() == 200) { // 応募者レコードを更新する // レスポンスから受信メッセージ本文の // DOMオブジェクトを生成 XMLDom doc = new XMLDom(res.getBody()); // メッセージ内のVerifyAddressUSAResultタグ部分 // のElementインスタンスを取得 XMLDom.Element result = doc.getElementByTagName( 'VerifyAddressUSAResult'); // 属性AddressStatus値がValid(妥当性検査合格) // の場合 if (result.getValue('AddressStatus')=='Valid'){ // 住所妥当性検査フラグを真にする cand.valid_us_address__c = true; // 国は米国固定なので決め打ち cand.Country__c = 'US'; // 属性ZipPlus4値を郵便番号に格納 cand.Zip_Postal_Code__c = result.getValue('ZipPlus4'); // 属性County値を行政区画に格納 cand.County__c = result.getValue('County'); // 属性CongressDistrict値を議会地区に // 格納 cand.Congress_District__c = result.getValue( 'CongressDistrict'); // 属性Latitude値を緯度に格納 cand.Latitude__c = // 数値項目なのでDoubleクラスを // 使ってDouble化する Double.valueOf( result.getValue( 'Latitude')); // 属性Longitude値を経度に格納 cand.Longitude__c = Double.valueOf( result.getValue( 'Longitude')); // 妥当性検査成功なので空文字にする cand.address_error__c = ''; // Validでない=不合格の場合 } else { // 住所情報は書き換えない // StrikeIronサービスで取得できる // 行政地区、議会地区、緯度、経度 // は空文字やnull値に初期化しておく // 行政区画を空文字にする cand.County__c = ''; // 議会地区を空文字にする cand.Congress_District__c = ''; // 緯度をnullにする // 画面に項目は出るが値の部分には // なにも出ない cand.Latitude__c = null; // 経度をnullにする cand.Longitude__c = null; // StrikeIronエラーメッセージを格納 cand.address_error__c = // エラー番号 result.getValue( 'AddressErrorNumber') + ' - ' + // エラーメッセージ result.getValue( 'AddressErrorMessage'); // 住所妥当性検査フラグを偽にする cand.valid_us_address__c = false; } // 結果がValidであるかどうかはともかく // StrikeIron妥当性検査を実施したので // 非同期妥当性検査フラグを真にする cand.asyncvalidationflag__c = true; // 更新後応募者レコード情報をメッセージに追加 msg += 'async updated candidate=' + cand + '\n'; // 応募者レコードを更新する update cand; // この更新処理についても // CandidateAddressValidationトリガを // 2度(before update/after update)実行 // されてしまうが、 // トリガ内で住所1、住所2、市、州の更新を // した場合のみafter updateで妥当性検査が // 実行される実装になっているため、 // StrikeIronサービスは呼び出されない // だったらinsertとbefore updateのみの // トリガにすればいいのだけど、 // Apexトリガには // 1. 元のレコードがロード、または初期化される // 2. 新しいレコードの値がロードされ、古い値を // 上書きされ、必須項目などの検証処理が実行 // される // 3. すべてのbeforeトリガが実行される // 4. カスタム検証ルールを含むシステム検証が // 行われる // 5. レコードはデータベースに保存されるが、 // コミットはされない // 6. すべてのafterトリガが実行される // 7. 割り当てルールが実行される // 8. 自動返信ルールが実行される // 9. ワークフロールールが実行される // 10. ワークフローフィールドが更新されたら、 // レコードがリロードされる // 11. レコードの更新後、beforeトリガおよび // afterトリガがもう一度起動される // 12. エスカレーションルールが実行される // 13. すべてのDML操作がデータベースにコミット // される // 14. 電子メールの送信など、コミット後の // ロジックが実行される // ※引用元: // Force.com開発者コードテキストDEV541 p174 // この順番に処理されていく。 // だから、宣言的開発で設定された必須 // チェックや入力規則などを先に実行させたい // がために面倒でもafterで処理しなくてはならな // かったから、と読んだ・ // HTTP状態コードが200ではない(通信失敗の)場合 } else { // デバッグログにレスポンス情報を書き込む System.debug('Callout failed: ' + res); // Error_Logへメッセージやレスポンスの主要情報 // を書き込む msg += 'STATUS:'+res.getStatus(); msg += '\nSTATUS_CODE:'+res.getStatusCode(); msg += '\nBODY: '+res.getBody(); Error_Log__c log = new Error_Log__c(); log.trace__c = msg; insert log; } // HTTP状態コードのif文 // 例外発生時処理 } catch(Exception e) { // デバッグログに例外情報書き込む System.debug('ERROR: '+ e); // Error_Logへ例外情報を書き込む Error_Log__c log = new Error_Log__c(); log.trace__c = msg + '\n' + e.getCause() + '\n' + e.getMessage(); insert log; } // try-catch句:終わり } // validateAddress():終わり }
非同期処理であり、
普通のプログラミング言語でもデバッグが大変なため
上記のようなError_Logカスタムオブジェクトを別途用意して
エラーの詳細情報をくみ取れるようにしておくと便利である。
が、
ログ情報がレコードとしてがんがんたまっていくので
容量上限に気を付けないとシステムが止まってしまう。
おまけに自動削除Apexトリガなどは埋め込みにくいので
人間系管理が面倒になることも注意だ。
ぬふー
0 件のコメント:
コメントを投稿