👻

[Salesforce] Apex Trigger ステップアップ

2021/02/02に公開

はじめに

プログラミングはSalesforceのApexが初めてという人、増えてきてますよね。
変数と if と for と List あたりが分かるようになれば動くコードは割と簡単に書けちゃいます。
でもそのお手軽さゆえか、クラス設計を覚える機会もないまま日々の業務に追われていませんか?
今日はトリガーを通じてクラス設計やデザインパターンの一端に触れてステップアップをしていきましょう😊

初心者編1

Trailheadで記載されているコードに、ビジネスロジックを記載したハンドラークラスを組み合わせたコードです。
Trigger.is~を組み合わせて処理タイミングを制御しています。
ハンドラークラスを使ってビジネスロジックを外だししているだけマシなのですが、トリガクラスごとに複数のif文を書かないといけないのは辛いですね。

AccountTrigger.cls
trigger AccountTrigger on Account (before insert, after insert, after update) {
    AccountTriggerHandler handler = new AccountTriggerHandler();
    if (Trigger.isInsert) {
        if (Trigger.isBefore) {
	    handler.beforeInsert(Trigger.new);
        } else if (Trigger.isAfter) {
	    handler.afterInsert(Trigger.new);
        }        
    }
    else if (Trigger.isUpdate) {
	if (Trigger.isBefore) {
	    handler.beforeUpdate(Trigger.old, Trigger.new);
        }
    }
}

初心者編2

switchTrigger.operationType を活用したコードに書き換えてみました。
if文のネストがなくなった分、ちょっとだけコードの見通しが良くなりましたよね。

AccountTrigger.cls
trigger AccountTrigger on Account (before insert, after insert, after update) {
    AccountTriggerHandler handler = new AccountTriggerHandler();
    switch on Trigger.operationType {
        when BEFORE_INSERT {
            handler.beforeInsert(Trigger.new);
        }
        when AFTER_INSERT {
            handler.afterInsert(Trigger.new);
        }
        when BEFORE_UPDATE {
            handler.beforeUpdate(Trigger.old, Trigger.new);
        }
    }
}

中級編

デザインパターンの1つである Template Methodパターン を用いてクラスを再設計していきましょう!

トリガークラス

AccountTrigger.cls
trigger AccountTrigger on Account(before insert, after insert, before update, after update, before delete, after delete, after undelete) {
    new AccountTriggerHandler().run();
}

はい、いきなり3行だけになりました。
Trigger.is~とかTrigger.operationTypeはどこにいったのでしょう?

トリガーハンドラ抽象クラス

TriggerHandler.cls
public abstract class TriggerHandler {

    public TriggerHandler() {}

    public void run() {
        switch on Trigger.operationType {
            when BEFORE_INSERT {
                this.beforeInsert();
            }
            when BEFORE_UPDATE {
                this.beforeUpdate();
            }
            when AFTER_INSERT {
                this.afterInsert();
            }
            when AFTER_UPDATE {
                this.afterUpdate();
            }
            when BEFORE_DELETE {
                this.beforeDelete();
            }
            when AFTER_DELETE {
                this.afterDelete();
            }
            when AFTER_UNDELETE {
                this.afterUndelete();
            }
        }
    }

    protected virtual void beforeInsert() {}
    protected virtual void beforeUpdate() {}
    protected virtual void beforeDelete() {}
    protected virtual void afterInsert() {}
    protected virtual void afterUpdate() {}
    protected virtual void afterDelete() {}
    protected virtual void afterUndelete() {}
}

abstract とか virtual っていうキーワードが出てきました。何か覚えてますか?
上記コードでは abstract は抽象クラスの宣言として、virtual は処理の上書きが可能な仮想メソッドとして利用しています。
run()メソッドで各メソッドを呼び出してますが、このクラス内で定義されているbeforeInsert()などのメソッドに処理実装はありません。
ただただメソッドを呼び出しているだけです。

トリガーハンドラ具象クラス

AccountTriggerHandler.cls
public inherited sharing class AccountTriggerHandler extends TriggerHandler {
    private List<Account> triggerOld;
    private List<Account> triggerNew;
    private Map<Id, Account> triggerOldMap;
    private Map<Id, Account> triggerNewMap;

    public AccountTriggerHandler() {
        if (Trigger.old != null) this.triggerOld = (List<Account>) Trigger.old;
        if (Trigger.new != null) this.triggerNew = (List<Account>) Trigger.new;
        if (Trigger.oldMap != null) this.triggerOldMap = (Map<Id, Account>) Trigger.oldMap;
        if (Trigger.newMap != null) this.triggerNewMap = (Map<Id, Account>) Trigger.newMap;
    }

    public override void beforeInsert() {
        for (Account newRec : this.triggerNew) {
	    // 何らかの処理...
        }
    }

    public override void afterInsert() {
        for (Account newRec : this.triggerNew) {
	    // 何らかの処理...
        }
    }

    public override void beforeUpdate() {
        for (Account newRec : this.triggerNew) {
	    // 何らかの処理...
        }
    }
}

今度は extends キーワードがでてきました。これを使うことでTriggerHandlerクラスを継承した実体のあるクラスを作成することができます。
コンストラクタでは Trigger.new などのトリガコンテキストをクラス変数に格納しています。

そして、override キーワードを使って、仮想メソッドを拡張しています。
抽象クラス側で呼び出しタイミングは制御しているので、実体クラス側に分岐は出てこず、beforeInsert()などのメソッドをoverride するだけで、必要なタイミングで処理が呼び出されます。

このように、Triggerの分岐処理のような処理の枠組みを抽象クラスにまとめ、具象クラスに具体的な処理を記載していく書き方をデザインパターン Template Methodパターン といいます。
このパターンを利用することにより、Triggerの実装方法の共通化を図れますし、毎回Triggerならではの分岐処理を記載する必要がなくなります。

まとめ

上記のどの書き方もSalesforce上では正しく処理されるため、いずれも本番環境で使われているのではないかと思います。1組織あたりトリガーは複数作成されることが多いと思いますし、処理の共通化、書き方の統一などメリットを享受できるクラス設計を考えていきましょう!

もっと高度なトリガー構成

参考

Discussion