LoginSignup
5
2

More than 1 year has passed since last update.

Salesforce Platform Developer I:Apexと.NETの基本(トリガー、非同期Apex)

Last updated at Posted at 2021-05-23

Apexと.NETの基本

.NET の概念の Lightning プラットフォームへの対応付け

Lightningプラットフォーム

Force.comプラットフォームはSaasの一種ですが、他のSaasと違いの1つはメタデータ駆動型です。

Apex

プログラミング言語です。C#、Javaと同じ、オブジェクト指向言語です。

サポートされているデータ型:
①Integer、Double、Long、Date、Datetime、String、Booleanなどのプリミティブ型、そして、18桁のIDデータ型
 ※すべての変数がデフォルトで null に初期化されます。
②SObject:Account、Contactなどの標準オブジェクトとMyCustomObject__cのようなカスタムオブジェクト
③コレクション:List、Set、Mapのみ
List
image.png
Set
image.png
Map
image.png

Visualforce

Apex書けなくてもデータ取得と作成可能
例:

ShowContact.vfp
<apex:page standardController="Contact">
  <apex:form>
    <apex:pageBlock title="Edit Contact" mode="Edit">
       <apex:pageBlockButtons >
         <apex:commandButton action="{!edit}" id="editButton" value="Edit"/>
         <apex:commandButton action="{!save}" id="saveButton" value="Save"/>
         <apex:commandButton action="{!cancel}" id="cancelButton" value="Cancel"/>
       </apex:pageBlockButtons>
       <apex:pageBlockSection >
          <apex:inputField value="{!contact.lastname}" />
          <apex:inputField value="{!contact.accountId}"/>
          <apex:inputField value="{!contact.phone}"/>
       </apex:pageBlockSection>
    </apex:pageBlock>
  </apex:form>
</apex:page>

Challenge

AccountUtils.cls
public with sharing class AccountUtils {
    public static List<Account> accountsByState(String st){
        List<Account> results = [SELECT Id,Name FROM Account where BillingState =:st];
        return results;
    }
}

実行コンテキストの理解

Apex を呼び出す方法

方法 説明
データベースのトリガ カスタムまたは標準オブジェクトでの特定のイベントに対して呼び出されます。
匿名 Apex 開発者コンソールやその他のツールにおいてその場で実行されるコードスニペット。
非同期 Apex future または Queueable Apex の実行時、一括処理ジョブの実行時、または指定間隔での Apex 実行のスケジュール時に行われます。
Web サービス SOAP または REST Web サービスを介して公開されるコード。
メールサービス 受信メールを処理するために設定されるコード。
Visualforce または Lightning ページ Visualforce コントローラおよび Lightning コンポーネントは、自動的に、またはユーザがアクションを開始したとき (ボタンのクリックなど) に Apex コードを実行できます。Lightning コンポーネントも Lightning プロセスおよびフローによって実行できます。

Apexデータアクセス権限

権限について、Apexはデフォルトでシステムモードで動作するので、全オブジェクトに対して全レコードの編集が可能です。
だから、Apexに声明する時、以下のキーを設定
with sharing:ユーザコンテキスト(レコードの共有ルール採用、実行ユーザの権限利用)
without sharing:システムコンテキスト(システム別、レコードの共有ルール無視、システム管理者の権限利用)
inherited sharing:Default レコードの共有権限は「with sharing」となります。呼び出し側の権限を継承する

※Apex Class 定義する時は何も指定しないと、Default は「without sharing」呼び出し側の権限を継承して実行します。

トリガの基礎

●トリガーは7つがあります。
 ・before insert
 ・after insert
 ・before update
 ・after update
 ・before delete
 ・after delete
 ・after undelete
※トリガは、同じ処理を Salesforceが提供するポイント&クリック自動化ツールのいずれでも実行できないことが絶対確実である場合にのみ、最後の手段として使用します。

実行コンテキストのマーク

①ハンドラー作成

AccountHandler.cls
public with sharing class AccountHandler {
    public static void CreateNewOpportunity(List<Account> accts) {
        for (Account a : accts) {
            Opportunity opp = new Opportunity();
            opp.Name = a.Name + ' Opportunity';
            opp.AccountId = a.Id;
            opp.StageName = 'Prospecting';
            opp.CloseDate = System.Today().addMonths(1);
            //ガバナ制限発生可能性が高い
            insert opp;
        }
    }
}

②トリガー作成

AccountTrigger.cls
trigger AccountTrigger on Account (before insert, before update, before
    delete, after insert, after update, after delete,  after undelete) {
    if (Trigger.isAfter && Trigger.isInsert) {
        AccountHandler.CreateNewOpportunity(Trigger.New);
    }
}

③[Debug (デバッグ)] > [Open Execute Anonymous Window (実行匿名ウィンドウを開く)] > 下記のソース実行(取引先レコード挿入)
※[Open Log (ログを開く)]選択、実行する。

Account acct = new Account(
    Name='Test Account 2',
    Phone='(415)555-8989',
    NumberOfEmployees=50,
    BillingCity='San Francisco');
insert acct;

④ログ確認
image.png

ガバナ制限対応

Insert処理はループの中にあるので、データ量が多いの場合、ガバナ制限を発生する。
改善方法:
①商談リストを作成
②各商談データレコード作成
③一括挿入

AccountHandler.cls
public with sharing class AccountHandler {
    public static void CreateNewOpportunity(List<Account> accts) {
        List<Opportunity> opps = new List<Opportunity>();
        for (Account a : accts) {
            Opportunity opp = new Opportunity();
            opp.Name = a.Name + ' Opportunity';
            opp.AccountId = a.Id;
            opp.StageName = 'Prospecting';
            opp.CloseDate = System.Today().addMonths(1);
            opps.add(opp);
        }
        if (opps.size() > 0) {
            insert opps;
        }
    }
}

負荷テスト実行

AccountTrigger_Test.cls
@isTest
private class AccountTrigger_Test {
    @isTest static void TestCreateNewAccountInBulk() {
        // Test Setup data
        // Create 200 new Accounts
        List<Account> accts = new List<Account>();
        for(Integer i=0; i < 200; i++) {
            Account acct = new Account(Name='Test Account ' + i);
            accts.add(acct);
        }              
        // Perform Test
        Test.startTest();
        insert accts;                               
        Test.stopTest();
        // Verify that 200 new Accounts were inserted
        List<Account> verifyAccts = [SELECT Id FROM Account];
        System.assertEquals(200, verifyAccts.size());    
        // Also verify that 200 new Opportunities were inserted
        List<Opportunity> verifyOpps = [SELECT Id FROM Opportunity];                              
        System.assertEquals(200, verifyOpps.size());                             
    }
}

image.png
image.png

Challenge

①ハンドラー作成

AccountTriggerHandler.cls
public with sharing class AccountTriggerHandler {
    public static void CreateAccounts(List<Account> accts)
    {
        for (Account a : accts){
            a.ShippingState = a.BillingState;
        }
    }
}

②トリガー作成

AccountTrigger.cls
trigger AccountTrigger on Account (before insert, before update, before
    delete, after insert, after update, after delete,  after undelete) {
    if (Trigger.isBefore && Trigger.isInsert) {
        AccountTriggerHandler.CreateAccounts(Trigger.New);
    }
}

③テストクラス作成

AccountTriggerTest.cls
@isTest
public class AccountTriggerTest {
    @isTest static void TestCreateNewAccountInBulk() {
        // Test Setup data
        // Create 200 new Accounts
        List<Account> accts = new List<Account>();
        for(Integer i = 0; i < 200; i++) {
            Account acct = new Account(Name = 'Test Account ' + i);
            acct.BillingState = 'CA';
            accts.add(acct);
        }              
        // Perform Test
        Test.startTest();
        insert accts;                               
        Test.stopTest();
        // Verify that 200 new Accounts were inserted
        List<Account> verifyAccts = [SELECT BillingState,ShippingState FROM Account];
        for (Account ac : verifyAccts){
            System.assertEquals('CA', ac.ShippingState); 
        }
    }
}

非同期Apexの使用

非同期の利用理由

・処理するレコード件数が非常に多い。
・外部 Web サービスへのコールアウトを行う。
・一部の処理を非同期コールにオフロードしてより適切で高速なユーザエクスペリエンスを作成する。

非同期処理実現方法

・futureメソッド
・Queueableインターフェース
・スケジュールApex
・Database.Batchableインターフェース

参考資料

futureメソッド

@futureタグ追加すれば、同期から非同期に変更可能

●シナリオ
・ガバナ制限緩和
・外部Webサービスコールアウト
・混和DML更新エラー退避

●制限
・メソッド前に、static void である必要あり。
・引数はSObjectができない(理由は待ち間に、SObject変更可能性があり)
・futureからfutureメソッド呼び出しできない
・実行順番が指定できない

@future(callout=true)    
    static void myFutureMethod(Set<Id> ids) {
        // 処理内容
    }

Queueableインターフェース

クラス作成時implements Queueable
ほぼfutureメソッド同等、futureメソッド代わりに可能。
変更不可の理由はクラスの中に一部同期処理がある場合、Queueableインターフェース利用できない

●シナリオ
・ガバナ制限緩和
・外部Webサービスコールアウト
・混和DML更新エラー退避
・ジョブチェーニング

●制限
・横:一つトランザクションに最大のQueueable数は50。
・縦:1つのみ

public class XXQueueable implements Queueable {
    public XXQueueable(List<Account> records, ID id) {
        // 初期化処理
    }
    public void execute(QueueableContext context) {
        // 業務処理
    }
}

Schedulableインターフェース

クラス作成時implements Schedulable
●シナリオ
・ガバナ制限緩和
・特定の時間に実行

●制限
・Apexクラスからスケジュール登録する時、implements Schedulable付けているApexのみ登録可能
・UIから登録:1時間まで登録可能;日次、週次、月次設定可能
 プログラムから登録:秒単位
・予定より遅れがある
・スケジュールできるApexジョブ数は100
@future呼び出し可能

global class XXSchedulable implements Schedulable {
    global void execute(SchedulableContext ctx) {
        // 業務処理
    }
}

Database.Batchableインターフェース

バッチインターフェース、クラス作成時implements Database.Batchable<sObject>

●シナリオ
・ジョブチェーニング
・大量データの処理

●制限
・複数バッチ並列で実行する場合、データLOCKの問題は要注意。
・トラブルシューティングが煩雑になることがあります。
・ジョブはキューに追加され、サーバの可用性に影響されるため、予想よりも時間がかかる場合があります。

global class XXBatchable implements Database.Batchable<sObject> {
    // バッチの処理対象データ(1回のみ実行)
    global Database.QueryLocator start(Database.BatchableContext bc) {
        String strQuery = 'SELECT id, Name・・・FROM XX__c WHERE・・・';
        Database.getQueryLocator(strQuery);
    }

    // バッチサイズごとに実行する(複数回実行)
    global void execute(Database.BatchableContext bc, List<sObject> scope){
        // 業務処理
    }

    // バッチの後処理 (メール送信など)(1回のみ実行)
    global void finish(Database.BatchableContext bc){
      // 後続処理
    }
}

デバッグと診断の実行

ソースの確認したい部分にsystem.debug()追記。
System.debug('My Debug Message');

レベルは以下です、情報量は左⇒右より詳しくなる。
NONE<ERROR<WARN<INFO<DEBUG<FINE<FINER<FINEST

デバッグログ制限
・最大20MBです
・組織に保持できるデバッグログは最大1,000MBです
※画面表示と実行が遅くになると、デバッグログ削除してみてください。

image.png
image.png
image.png
image.png
image.png
image.png
image.png

チェックポイントの設定

※Java、.NETと違います、ポイント設定しても、停止しない、コード行に関して多くの詳細な実行情報を明らかに確認できる

確認したいクラスの確認したい行の左余白の上に置いて 1 回クリックします。ポイント設定。
image.png
[Debug (デバッグ)] > [Open Execute Anonymous Window (実行匿名ウィンドウを開く)] > [Execute (実行)]
[Symbols (記号)] タブで、実行ツリー内のノードを展開します。[Key (キー)] 列と [Value (値)] 列を確認します。
image.png
[Heap (ヒープ)] タブをクリックします。[Count (件数)] 列と [Total Size (合計サイズ)] 列を確認します。
image.png

参照資料

拡張豆知識:

SaaS:Software-as-a-Service(例:salesforce)
PaaS:Platform as a Service(AWS、Azure、Heroku)
IaaS:Infrastructure as a Service(Amazon EC2)
DaaS:Desktop as a Service(Windows Virtual Desktop、クラウド上のVDIの認識でよい)
MaaS:Mobility as a Service(バスや電車、タクシー、飛行機など、すべての交通手段による移動を一つのサービスに統合し、ルート検索から支払いまでをシームレスにつなぐ概念である。)
image.png
image.png
image.png

5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2