yonet77的な雑記帳

日々思いついたネタなどを書き留めておきます

Apex Triggerの実装について考えてます

Force.comでの開発で、ほとんど避けて通れない...の1つに

Apex Trigger

があると思います。


Apexトリガを実装するにあたっては、ココで紹介されてるテンプレートが結構気に入ってて、これまで流用させてもらってました。
(2010年と結構古い記事ですが)

これはトリガ側のイベントに合わせて、コンテキスト変数をトリガハンドラ(Apexクラス)に渡して、実際の処理はハンドラ側に実装する、という具合になります。
複数のイベントが混じりあうことなく、イベント毎に処理を実装していくので、個人的には分かりやすい構成になるかなー...と考えてました。


トリガハンドラの肥大化

ただ、トリガに要求される機能がだんだんと膨れ上がってくると、トリガハンドラが肥大化しちゃって、他の人(というか他の開発者)が、ちょっとこんな機能や、あんな機能を追加したい...と思っても、肥大化したApexクラスを目の前にして挫けることも多々出てきそうです。。
やっぱり人もコードもスリムなのが良いですよね...(え?


トリガの拡張

また、今後例えば管理パッケージを作成し、その管理パッケージをカスタマイズしてくれる(して頂ける)開発者が出てきたとき、管理パッケージ側のApexトリガ内の処理が走る前/走った後に、そっとカスタマイズした機能を忍び込ませたりしたい...ですよね。
※トリガの実行順序はForce.com側で決定されるもので制御不能...(でしたっけ?たしか)


現在考え中...ですが

というわけで、トリガハンドラの肥大化を抑え、トリガ拡張のためのポイントを追加できるような仕組みがないかなー...と考えているところです。
そんな案を、ちょっと晒してみます。。
githubにはココ


Trigger >>
※サンプルとして、Accountに対するトリガを作ってみました。

...あまり大したことしてません。"TrgHandlerAccount"というヤツも見てみましょう。

TrgHandlerAccount >>

TrgHandlerを継承しているので、ついでにそちらも。

TrgHandler >>


補足

まず、大元になるインタフェースを定義します。

public virtual interface ObserverTrg {

}

その後に、各イベントに応じたインタフェースを定義します。最終的にトリガハンドラで、各種インタフェースの実装クラスにあるメソッドを呼び出してあげる...という具合にする感じです。

public interface ObserverTrgBeforeInsert extends ObserverTrg {
	List<SObject> onBeforeInsert(List<SObject> newObjects);
}

public interface ObserverTrgBeforeUpdate extends ObserverTrg {
	List<SObject> onBeforeUpdate(List<SObject> oldObjects, List<SObject> newObjects, Map<Id, SObject> oldObjectMap, Map<Id, SObject> newObjectMap);
}

...
/**
 * Event Action: Before Insert
 */
public void OnBeforeInsert(Account[] newAccounts){
    for(TrgHandler.ObserverTrgBeforeInsert observer : beforeInsertObservers){
    	newAccounts = (List<Account>)observer.onBeforeInsert(newAccounts);
    }
}

/**
 * Event Action : Before Update
 */
public void OnBeforeUpdate(Account[] oldAccounts, Account[] updAccounts, Map<Id, Account> oldAccountMap, Map<Id, Account> updAccountMap){
        
    for(TrgHandler.ObserverTrgBeforeUpdate observer : beforeUpdateObservers){
    	updAccounts = (List<Account>)observer.onBeforeUpdate(oldAccounts, updAccounts, oldAccountMap, updAccountMap);
    }

}
    

さて、インタフェースの中身は・・というと、例えばこんな感じで。各イベントのインタフェースを実装したクラスをテキトーに作ってみます。

public with sharing class TrgHandlerAccount_Validation implements TrgHandler.ObserverTrgBeforeInsert, TrgHandler.ObserverTrgBeforeUpdate {

	public TrgHandlerAccount_Validation(){}
	
	/**
	 * Action : Before Insert
	 */
	public List<SObject> onBeforeInsert(List<SObject> newAccounts){
		for(Account acc : (List<Account>)newAccounts){
			// Some Validation Process...etc	
		}
		return newAccounts;
	}
	
	/**
	 * Action : Before Update
	 */
	public List<SObject> onBeforeUpdate(List<SObject> oldAccounts, List<SObject> updAccounts,
			Map<Id, SObject> oldAccountMap, Map<Id, SObject> updAccountMap){
		for(Account acc : (List<Account>)updAccounts){	
			// Some Validation Process...etc
		}		
		return updAccounts;
	}
}

そして最後に、実装したクラスが呼び出されるようにしてあげましょう。トリガ内で、上記の実装クラスのインスタンスをトリガハンドラに渡してあげましょう。

trigger AccountTrigger on Account (before insert, before update) {


    TrgHandlerAccount handler = new TrgHandlerAccount(Trigger.isExecuting, Trigger.size);
    
    // トリガハンドラの拡張
    handler.addObserver('before insert,before update', new TrgHandlerAccount_Validation());
    
    
    // トリガの実行
    if(Trigger.isInsert && Trigger.isBefore){
    	handler.OnBeforeInsert(Trigger.new);
    	
    } else if(Trigger.isUpdate && Trigger.isBefore){
        handler.OnBeforeUpdate(Trigger.old, Trigger.new, Trigger.oldMap, Trigger.newMap);
        
    }
}

handler.addObserver()で追加した順番で処理を実行するようにしているので、トリガに組み込みたい処理の実行順序をある程度は制御できるかなーと。
あとは、gistの方にも書いてますが、カスタム設定とかに実行させたいApexクラス名なんかを保持しておいて、Apexコード外から制御できるようにしてあげると、なお良いかな?と思います。(特に管理パッケージとか)

トリガに組み込みたい処理を、分かりやすい程度に(+各クラスが他と依存しないよう独立している程度に)分割してあげて、handler.addObserver()でガシガシ追加していってあげれば、トリガハンドラ側の処理の肥大化は防げるし、後で追加するのも多少はラクになるかなぁ...と思っているところです。。


要改善....

handler.addObserver()で、沢山の実装クラスを追加していくと....各実装クラス内で

for(Account acc : newAccounts){
    // xxxxxxx
}

と、for文によるループが実行されるので、処理の効率性という観点からは不利ですね。。
何か良い案ないかなーと妄想しているところです。


終わりに

冒頭の方でも書きましたが、まだ考えたばっかりのハナシなので、もう少し煮詰めて熟成させていきたいなーと思います。
というところで、最後にこの記事を紹介して終わりにしたいと思います。

http://advancedapex.com/2013/07/26/interesting-trigger-framework/
http://krishhari.wordpress.com/2013/07/22/an-architecture-framework-to-handle-triggers-in-the-force-com-platform/

もっとすごいこと考えてる人が沢山いるのでした....ギャフン orz