Salesforce Developers Japan Blog

Trailheadスーパーバッジにチャレンジしよう vol2. – Apex Specialist のTipsと日本語訳

TrailheadはSalesforceテクノロジーを学ぶ上で非常に素晴らしい学習プラットフォームです。
丁寧で網羅的な解説から初心者でも迷わない詳細な演習手順によって、だれでも楽しくステップバイステップでSalesforceを実際に触りながら学ぶことができます。
ですがしばらくTrailheadで学習を進め、カテゴリごとの学習修了を意味するバッジの数もそこそこ増えてきた時に、ふとあなたは思うはずです。

 

「手順通りの演習は出来る様になってきたけど、本当に実際の仕事に役立つ知識が身についてるのだろうか?」

 

そこで自分の力試しを行うことが出来るのがスーパーバッジ(Super Badge)という特別な機能です。
スーパーバッジでは従来のモジュールやプロジェクトとは異なり、演習を必ずクリアするための詳細な手順などは書いてありません。
その代わりに前提となる業務要件や解決しなければならない課題、そして行うべきミッションが記されています。
あなたはまさに実際の開発プロジェクトを行なっている様な感覚で自分で考え、最適な答えを探しながら問題を解いていきます。

もちろん”Super”の名の通り、課題の難易度は従来のものに比べて格段に高いです。
ですが、このスーパーバッジのチャレンジを独力でクリアすることができたなら、あなたの力は紛れもなく本物です。
いつでも胸を張ってSalesforceのエキスパートとして、あらゆる場所で大活躍出来ること間違いなしです。

 

・・・前回と同じ書き出しで始まったこちらのエントリーですが、今回は2回目ということで、Salesforce開発者にとって最も基礎となり、また必須といえるスーパーバッジApex Specialistを取得するのに役立つTipsと、スーパーバッジに記される業務要件およびチャレンジの日本語訳を用意しました。

今までスーパーバッジにチャレンジしたかったけど、英語が苦手で、、、と敬遠していた方も、これを機会にぜひ自分のスキルを確認してみましょう。


Apex Specialist 日本語訳

日本語訳はこちらにあります。
Trailhead自体のチャレンジやチェックは英語が前提で行われますので、ラベル等の日本語補足までシステムに入力してしまわない様にご注意下さい。
またスーパーバッジへのチャレンジはUnlockと呼ばれる前提となるモジュールの修了が必要となりますので、まだスーパーバッジに鍵マークが付いている人は、前提となるモジュールから始めてください。

 

 

Apex Specialist Super Badge日本語訳
https://salesforce.quip.com/gJ3QAkFy6boE

 

 

 


Apex SpecialistのTips

Apex Specialistのスーパーバッジを取得するには、トリガの記述方法からデータ一括処理、スケジュールApex、コールアウト及びMockテストといった、Apexの様々な機能を理解していなければなりません。
ここではそのまま正解手順をお教えすることはできませんが、チャレンジを進めていく上で役に立つであろうTipsを3つご紹介します。

またこのスーパーバッジは事前にパッケージのインストールが必要となりますが、Playgroundへパッケージがインストールできないという方は、Trailheadスーパーバッジにチャレンジしよう – Reports & Dashboards Specialist「Ⅰ. パッケージインストールのTips」を参考にしてみて下さい。

 

Ⅰ. ApexトリガのTips

Apexトリガには「常に複数データの一括処理を前提に設計する」という鉄則があります。これはデータローダなどWeb API経由やデータインポートウィザード、ApexによるDML処理など、1回のトランザクション中に複数レコードが同時に処理される場合、トリガにはその複数レコードが一括で渡されるためです。

つまりトリガ対象のデータを表す Trigger.new には sObject 型ではなくList<sObject> 型のデータが入っているため、それを意識したコーディングが必要となります。
また、例えば以下のような要件があったとします。

「取引先責任者の追加・更新時に、所属する取引先から住所(郵送先)をコピーする」

これをそのまま愚直に実装すると、以下のようになります。

trigger UpdateAddressTrigger on Contact (before insert,before update) {
    for(Contact c : Trigger.new){
        Account a = [SELECT id,
                     BillingCountry,
                     BillingState,
                     BillingCity,
                     BillingStreet 
                     FROM Account WHERE id = :c.AccountId];
        c.MailingCountry = a.BillingCountry;
        c.MailingState = a.BillingState;
        c.MailingCity = a.BillingCity;
        c.MailingStreet = a.BillingStreet;
    }    
}

このコードは一見正しく見えますし、状況によっては正しく動作します。しかし、前出の通り複数レコードが一括で登録された場合には、エラーとなるケースが出てきます。
パフォーマンスを維持するためにApexにはガバナ制約と呼ばれる各種制約があり、例えば一回のトランザクション中に実行できるSOQLクエリは100コールまでと決められており、それを越えるとそのトランザクションはエラーとなります。

 

Apexガバナ制約
https://developer.salesforce.com/docs/atlas.ja-jp.salesforce_app_limits_cheatsheet.meta/salesforce_app_limits_cheatsheet/salesforce_app_limits_platform_apexgov.htm

 

上記コードの場合、3-8行目にあるSOQLクエリはforループの中にあるので、Trigger.newのレコード数が100件以上だとエラーになるわけです。Trigger.newには最大200件のデータが入ってくることがあります。(200件以上同時に投入されたデータなどは、200件ごとに分割して処理されます。)

では書き換えてみましょう。

trigger UpdateAddressTrigger on Contact (before insert,before update) {

    //事前に対象となる取引先責任者の取引先の住所項目を1回の関連クエリで取得しておく
    Map<String, Contact> addressContactMap = new Map<String, Contact>(); 
    for(Contact c : [SELECT id,
                     Account.BillingCountry,
                     Account.BillingState,
                     Account.BillingCity,
                     Account.BillingStreet 
                     FROM Contact WHERE id IN :Trigger.new]){
		addressContactMap.put(c.id, c);
    }
    
    //実際の住所の更新は事前に取得したMapから行う
    for(Contact c : Trigger.new){
        Contact addressContact = addressContactMap.get(c.id);
        c.MailingCountry = addressContact.Account.BillingCountry;
        c.MailingState = addressContact.Account.BillingState;
        c.MailingCity = addressContact.Account.BillingCity;
        c.MailingStreet = addressContact.Account.BillingStreet;
    }
}

例えば上記のように書き換えれば、forループの中でクエリは発行されないので、仮に200件のレコードが同時に入ってきたとしても正しく処理ができます。
このようにトリガを使う場合には、常に複数のレコードがTrigger.newに渡されることを意識して設計しましょう。

 

Ⅱ. コールアウト・非同期処理のTips

Apexにはプログラム内からHTTP通信を外部に行うためのHttpクラス、HttpRequestクラス、HttpResponseクラスがあらかじめ用意されています。
これらを利用すれば、簡単に外部サービスのREST APIなどをCallすることが可能です。
注意点としてSalesforceでは情報漏洩を防ぐために、許可していない外部サイトへのコールアウトをブロックします。そこでまずは [設定] – [セキュリティ] – [リモートサイトの設定] から、呼び出し予定のサイトをあらかじめホワイトリストとして登録しておきます。

次に、外部へREST APIなどのコールアウトする際には、コールアウト先サービスやネットワークの状況などによって、迅速にレスポンスが帰ってくるかどうかはわかりません。
レスポンスを待っている間にプログラムが止まってしまうと、全体の処理完了が非常に遅くなったり、最悪の場合タイムアウトして処理全体が失敗に終わります。
そこで、Apexでは @future アノテーションを付与することによってそのメソッドの呼び出しを非同期処理にできます。
呼び出し元のプログラムではメソッド呼び出しはすぐに終了するので、次の処理に取り掛かることができます。そして実際にはバックグラウンドで別のプロセスが、呼び出した@future付きメソッドの処理を別途行なってくれます。

この前出のHttp系クラスと@futureアノテーションを組み合わせることで、ユーザビリティやパフォーマンスを犠牲にすることなく外部サービスへのAPIコールを可能にします。

例えば具体的には以下のような実装になります。

public class SampleCallout {
    private static final String CALLOUT_URL = 'https://jsonplaceholder.typicode.com/posts';
    
    @future(callout = true)
    public static void calloutSomewhere(){
        
        Http http = new Http();
        HttpRequest request = new HttpRequest();
        request.setEndpoint(CALLOUT_URL);
        request.setMethod('GET');
        HttpResponse response = http.send(request);
        if (response.getStatusCode() == 200) {
            List equipmentList = new List();
            List< Object> results = (List< Object>)JSON.deserializeUntyped(response.getBody());

            for (Object resultObject : results) {
                Map<String,Object> item = (Map<String,Object>)resultObject;
                System.debug(item.get('title'));
                //Do Something
            }            

        }
    }
}

@future アノテーションには callout プロパティがありますが、こちらを true に指定することによって、@future アノテーションが付与されたメソッド内での http.send を許可するようになります。
また、最近のREST APIサービスはJSONで結果データを返却するものが多いかと思いますが、JSON文字列をApex Objectと相互変換するためのJSONクラスもあらかじめ用意されています。

このようにコールアウト処理を記述する際には、パフォーマンスなどを考慮して非同期化するなどを考慮しましょう。

 

Ⅲ. スケジュール処理のTips

VisualforceやLightning Componentのボタン押下に起因するApexコントローラや、データの保存に起因するApexトリガだけがApexを実行する方法ではありません。
Apexには時間を指定して自動的に処理を実行するための Schedulable インタフェースが用意されています。

実装方法は極めて単純で、executeメソッドを実装するだけです。

public class RestCallSchedule implements Schedulable{
    public void execute(System.SchedulableContext context){
        SampleCallout.calloutSomewhere();
    } 
}

executeメソッド内で実行したい処理を記述するだけで、スケジュール実行時の処理の準備は完了です。

では次にこの処理を実行するスケジュール設定はどのように行えばよいでしょうか?
これはSystem.schedule メソッドを呼び出すことによって、任意のタイミング、そして任意の繰り返し期間で処理をスケジューリングできます。

String jobId = System.schedule('testSchedule','0 0 1 * * ?', new RestCallSchedule());
CronTrigger ct = [SELECT Id, CronExpression, TimesTriggered, NextFireTime FROM CronTrigger WHERE id = :jobId];

戻り値としてJobIdが帰ってきますので、このJobIDがCronTriggerオブジェクトに正しく登録されているかで、Jobが正しくスケジュールされているかなどを確認することも可能です。
第2引数ではUnixのCron互換の書式で細かな日時や繰り返しの指定が行えます。スケジュールの指定の仕方は詳しくはこちらをご覧ください。

Apex スケジューラ
https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/apex_scheduler.htm

 

また、Apexプログラム内だけでなく、GUIからでもSchedulable インタフェースを実装したApexクラスをスケジュールすることも可能です。

[設定] – [カスタムコード] – [Apexクラス] から Apexをスケジュール ボタンから行えます。

GUIでの設定画面にもありますが、スケジュール処理はSalesforceインスタンス全体のジョブキューの状況によって実際の実行時間が多少前後するので、余裕のあるスケジュール設計を心がけましょう。
スケジュール処理は夜間バッジの実施など実際の業務でもよく使いますので、是非とも押さえておきたい機能です。

 

Ⅳ. コールアウト・非同期処理のテストTips

Apexでは全てのコードをテストすることを推奨しており、実際にApexコード全体ののテストカバレッジ(網羅率)が75%以上、かつApexトリガは原則何らかのテストをしていないと(1%以上)本番環境へデプロイができないという制約が設けられています。
この制約はもちろんコールアウト処理や非同期処理も例外では無いのですが、これらの処理をテストするにはどうしたらよいでしょうか?

幸いApexにはコールアウト処理や非同期処理のテストも可能になるような機能が備わっています。

まずはコールアウト処理をどのようにテストするのかを見ていきましょう。

ApexにはHttpCalloutMock インタフェースという、テスト時に外部サービスを呼び出すのではなく強制的にダミーデータを返すようにする機能が用意されています。
このインタフェースは非常にシンプルで、どんなレスポンスを返すかを実装するだけです。

例えば以下のように実装します。

@isTest
global class RestCalloutServiceMock implements HttpCalloutMock {
    global HTTPResponse respond(HTTPRequest req) {
	        HttpResponse response = new HttpResponse();
			response.setStatusCode(200);
			response.setStatus('Complete');
			response.setBody('[{"userId":"1","id":1,"title":"Test","body":"TestBody"},' +
                         '{"userId":"2","id":2,"title":"Test2","body":""}]');
			return response;
    }
}

次に作成したMockクラスを、テストコードの中で宣言します。

RestCalloutServiceMock fakeResponse = new RestCalloutServiceMock();
Test.setMock(HttpCalloutMock.class, fakeResponse);

これでTest.setMockが呼び出されたテストコード内でコールアウトが行われた場合は、実際のコールアウト部分のコードに関わらず常にMockで実装されたレスポンスが返却されるようにまります。
このようにMockを使用すれば、実装コードにテストを実施するためにトリッキーな処理を記載しなくても、かつ外部サービスの状況に依存することなく簡単ににコールアウトをテストできます。

では次に、非同期処理のテストはどのように行えばよいでしょうか?
例えば @future アノテーションがついたメソッドを呼び出しても、非同期処理はテストコードのアサーションを実行するまでに完了していないかもしれません。
そこで、テスト中に実行した非同期処理が確実に終了していることを確実にする機能がApexには備わっています。
Test.startTest() と Test.stopTest() の処理はテストメソッド中一度だけ呼び出せる処理で、テストコードのガバナ制約へのリソース制約への厳密な遵守と、非同期処理も含めた確実な完了を担保できます。

使い方は非常に簡単で、以下のようにテストコード中で適切なタイミングで呼び出すだけです。

@isTest
private class TestSample {
    
    private void testSomething(){
        /*
         * テストデータの作成など、前処理を行う
         */
        
        //テスト開始。前処理で使用されたSOQL・DMLなどのガバナとは別に新規のリソースが割り当てられる
        Test.startTest();
            
        /*
         * 実際のテストコード
         */
             
        //テスト終了。非同期処理が完了するまで待ち、startTest呼び出し前のガバナリソースが再び割り当てられる
        Test.stopTest();
    
        //確実に非同期処理が終わっているので、こちらで非同期処理の結果が正しいかのアサーションを行う
        system.assert(true);
    }
}

このようにテストコードの前後でstartTest、stopTestを呼び出すことによって、ガバナ制約への遵守をしっかりテストし、非同期処理のアサーションも可能になります。


ここまでのTipsが頭に入ったなら、あとはスーパーバッジを取るだけです。

Q. どうしても解けない場合は?

A. Super BadgeのUnlockができているのであれば、これらのTipsはTrailheadのモジュール内で必ず学んでいるはずです。もしこのTipsを読んでも内容がイマイチ理解できない場合には、まずはUnlockしたモジュールをもう一度確認してみて下さい。
それでもどうしてもチャレンジが先に進めないという方は、Trailblazer CommunityにはTrailheadに関連する話題を話すためのグループがありますので、コミュニティにサポートをお願いしてみるのも良いでしょう。

Apexの実践レベルのバッジをクリアできれば、あなたもこれで中級Salesforce Developerの仲間入りです!

コメント

Trailheadスーパーバッジにチャレンジしよう vol2. – Apex Specialist のTipsと日本語訳