SlideShare a Scribd company logo
1 of 32
#sfdg
Apex Triggerの
ベストプラクティスを目指して(進捗作成中)
[Tokyo] Salesforce DG Meetup #18
Takahiro Yonei (@yonet77)
#sfdg
 米井 孝浩(よねい たかひろ)
 TAOドライブ株式会社 エンジニア
 Salesforce向けの受託開発をメインにしてます
 (最近は、Herokuも少し...)
 Salesforce DG (Tokyo) の運営メンバの1人
 (いちおう)Salesforce MVP (Spring’ 15)
#sfdg
1. Apexトリガについて(ちょっとおさらいなど)
2. より良いApexトリガの実装を目指して (1)
3. より良いApexトリガの実装を目指して (2)
#sfdg
#sfdg
 Salesforceレコードへの変更前後にカスタムロジックを実行する機能
 以下の操作の前後に実行することが可能
• insert
• update
• delete
• merge
• upsert
• undelete
 データベースのトリガと大体似たような感じ
-> Salesforceでは、それがApexで記述できるのが良い
#sfdg
 だいたいこんな感じ
trigger ContextExampleTrigger on Account (before insert, after insert, after delete) {
if (Trigger.isInsert) {
if (Trigger.isBefore) {
// Before Insert で何か処理したいことなど
} else if (Trigger.isAfter) {
// After Insert で何か処理したいことなど
}
} else if (Trigger.isDelete) {
// After Delete で何か処理したいことなど
}
}
#sfdg
 実装上、注意する点など
 トリガの実行順序
1. 元のレコードがロード、または初期化される
2. 新しいレコードのフィールド値がロードされ、古い値を上書きする
3. 全てのbeforeトリガが実行される
4. カスタム検証ルールを含むシステム検証が行われる
5. 重複ルールが実行される
6. レコードはデータベースに保存されるが、コミットはされない
7. すべてのafterトリガが実行される
8. 割り当てルールが実行される
9. 自動応答ルールが実行される
10. ワークフロールールが実行される
11. ワークフローフィールドが更新されたら、レコードがリロードされる
12. 10の処理をうけて、beforeトリガとafterトリガを再度1度だけ実行する
13. エスカレーションルールが実行される
14. レコードが積み上げ集計項目をもっていたりクロスオブジェクトワークフローの一部である場合は、
親レコードの該当項目の値も更新する
15. すべてのDML操作がデータベースにコミットされる
16. 電子メールの送信など、コミット後のロジックが実行される
入力規則の前に
beforeトリガが実行される
ワークフロールールは
afterトリガの後に実行される
#sfdg
 実装上、注意する点など
 1つのオブジェクトに複数のトリガが定義されている場合、
トリガの実行順序は制御できない
• 例えば、sandbox環境での順序と、運用環境での順序は異なる
• 1オブジェクト -> 1トリガ が基本
 トリガを呼び出さない操作には以下がある(詳細は公式ドキュメントを)
• 削除のカスケード
• マージ操作の結果として親が変更される子レコードの更新カスケード
• キャンペーン状況、住所の一括変更
• 選択リストの名前変更 or 置換 ...etc
#sfdg
 実装上、注意する点など
 一括操作を前提としてロジックを組むことが必須
• コレクション(Listなど)にレコードを追加し、それに対してDMLを実行
することで、DMLステートメント数を最小限にする
• レコードを事前処理してコレクションを生成し、SOQLステートメントに
組み込むことで、SOQLステートメント数を最小限にする etc
#sfdg
#sfdg
 Apexトリガテンプレートを利用する
 A Simple Trigger Template for Salesforce を参考にしてみる
 このテンプレートは、以下の課題をクリアすることを目指したもの
• バルク処理が考慮されたテンプレートになってない
• 7つのBoolean型のコンテキスト変数が、可読性とメンテナンス性の維持を
妨げている
• 特定のコンテキストでは、Trigger.old, Trigger.new が使えないのを忘れがち
• 非同期処理は、毎回自前で考慮する必要がある
#sfdg
 トリガ側のサンプル(取引先を利用)
trigger AccountTrigger on Account (before insert, before update, ...(その他イベント)) {
if( AccountTriggerHandler.hasExecuted ){
return; // prevent recursive re-entry
}
AccountTriggerHandler.hasExecuted= true;
AccountTriggerHandler handler = new AccountTriggerHandler(Trigger.isExecuting, Trigger.size);
if(Trigger.isInsert && Trigger.isBefore){
handler.OnBeforeInsert(Trigger.new);
} else if(Trigger.isInsert && Trigger.isAfter){
handler.OnAfterInsert(Trigger.new);
AccountTriggerHandler.OnAfterInsertAsync(Trigger.newMap.keySet());
} else if(Trigger.isUpdate && Trigger.isBefore){
handler.OnBeforeUpdate(Trigger.old, Trigger.new, Trigger.newMap);
} ...
}
各イベントでの処理を、
ハンドラ側で実装する
非同期用の処理も、
ハンドラ側で実装する
トリガハンドラを用意する
#sfdg
 トリガハンドラのサンプル
public with sharing class AccountTriggerHandler {
public static boolean hasExecuted = false;
private boolean m_isExecuting = false;
private integer BatchSize = 0;
public AccountTriggerHandler(boolean isExecuting, integer size){
m_isExecuting = isExecuting;
BatchSize = size;
}
public void OnBeforeInsert(Account[] newAccounts){ // 何かの処理 }
public void OnAfterInsert(Account[] newAccounts){ // 何かの処理 }
@future public static void OnAfterInsertAsync(Set<ID> newAccountIDs){
// 何かの非同期処理
List<Account> newAccounts = [select Id, Name from Account where Id IN :newAccountIDs];
}
...
}
トリガで発生したイベントからの
処理を実装する
#sfdg
 トリガハンドラ側の実装
public with sharing class AccountTriggerHandler {
public static boolean hasExecuted = false;
private boolean m_isExecuting = false;
private integer BatchSize = 0;
public AccountTriggerHandler(boolean isExecuting, integer size){
m_isExecuting = isExecuting;
BatchSize = size;
}
...
}
再帰処理防止のためのstatic変数
#sfdg
 トリガ側の実装
trigger AccountTrigger on Account (before insert, before update, ...(その他イベント)) {
if( AccountTriggerHandler.hasExecuted ){
return; // prevent recursive re-entry
}
AccountTriggerHandler.hasExecuted= true;
AccountTriggerHandler handler = new AccountTriggerHandler(Trigger.isExecuting, Trigger.size);
if(Trigger.isInsert && Trigger.isBefore){
handler.OnBeforeInsert(Trigger.new);
} else if(Trigger.isInsert && Trigger.isAfter){
handler.OnAfterInsert(Trigger.new);
AccountTriggerHandler.OnAfterInsertAsync(Trigger.newMap.keySet());
} else if(Trigger.isUpdate && Trigger.isBefore){
handler.OnBeforeUpdate(Trigger.old, Trigger.new, Trigger.newMap);
} ...
}
①トリガ実行時にstatic変数のフラグをtrueにする
②再帰時にはトリガをスキップさせる
#sfdg
 トリガハンドラのサンプル
public with sharing class AccountTriggerHandler {
...
public void OnBeforeInsert(Account[] newAccounts) {
// 何かの処理
}
@future public static void OnAfterInsertAsync(Set<ID> newAccountIDs){
// 何かの非同期処理
List<Account> newAccounts = [select Id, Name from Account where Id IN :newAccountIDs];
}
public void OnBeforeUpdate(Account[] oldAccounts, Account[] updatedAccounts, Map<ID, Account> accountMap){
//Example Map usage
Map<ID, Contact> contacts = new Map<ID, Contact>([select Id, FirstName, LastName, Email
from Contact where AccountId IN :accountMap.keySet()]);
}
}
• トリガハンドラにて各イベントに
応じた処理を実装する
• トリガ内で利用できる変数を考慮して、
引数を決めておく
#sfdg
 トリガ側の実装
trigger AccountTrigger on Account (before insert, before update, ...(その他イベント)) {
...
AccountTriggerHandler.hasExecuted= true;
AccountTriggerHandler handler = new AccountTriggerHandler(Trigger.isExecuting, Trigger.size);
if(Trigger.isInsert && Trigger.isBefore){
handler.OnBeforeInsert(Trigger.new);
} else if(Trigger.isInsert && Trigger.isAfter){
handler.OnAfterInsert(Trigger.new);
AccountTriggerHandler.OnAfterInsertAsync(Trigger.newMap.keySet());
} else if(Trigger.isUpdate && Trigger.isBefore){
handler.OnBeforeUpdate(Trigger.old, Trigger.new, Trigger.newMap);
} ...
}
各イベントに対応するトリガハンドラ側の
メソッドを呼び出す
#sfdg
 テンプレート導入によるメリット
 同じところはコピー&ペーストで済ませられる
 ロジックの実装によりフォーカスできる
#sfdg
#sfdg
 前述のトリガテンプレート含め、「トリガハンドラ用のApexクラスを実装して
処理を移譲する」が推奨パターンとして広く周知されてきた
 しかし、トリガでの実装がどんどん増えてくると、トリガハンドラも巨大な
Apexクラスとなり、メンテナンスが困難に...?
 1つのApexクラスで対応するのが困難であれば、Apexクラスを分割してはどう
か?
 トリガでの実装で、機能単位でApexクラスを分割して、各Apexクラスで
1つのことをうまくやるように考えてみる
※次ページ以降のソースコードは
https://github.com/takahiro-yonei/ApexTriggerForMeetup18 で公開してます
#sfdg
 トリガのサンプル
trigger AccountTrigger on Account (before insert, before update, after insert) {
AccountTriggerHandler handler = new AccountTriggerHandler(Trigger.isExecuting, Trigger.size);
handler.addObserver(new TriggerOperation[]{TriggerOperation.BEFORE_INSERT, TriggerOperation.BEFORE_UPDATE},
new AccountTriggerHandler_Validation());
handler.addObserver(new TriggerOperation[]{TriggerOperation.AFTER_INSERT},
new AccountTriggerHandler_FeedToXXX());
switch on Trigger.operationType {
when BEFORE_INSERT {
handler.onBeforeInsert(Trigger.new);
}
when BEFORE_UPDATE {
handler.onBeforeUpdate(Trigger.old, Trigger.new, Trigger.oldMap, Trigger.newMap);
}
when AFTER_INSERT {
handler.onAfterInsert(Trigger.new);
}
}
}
• トリガで処理したい内容を、適度な大きさで
分割する
• 分割したApexクラスを、処理したい順番でト
リガハンドラに保持させる
#sfdg
 分割したApexクラス側の実装 (1)
public without sharing class AccountTriggerHandler_Validation
implements BaseTriggerHandler.ObserverTrgBeforeInsert, BaseTriggerHandler.ObserverTrgBeforeUpdate {
public AccountTriggerHandler_Validation(){}
public List<SObject> onBeforeInsert(List<SObject> newAccounts){
for(Account acc : (List<Account>)newAccounts){
// Some Validation Process...etc
System.debug('onBeforeInsert: ' + acc);
}
return newAccounts;
}
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
System.debug('onBeforeUpdate: ' + acc);
}
return updAccounts;
}
}
• Before Insert, Before Updateの
時だけ反応する
#sfdg
 分割したApexクラス側の実装 (2)
public without sharing class AccountTriggerHandler_FeedToXXX
implements BaseTriggerHandler.ObserverTrgAfterInsert {
public AccountTriggerHandler_FeedToXXX() {}
public void onAfterInsert(List<SObject> newAccounts){
for(Account acc : (List<Account>)newAccounts){
// Some Process
System.debug('onAfterInsert: ' + acc);
}
}
}
• After Insertの時だけ反応する
#sfdg
 中の仕組みについて (0)
 登場するApexクラス
BaseTriggerHandler
Account
TriggerHandler
Account
TriggerHandler_XXX
• 実際にトリガ処理を実装するところ
呼び出す
インタフェースを
実装
継承
Account
Trigger
#sfdg
 中の仕組みについて (1)
public without sharing class AccountTriggerHandler extends BaseTriggerHandler {
private static Boolean InProcess = false;
public AccountTriggerHandler(boolean pIsExecuting, Integer pSize){
super(pIsExecuting, pSize);
}
public void onBeforeInsert(Account[] newAccounts){
for(BaseTriggerHandler.ObserverTrgBeforeInsert observer : beforeInsertObservers){
newAccounts = (List<Account>)observer.onBeforeInsert(newAccounts);
}
}
public void onAfterInsert(Account[] newAccounts){
for(BaseTriggerHandler.ObserverTrgAfterInsert observer : afterInsertObservers){
observer.onAfterInsert(newAccounts);
}
}
...
• 各イベントに応じたインタフェースを実装した
Apexクラスのメソッドを順次実行していく
#sfdg
 中の仕組みについて (2)
public virtual class BaseTriggerHandler {
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);
}
public interface ObserverTrgBeforeDelete extends ObserverTrg {
List<SObject> onBeforeDelete(List<SObject> delObjects);
}
public interface ObserverTrgAfterInsert extends ObserverTrg {
void onAfterInsert(List<SObject> newObjects);
}
public interface ObserverTrgAfterUpdate extends ObserverTrg {
void onAfterUpdate(List<SObject> oldObjects, List<SObject> newObjects, Map<Id, SObject> oldObjectMap,
Map<Id, SObject> newObjectMap);
}
public interface ObserverTrgAfterDelete extends ObserverTrg {
void onAfterDelete(List<SObject> delObjects);
}
• トリガイベントに応じたインタフェースを
定義しておく
• 各種イベントで処理したい内容を持つApex
クラスを用意し、そのイベント用のインタ
フェースを実装する
#sfdg
 中の仕組みについて (3)
public virtual class BaseTriggerHandler {
...
protected Boolean IsExecuting = false;
protected Integer BatchSize = 0;
protected List<ObserverTrgBeforeInsert> beforeInsertObservers;
protected List<ObserverTrgBeforeUpdate> beforeUpdateObservers;
protected List<ObserverTrgBeforeDelete> beforeDeleteObservers;
protected List<ObserverTrgAfterInsert> afterInsertObservers;
protected List<ObserverTrgAfterUpdate> afterUpdateObservers;
protected List<ObserverTrgAfterDelete> afterDeleteObservers;
public BaseTriggerHandler(boolean param_IsExecuting, Integer param_Size){
this.IsExecuting = param_IsExecuting;
this.BatchSize = param_Size;
beforeInsertObservers = new List<ObserverTrgBeforeInsert>();
beforeUpdateObservers = new List<ObserverTrgBeforeUpdate>();
beforeDeleteObservers = new List<ObserverTrgBeforeDelete>();
afterInsertObservers = new List<ObserverTrgAfterInsert>();
afterUpdateObservers = new List<ObserverTrgAfterUpdate>();
afterDeleteObservers = new List<ObserverTrgAfterDelete>();
}
• 各イベントで処理させるApexクラスを格
納するためのリスト
• 各イベントでは、このリストにあるApex
クラスを順次処理していく
#sfdg
 中の仕組みについて (4)
public virtual class BaseTriggerHandler {
...
public void addObserver(List<System.TriggerOperation> points, ObserverTrg ob){
for(System.TriggerOperation o : points){
switch on o {
when BEFORE_INSERT {
beforeInsertObservers.add((ObserverTrgBeforeInsert)ob);
}
when BEFORE_UPDATE {
beforeUpdateObservers.add((ObserverTrgBeforeUpdate)ob);
}
when BEFORE_DELETE {
beforeDeleteObservers.add((ObserverTrgBeforeDelete)ob);
}
when AFTER_INSERT {
afterInsertObservers.add((ObserverTrgAfterInsert)ob);
}
when AFTER_UPDATE {
afterUpdateObservers.add((ObserverTrgAfterUpdate)ob);
}
...(略)
when else {}
}
}
}
• 各イベントに応じたインタフェースを実
装したApexクラスをリストに追加する
• トリガ起動時に、Apexクラスのインスタ
ンスを、リストに追加していく
#sfdg
trigger AccountTrigger on Account (before insert, before update, after insert) {
AccountTriggerHandler handler = new AccountTriggerHandler(Trigger.isExecuting, Trigger.size);
handler.addObserver(new TriggerOperation[]{TriggerOperation.BEFORE_INSERT, TriggerOperation.BEFORE_UPDATE},
new AccountTriggerHandler_Validation());
handler.addObserver(new TriggerOperation[]{TriggerOperation.AFTER_INSERT},
new AccountTriggerHandler_FeedToXXX());
switch on Trigger.operationType {
when BEFORE_INSERT {
handler.onBeforeInsert(Trigger.new);
}
when BEFORE_UPDATE {
handler.onBeforeUpdate(Trigger.old, Trigger.new, Trigger.oldMap, Trigger.newMap);
}
when AFTER_INSERT {
handler.onAfterInsert(Trigger.new);
}
}
}
• 分割したApexクラスを、処理したい順番でト
リガハンドラに保持させる
 中の仕組みについて (5)
#sfdg
 メリット vs デメリット
 メリット
 各Apexクラスの責務が明確になって、メンテナンス性は維持できる
 各Apexクラスの処理順序も制御できる
 デメリット
 分割するApexクラスの粒度
• あんまり細かく分けてもApexクラスが大量に作成されて、メンテナン
ス性が落ちるかもしれない
• チーム内で、分割する基準を共有しておく必要がある
 トリガ処理の効率
• 分割したApexクラスごとにfor-loopが実行されるので、トリガハンドラ
が単体の場合よりも処理効率は落ちる
• (まだあまり大きな問題になったことはないけど...)
#sfdg
 Apexトリガは非常に有用だが、毎回ゼロから実装するのは手間がかかるし、ト
リガ本体に全て詰め込むとメンテナス困難になる、という経緯からテンプレー
トが考案され、広まった(と思う)
 テンプレートを用意することで、重要なロジックの実装により集中できる
 トリガハンドラに移譲する仕組みは良いが、継続的な改善でトリガハンドラ
が巨大になってメンテナンス困難になる可能性もある
 トリガハンドラに移譲する仕組みを発展させて、トリガハンドラを分割するこ
とを検討してみた
 巨大になりがちなトリガハンドラの可読性とメンテナンス性を維持する上で
は有効(と思う)
 今後の発展として、プロセスビルダーとApexトリガはどこまで共存できるか?
について検討してみたい(※気が向いたら)
#sfdg

More Related Content

What's hot

[B31] LOGMinerってレプリケーションソフトで使われているけどどうなってる? by Toshiya Morita
[B31] LOGMinerってレプリケーションソフトで使われているけどどうなってる? by Toshiya Morita[B31] LOGMinerってレプリケーションソフトで使われているけどどうなってる? by Toshiya Morita
[B31] LOGMinerってレプリケーションソフトで使われているけどどうなってる? by Toshiya MoritaInsight Technology, Inc.
 
Ganglia のUIにGrafanaを追加する話
Ganglia のUIにGrafanaを追加する話Ganglia のUIにGrafanaを追加する話
Ganglia のUIにGrafanaを追加する話KLab Inc. / Tech
 
Laravelでfacadeを使わない開発
Laravelでfacadeを使わない開発Laravelでfacadeを使わない開発
Laravelでfacadeを使わない開発Kenjiro Kubota
 
Gocon2017:Goのロギング周りの考察
Gocon2017:Goのロギング周りの考察Gocon2017:Goのロギング周りの考察
Gocon2017:Goのロギング周りの考察貴仁 大和屋
 
楽天プロジェクトX:基幹DB移設 編
楽天プロジェクトX:基幹DB移設 編楽天プロジェクトX:基幹DB移設 編
楽天プロジェクトX:基幹DB移設 編Rakuten Group, Inc.
 
Keycloak拡張入門
Keycloak拡張入門Keycloak拡張入門
Keycloak拡張入門Hiroyuki Wada
 
並行処理初心者のためのAkka入門
並行処理初心者のためのAkka入門並行処理初心者のためのAkka入門
並行処理初心者のためのAkka入門Yoshimura Soichiro
 
ログの書き方がチームの生産性を爆上げする話
ログの書き方がチームの生産性を爆上げする話ログの書き方がチームの生産性を爆上げする話
ログの書き方がチームの生産性を爆上げする話Tsuyoshi Ushio
 
Keycloak入門-OpenID ConnectによるAPIセキュリティ
Keycloak入門-OpenID ConnectによるAPIセキュリティKeycloak入門-OpenID ConnectによるAPIセキュリティ
Keycloak入門-OpenID ConnectによるAPIセキュリティYuichi Nakamura
 
LIFULL HOME'SでのSolrの構成と運用の変遷
LIFULL HOME'SでのSolrの構成と運用の変遷LIFULL HOME'SでのSolrの構成と運用の変遷
LIFULL HOME'SでのSolrの構成と運用の変遷LIFULL Co., Ltd.
 
サーバーサイド Kotlin のテストフレームワーク事情
サーバーサイド Kotlin のテストフレームワーク事情サーバーサイド Kotlin のテストフレームワーク事情
サーバーサイド Kotlin のテストフレームワーク事情Shinya Mochida
 
メルカリ・ソウゾウでは どうGoを活用しているのか?
メルカリ・ソウゾウでは どうGoを活用しているのか?メルカリ・ソウゾウでは どうGoを活用しているのか?
メルカリ・ソウゾウでは どうGoを活用しているのか?Takuya Ueda
 
Salesforceの標準オブジェクトについて復習してみた
Salesforceの標準オブジェクトについて復習してみたSalesforceの標準オブジェクトについて復習してみた
Salesforceの標準オブジェクトについて復習してみたy-maeda
 
「書ける」から「できる」になれる! ~Javaメモリ節約ノウハウ話~
「書ける」から「できる」になれる! ~Javaメモリ節約ノウハウ話~「書ける」から「できる」になれる! ~Javaメモリ節約ノウハウ話~
「書ける」から「できる」になれる! ~Javaメモリ節約ノウハウ話~JustSystems Corporation
 
Swaggerでのapi開発よもやま話
Swaggerでのapi開発よもやま話Swaggerでのapi開発よもやま話
Swaggerでのapi開発よもやま話KEISUKE KONISHI
 
最近のやられアプリを試してみた
最近のやられアプリを試してみた最近のやられアプリを試してみた
最近のやられアプリを試してみたzaki4649
 
Jakarta EE 9 と これから
Jakarta EE 9 と これからJakarta EE 9 と これから
Jakarta EE 9 と これからKenji Kazumura
 
【修正版】Django + SQLAlchemy: シンプルWay
【修正版】Django + SQLAlchemy: シンプルWay【修正版】Django + SQLAlchemy: シンプルWay
【修正版】Django + SQLAlchemy: シンプルWayTakayuki Shimizukawa
 
SalesforceにおけるCDC(変更データキャプチャ)の実装・活用法について
SalesforceにおけるCDC(変更データキャプチャ)の実装・活用法についてSalesforceにおけるCDC(変更データキャプチャ)の実装・活用法について
SalesforceにおけるCDC(変更データキャプチャ)の実装・活用法についてTakashi Hatamoto
 

What's hot (20)

[B31] LOGMinerってレプリケーションソフトで使われているけどどうなってる? by Toshiya Morita
[B31] LOGMinerってレプリケーションソフトで使われているけどどうなってる? by Toshiya Morita[B31] LOGMinerってレプリケーションソフトで使われているけどどうなってる? by Toshiya Morita
[B31] LOGMinerってレプリケーションソフトで使われているけどどうなってる? by Toshiya Morita
 
Ganglia のUIにGrafanaを追加する話
Ganglia のUIにGrafanaを追加する話Ganglia のUIにGrafanaを追加する話
Ganglia のUIにGrafanaを追加する話
 
Laravelでfacadeを使わない開発
Laravelでfacadeを使わない開発Laravelでfacadeを使わない開発
Laravelでfacadeを使わない開発
 
Gocon2017:Goのロギング周りの考察
Gocon2017:Goのロギング周りの考察Gocon2017:Goのロギング周りの考察
Gocon2017:Goのロギング周りの考察
 
楽天プロジェクトX:基幹DB移設 編
楽天プロジェクトX:基幹DB移設 編楽天プロジェクトX:基幹DB移設 編
楽天プロジェクトX:基幹DB移設 編
 
Keycloak拡張入門
Keycloak拡張入門Keycloak拡張入門
Keycloak拡張入門
 
並行処理初心者のためのAkka入門
並行処理初心者のためのAkka入門並行処理初心者のためのAkka入門
並行処理初心者のためのAkka入門
 
ログの書き方がチームの生産性を爆上げする話
ログの書き方がチームの生産性を爆上げする話ログの書き方がチームの生産性を爆上げする話
ログの書き方がチームの生産性を爆上げする話
 
Oracle GoldenGate アーキテクチャと基本機能
Oracle GoldenGate アーキテクチャと基本機能Oracle GoldenGate アーキテクチャと基本機能
Oracle GoldenGate アーキテクチャと基本機能
 
Keycloak入門-OpenID ConnectによるAPIセキュリティ
Keycloak入門-OpenID ConnectによるAPIセキュリティKeycloak入門-OpenID ConnectによるAPIセキュリティ
Keycloak入門-OpenID ConnectによるAPIセキュリティ
 
LIFULL HOME'SでのSolrの構成と運用の変遷
LIFULL HOME'SでのSolrの構成と運用の変遷LIFULL HOME'SでのSolrの構成と運用の変遷
LIFULL HOME'SでのSolrの構成と運用の変遷
 
サーバーサイド Kotlin のテストフレームワーク事情
サーバーサイド Kotlin のテストフレームワーク事情サーバーサイド Kotlin のテストフレームワーク事情
サーバーサイド Kotlin のテストフレームワーク事情
 
メルカリ・ソウゾウでは どうGoを活用しているのか?
メルカリ・ソウゾウでは どうGoを活用しているのか?メルカリ・ソウゾウでは どうGoを活用しているのか?
メルカリ・ソウゾウでは どうGoを活用しているのか?
 
Salesforceの標準オブジェクトについて復習してみた
Salesforceの標準オブジェクトについて復習してみたSalesforceの標準オブジェクトについて復習してみた
Salesforceの標準オブジェクトについて復習してみた
 
「書ける」から「できる」になれる! ~Javaメモリ節約ノウハウ話~
「書ける」から「できる」になれる! ~Javaメモリ節約ノウハウ話~「書ける」から「できる」になれる! ~Javaメモリ節約ノウハウ話~
「書ける」から「できる」になれる! ~Javaメモリ節約ノウハウ話~
 
Swaggerでのapi開発よもやま話
Swaggerでのapi開発よもやま話Swaggerでのapi開発よもやま話
Swaggerでのapi開発よもやま話
 
最近のやられアプリを試してみた
最近のやられアプリを試してみた最近のやられアプリを試してみた
最近のやられアプリを試してみた
 
Jakarta EE 9 と これから
Jakarta EE 9 と これからJakarta EE 9 と これから
Jakarta EE 9 と これから
 
【修正版】Django + SQLAlchemy: シンプルWay
【修正版】Django + SQLAlchemy: シンプルWay【修正版】Django + SQLAlchemy: シンプルWay
【修正版】Django + SQLAlchemy: シンプルWay
 
SalesforceにおけるCDC(変更データキャプチャ)の実装・活用法について
SalesforceにおけるCDC(変更データキャプチャ)の実装・活用法についてSalesforceにおけるCDC(変更データキャプチャ)の実装・活用法について
SalesforceにおけるCDC(変更データキャプチャ)の実装・活用法について
 

Similar to ApexトリガのBest Practiceを目指して

Twitter sphere of #twitter4j #twtr_hack
Twitter sphere of #twitter4j #twtr_hackTwitter sphere of #twitter4j #twtr_hack
Twitter sphere of #twitter4j #twtr_hackkimukou_26 Kimukou
 
Metaprogramming Universe in C# - 実例に見るILからRoslynまでの活用例
Metaprogramming Universe in C# - 実例に見るILからRoslynまでの活用例Metaprogramming Universe in C# - 実例に見るILからRoslynまでの活用例
Metaprogramming Universe in C# - 実例に見るILからRoslynまでの活用例Yoshifumi Kawai
 
Neo4j の「データ操作プログラミング」から 「ビジュアライズ」まで
Neo4j の「データ操作プログラミング」から 「ビジュアライズ」までNeo4j の「データ操作プログラミング」から 「ビジュアライズ」まで
Neo4j の「データ操作プログラミング」から 「ビジュアライズ」までKeiichiro Seida
 
PHP 2大 web フレームワークの徹底比較!
PHP 2大 web フレームワークの徹底比較!PHP 2大 web フレームワークの徹底比較!
PHP 2大 web フレームワークの徹底比較!Shohei Okada
 
ReduxとSwiftの組み合わせ:改訂版
ReduxとSwiftの組み合わせ:改訂版ReduxとSwiftの組み合わせ:改訂版
ReduxとSwiftの組み合わせ:改訂版Fumiya Sakai
 
StackStormを活用した運用自動化の実践
StackStormを活用した運用自動化の実践StackStormを活用した運用自動化の実践
StackStormを活用した運用自動化の実践Shu Sugimoto
 
エンタープライズ分野での実践AngularJS
エンタープライズ分野での実践AngularJSエンタープライズ分野での実践AngularJS
エンタープライズ分野での実践AngularJSAyumi Goto
 
Salesforce DUG Japan Meetup#9(REST API, Metadata API etc)
Salesforce DUG Japan Meetup#9(REST API, Metadata API etc)Salesforce DUG Japan Meetup#9(REST API, Metadata API etc)
Salesforce DUG Japan Meetup#9(REST API, Metadata API etc)Takahiro Yonei
 
ログにまつわるエトセトラ
ログにまつわるエトセトラログにまつわるエトセトラ
ログにまつわるエトセトラ菊池 佑太
 
Replace Output Iterator and Extend Range JP
Replace Output Iterator and Extend Range JPReplace Output Iterator and Extend Range JP
Replace Output Iterator and Extend Range JPAkira Takahashi
 
データマイニング+WEB勉強会資料第6回
データマイニング+WEB勉強会資料第6回データマイニング+WEB勉強会資料第6回
データマイニング+WEB勉強会資料第6回Naoyuki Yamada
 
Azure で Serverless 初心者向けタッチ&トライ
Azure で Serverless 初心者向けタッチ&トライAzure で Serverless 初心者向けタッチ&トライ
Azure で Serverless 初心者向けタッチ&トライMasanobu Sato
 
React+TypeScriptもいいぞ
React+TypeScriptもいいぞReact+TypeScriptもいいぞ
React+TypeScriptもいいぞMitsuru Ogawa
 

Similar to ApexトリガのBest Practiceを目指して (20)

Twitter sphere of #twitter4j #twtr_hack
Twitter sphere of #twitter4j #twtr_hackTwitter sphere of #twitter4j #twtr_hack
Twitter sphere of #twitter4j #twtr_hack
 
Metaprogramming Universe in C# - 実例に見るILからRoslynまでの活用例
Metaprogramming Universe in C# - 実例に見るILからRoslynまでの活用例Metaprogramming Universe in C# - 実例に見るILからRoslynまでの活用例
Metaprogramming Universe in C# - 実例に見るILからRoslynまでの活用例
 
Neo4j の「データ操作プログラミング」から 「ビジュアライズ」まで
Neo4j の「データ操作プログラミング」から 「ビジュアライズ」までNeo4j の「データ操作プログラミング」から 「ビジュアライズ」まで
Neo4j の「データ操作プログラミング」から 「ビジュアライズ」まで
 
PHP 2大 web フレームワークの徹底比較!
PHP 2大 web フレームワークの徹底比較!PHP 2大 web フレームワークの徹底比較!
PHP 2大 web フレームワークの徹底比較!
 
Apexデザインパターン
ApexデザインパターンApexデザインパターン
Apexデザインパターン
 
ReduxとSwiftの組み合わせ:改訂版
ReduxとSwiftの組み合わせ:改訂版ReduxとSwiftの組み合わせ:改訂版
ReduxとSwiftの組み合わせ:改訂版
 
Dotnetconf2017
Dotnetconf2017Dotnetconf2017
Dotnetconf2017
 
StackStormを活用した運用自動化の実践
StackStormを活用した運用自動化の実践StackStormを活用した運用自動化の実践
StackStormを活用した運用自動化の実践
 
エンタープライズ分野での実践AngularJS
エンタープライズ分野での実践AngularJSエンタープライズ分野での実践AngularJS
エンタープライズ分野での実践AngularJS
 
Salesforce DUG Japan Meetup#9(REST API, Metadata API etc)
Salesforce DUG Japan Meetup#9(REST API, Metadata API etc)Salesforce DUG Japan Meetup#9(REST API, Metadata API etc)
Salesforce DUG Japan Meetup#9(REST API, Metadata API etc)
 
Ajax 応用
Ajax 応用Ajax 応用
Ajax 応用
 
ログにまつわるエトセトラ
ログにまつわるエトセトラログにまつわるエトセトラ
ログにまつわるエトセトラ
 
JavaScript 実践講座 Framework, Tool, Performance
JavaScript 実践講座 Framework, Tool, PerformanceJavaScript 実践講座 Framework, Tool, Performance
JavaScript 実践講座 Framework, Tool, Performance
 
Heroku Postgres
Heroku PostgresHeroku Postgres
Heroku Postgres
 
Heroku Postgres
Heroku PostgresHeroku Postgres
Heroku Postgres
 
Replace Output Iterator and Extend Range JP
Replace Output Iterator and Extend Range JPReplace Output Iterator and Extend Range JP
Replace Output Iterator and Extend Range JP
 
データマイニング+WEB勉強会資料第6回
データマイニング+WEB勉強会資料第6回データマイニング+WEB勉強会資料第6回
データマイニング+WEB勉強会資料第6回
 
Azure で Serverless 初心者向けタッチ&トライ
Azure で Serverless 初心者向けタッチ&トライAzure で Serverless 初心者向けタッチ&トライ
Azure で Serverless 初心者向けタッチ&トライ
 
Das 2015
Das 2015Das 2015
Das 2015
 
React+TypeScriptもいいぞ
React+TypeScriptもいいぞReact+TypeScriptもいいぞ
React+TypeScriptもいいぞ
 

More from Takahiro Yonei

SalesforceとHerokuのより良い関係を目指して(たぶん序章)
SalesforceとHerokuのより良い関係を目指して(たぶん序章)SalesforceとHerokuのより良い関係を目指して(たぶん序章)
SalesforceとHerokuのより良い関係を目指して(たぶん序章)Takahiro Yonei
 
HerokuとSalesforceで例えばこんなCMSでも (LT資料)
HerokuとSalesforceで例えばこんなCMSでも (LT資料)HerokuとSalesforceで例えばこんなCMSでも (LT資料)
HerokuとSalesforceで例えばこんなCMSでも (LT資料)Takahiro Yonei
 
EC-CubeをHerokuでも
EC-CubeをHerokuでもEC-CubeをHerokuでも
EC-CubeをHerokuでもTakahiro Yonei
 
Tokyo SFDG Meetup#16 / Release Note, Einstein Platform Service
Tokyo SFDG Meetup#16 / Release Note, Einstein Platform ServiceTokyo SFDG Meetup#16 / Release Note, Einstein Platform Service
Tokyo SFDG Meetup#16 / Release Note, Einstein Platform ServiceTakahiro Yonei
 
Meetup #15 : リリースノート輪読 / Apexまわり
Meetup #15 : リリースノート輪読 / ApexまわりMeetup #15 : リリースノート輪読 / Apexまわり
Meetup #15 : リリースノート輪読 / ApexまわりTakahiro Yonei
 
カスタムメタデータを受託の案件で使ってみた話
カスタムメタデータを受託の案件で使ってみた話カスタムメタデータを受託の案件で使ってみた話
カスタムメタデータを受託の案件で使ってみた話Takahiro Yonei
 
Visualforceをあきらめない
VisualforceをあきらめないVisualforceをあきらめない
VisualforceをあきらめないTakahiro Yonei
 
Salesforce dug tokyo_meetup#8_about_releasenote
Salesforce dug tokyo_meetup#8_about_releasenoteSalesforce dug tokyo_meetup#8_about_releasenote
Salesforce dug tokyo_meetup#8_about_releasenoteTakahiro Yonei
 
SDUG Tokyo Meetup#7 About ReleaseNote
SDUG Tokyo Meetup#7 About ReleaseNoteSDUG Tokyo Meetup#7 About ReleaseNote
SDUG Tokyo Meetup#7 About ReleaseNoteTakahiro Yonei
 
DCMax CrowdHackathonチャレンジ②
DCMax CrowdHackathonチャレンジ②DCMax CrowdHackathonチャレンジ②
DCMax CrowdHackathonチャレンジ②Takahiro Yonei
 
Salesforce DUG Tokyo meetup#5
Salesforce DUG Tokyo meetup#5Salesforce DUG Tokyo meetup#5
Salesforce DUG Tokyo meetup#5Takahiro Yonei
 
Force.com Developer Group Japan Meetup#2
Force.com Developer Group Japan Meetup#2Force.com Developer Group Japan Meetup#2
Force.com Developer Group Japan Meetup#2Takahiro Yonei
 
Force.com Developer Group Japan Meetup#1
Force.com Developer Group Japan Meetup#1Force.com Developer Group Japan Meetup#1
Force.com Developer Group Japan Meetup#1Takahiro Yonei
 

More from Takahiro Yonei (14)

SalesforceとHerokuのより良い関係を目指して(たぶん序章)
SalesforceとHerokuのより良い関係を目指して(たぶん序章)SalesforceとHerokuのより良い関係を目指して(たぶん序章)
SalesforceとHerokuのより良い関係を目指して(たぶん序章)
 
HerokuとSalesforceで例えばこんなCMSでも (LT資料)
HerokuとSalesforceで例えばこんなCMSでも (LT資料)HerokuとSalesforceで例えばこんなCMSでも (LT資料)
HerokuとSalesforceで例えばこんなCMSでも (LT資料)
 
EC-CubeをHerokuでも
EC-CubeをHerokuでもEC-CubeをHerokuでも
EC-CubeをHerokuでも
 
Tokyo SFDG Meetup#16 / Release Note, Einstein Platform Service
Tokyo SFDG Meetup#16 / Release Note, Einstein Platform ServiceTokyo SFDG Meetup#16 / Release Note, Einstein Platform Service
Tokyo SFDG Meetup#16 / Release Note, Einstein Platform Service
 
Meetup #15 : リリースノート輪読 / Apexまわり
Meetup #15 : リリースノート輪読 / ApexまわりMeetup #15 : リリースノート輪読 / Apexまわり
Meetup #15 : リリースノート輪読 / Apexまわり
 
カスタムメタデータを受託の案件で使ってみた話
カスタムメタデータを受託の案件で使ってみた話カスタムメタデータを受託の案件で使ってみた話
カスタムメタデータを受託の案件で使ってみた話
 
Visualforceをあきらめない
VisualforceをあきらめないVisualforceをあきらめない
Visualforceをあきらめない
 
Salesforce dug tokyo_meetup#8_about_releasenote
Salesforce dug tokyo_meetup#8_about_releasenoteSalesforce dug tokyo_meetup#8_about_releasenote
Salesforce dug tokyo_meetup#8_about_releasenote
 
SDUG Tokyo Meetup#7 About ReleaseNote
SDUG Tokyo Meetup#7 About ReleaseNoteSDUG Tokyo Meetup#7 About ReleaseNote
SDUG Tokyo Meetup#7 About ReleaseNote
 
DCMax CrowdHackathonチャレンジ②
DCMax CrowdHackathonチャレンジ②DCMax CrowdHackathonチャレンジ②
DCMax CrowdHackathonチャレンジ②
 
Salesforce DUG Tokyo meetup#5
Salesforce DUG Tokyo meetup#5Salesforce DUG Tokyo meetup#5
Salesforce DUG Tokyo meetup#5
 
Cloudforce2012 LT
Cloudforce2012 LTCloudforce2012 LT
Cloudforce2012 LT
 
Force.com Developer Group Japan Meetup#2
Force.com Developer Group Japan Meetup#2Force.com Developer Group Japan Meetup#2
Force.com Developer Group Japan Meetup#2
 
Force.com Developer Group Japan Meetup#1
Force.com Developer Group Japan Meetup#1Force.com Developer Group Japan Meetup#1
Force.com Developer Group Japan Meetup#1
 

ApexトリガのBest Practiceを目指して

  • 2. #sfdg  米井 孝浩(よねい たかひろ)  TAOドライブ株式会社 エンジニア  Salesforce向けの受託開発をメインにしてます  (最近は、Herokuも少し...)  Salesforce DG (Tokyo) の運営メンバの1人  (いちおう)Salesforce MVP (Spring’ 15)
  • 5. #sfdg  Salesforceレコードへの変更前後にカスタムロジックを実行する機能  以下の操作の前後に実行することが可能 • insert • update • delete • merge • upsert • undelete  データベースのトリガと大体似たような感じ -> Salesforceでは、それがApexで記述できるのが良い
  • 6. #sfdg  だいたいこんな感じ trigger ContextExampleTrigger on Account (before insert, after insert, after delete) { if (Trigger.isInsert) { if (Trigger.isBefore) { // Before Insert で何か処理したいことなど } else if (Trigger.isAfter) { // After Insert で何か処理したいことなど } } else if (Trigger.isDelete) { // After Delete で何か処理したいことなど } }
  • 7. #sfdg  実装上、注意する点など  トリガの実行順序 1. 元のレコードがロード、または初期化される 2. 新しいレコードのフィールド値がロードされ、古い値を上書きする 3. 全てのbeforeトリガが実行される 4. カスタム検証ルールを含むシステム検証が行われる 5. 重複ルールが実行される 6. レコードはデータベースに保存されるが、コミットはされない 7. すべてのafterトリガが実行される 8. 割り当てルールが実行される 9. 自動応答ルールが実行される 10. ワークフロールールが実行される 11. ワークフローフィールドが更新されたら、レコードがリロードされる 12. 10の処理をうけて、beforeトリガとafterトリガを再度1度だけ実行する 13. エスカレーションルールが実行される 14. レコードが積み上げ集計項目をもっていたりクロスオブジェクトワークフローの一部である場合は、 親レコードの該当項目の値も更新する 15. すべてのDML操作がデータベースにコミットされる 16. 電子メールの送信など、コミット後のロジックが実行される 入力規則の前に beforeトリガが実行される ワークフロールールは afterトリガの後に実行される
  • 8. #sfdg  実装上、注意する点など  1つのオブジェクトに複数のトリガが定義されている場合、 トリガの実行順序は制御できない • 例えば、sandbox環境での順序と、運用環境での順序は異なる • 1オブジェクト -> 1トリガ が基本  トリガを呼び出さない操作には以下がある(詳細は公式ドキュメントを) • 削除のカスケード • マージ操作の結果として親が変更される子レコードの更新カスケード • キャンペーン状況、住所の一括変更 • 選択リストの名前変更 or 置換 ...etc
  • 9. #sfdg  実装上、注意する点など  一括操作を前提としてロジックを組むことが必須 • コレクション(Listなど)にレコードを追加し、それに対してDMLを実行 することで、DMLステートメント数を最小限にする • レコードを事前処理してコレクションを生成し、SOQLステートメントに 組み込むことで、SOQLステートメント数を最小限にする etc
  • 10. #sfdg
  • 11. #sfdg  Apexトリガテンプレートを利用する  A Simple Trigger Template for Salesforce を参考にしてみる  このテンプレートは、以下の課題をクリアすることを目指したもの • バルク処理が考慮されたテンプレートになってない • 7つのBoolean型のコンテキスト変数が、可読性とメンテナンス性の維持を 妨げている • 特定のコンテキストでは、Trigger.old, Trigger.new が使えないのを忘れがち • 非同期処理は、毎回自前で考慮する必要がある
  • 12. #sfdg  トリガ側のサンプル(取引先を利用) trigger AccountTrigger on Account (before insert, before update, ...(その他イベント)) { if( AccountTriggerHandler.hasExecuted ){ return; // prevent recursive re-entry } AccountTriggerHandler.hasExecuted= true; AccountTriggerHandler handler = new AccountTriggerHandler(Trigger.isExecuting, Trigger.size); if(Trigger.isInsert && Trigger.isBefore){ handler.OnBeforeInsert(Trigger.new); } else if(Trigger.isInsert && Trigger.isAfter){ handler.OnAfterInsert(Trigger.new); AccountTriggerHandler.OnAfterInsertAsync(Trigger.newMap.keySet()); } else if(Trigger.isUpdate && Trigger.isBefore){ handler.OnBeforeUpdate(Trigger.old, Trigger.new, Trigger.newMap); } ... } 各イベントでの処理を、 ハンドラ側で実装する 非同期用の処理も、 ハンドラ側で実装する トリガハンドラを用意する
  • 13. #sfdg  トリガハンドラのサンプル public with sharing class AccountTriggerHandler { public static boolean hasExecuted = false; private boolean m_isExecuting = false; private integer BatchSize = 0; public AccountTriggerHandler(boolean isExecuting, integer size){ m_isExecuting = isExecuting; BatchSize = size; } public void OnBeforeInsert(Account[] newAccounts){ // 何かの処理 } public void OnAfterInsert(Account[] newAccounts){ // 何かの処理 } @future public static void OnAfterInsertAsync(Set<ID> newAccountIDs){ // 何かの非同期処理 List<Account> newAccounts = [select Id, Name from Account where Id IN :newAccountIDs]; } ... } トリガで発生したイベントからの 処理を実装する
  • 14. #sfdg  トリガハンドラ側の実装 public with sharing class AccountTriggerHandler { public static boolean hasExecuted = false; private boolean m_isExecuting = false; private integer BatchSize = 0; public AccountTriggerHandler(boolean isExecuting, integer size){ m_isExecuting = isExecuting; BatchSize = size; } ... } 再帰処理防止のためのstatic変数
  • 15. #sfdg  トリガ側の実装 trigger AccountTrigger on Account (before insert, before update, ...(その他イベント)) { if( AccountTriggerHandler.hasExecuted ){ return; // prevent recursive re-entry } AccountTriggerHandler.hasExecuted= true; AccountTriggerHandler handler = new AccountTriggerHandler(Trigger.isExecuting, Trigger.size); if(Trigger.isInsert && Trigger.isBefore){ handler.OnBeforeInsert(Trigger.new); } else if(Trigger.isInsert && Trigger.isAfter){ handler.OnAfterInsert(Trigger.new); AccountTriggerHandler.OnAfterInsertAsync(Trigger.newMap.keySet()); } else if(Trigger.isUpdate && Trigger.isBefore){ handler.OnBeforeUpdate(Trigger.old, Trigger.new, Trigger.newMap); } ... } ①トリガ実行時にstatic変数のフラグをtrueにする ②再帰時にはトリガをスキップさせる
  • 16. #sfdg  トリガハンドラのサンプル public with sharing class AccountTriggerHandler { ... public void OnBeforeInsert(Account[] newAccounts) { // 何かの処理 } @future public static void OnAfterInsertAsync(Set<ID> newAccountIDs){ // 何かの非同期処理 List<Account> newAccounts = [select Id, Name from Account where Id IN :newAccountIDs]; } public void OnBeforeUpdate(Account[] oldAccounts, Account[] updatedAccounts, Map<ID, Account> accountMap){ //Example Map usage Map<ID, Contact> contacts = new Map<ID, Contact>([select Id, FirstName, LastName, Email from Contact where AccountId IN :accountMap.keySet()]); } } • トリガハンドラにて各イベントに 応じた処理を実装する • トリガ内で利用できる変数を考慮して、 引数を決めておく
  • 17. #sfdg  トリガ側の実装 trigger AccountTrigger on Account (before insert, before update, ...(その他イベント)) { ... AccountTriggerHandler.hasExecuted= true; AccountTriggerHandler handler = new AccountTriggerHandler(Trigger.isExecuting, Trigger.size); if(Trigger.isInsert && Trigger.isBefore){ handler.OnBeforeInsert(Trigger.new); } else if(Trigger.isInsert && Trigger.isAfter){ handler.OnAfterInsert(Trigger.new); AccountTriggerHandler.OnAfterInsertAsync(Trigger.newMap.keySet()); } else if(Trigger.isUpdate && Trigger.isBefore){ handler.OnBeforeUpdate(Trigger.old, Trigger.new, Trigger.newMap); } ... } 各イベントに対応するトリガハンドラ側の メソッドを呼び出す
  • 19. #sfdg
  • 20. #sfdg  前述のトリガテンプレート含め、「トリガハンドラ用のApexクラスを実装して 処理を移譲する」が推奨パターンとして広く周知されてきた  しかし、トリガでの実装がどんどん増えてくると、トリガハンドラも巨大な Apexクラスとなり、メンテナンスが困難に...?  1つのApexクラスで対応するのが困難であれば、Apexクラスを分割してはどう か?  トリガでの実装で、機能単位でApexクラスを分割して、各Apexクラスで 1つのことをうまくやるように考えてみる ※次ページ以降のソースコードは https://github.com/takahiro-yonei/ApexTriggerForMeetup18 で公開してます
  • 21. #sfdg  トリガのサンプル trigger AccountTrigger on Account (before insert, before update, after insert) { AccountTriggerHandler handler = new AccountTriggerHandler(Trigger.isExecuting, Trigger.size); handler.addObserver(new TriggerOperation[]{TriggerOperation.BEFORE_INSERT, TriggerOperation.BEFORE_UPDATE}, new AccountTriggerHandler_Validation()); handler.addObserver(new TriggerOperation[]{TriggerOperation.AFTER_INSERT}, new AccountTriggerHandler_FeedToXXX()); switch on Trigger.operationType { when BEFORE_INSERT { handler.onBeforeInsert(Trigger.new); } when BEFORE_UPDATE { handler.onBeforeUpdate(Trigger.old, Trigger.new, Trigger.oldMap, Trigger.newMap); } when AFTER_INSERT { handler.onAfterInsert(Trigger.new); } } } • トリガで処理したい内容を、適度な大きさで 分割する • 分割したApexクラスを、処理したい順番でト リガハンドラに保持させる
  • 22. #sfdg  分割したApexクラス側の実装 (1) public without sharing class AccountTriggerHandler_Validation implements BaseTriggerHandler.ObserverTrgBeforeInsert, BaseTriggerHandler.ObserverTrgBeforeUpdate { public AccountTriggerHandler_Validation(){} public List<SObject> onBeforeInsert(List<SObject> newAccounts){ for(Account acc : (List<Account>)newAccounts){ // Some Validation Process...etc System.debug('onBeforeInsert: ' + acc); } return newAccounts; } 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 System.debug('onBeforeUpdate: ' + acc); } return updAccounts; } } • Before Insert, Before Updateの 時だけ反応する
  • 23. #sfdg  分割したApexクラス側の実装 (2) public without sharing class AccountTriggerHandler_FeedToXXX implements BaseTriggerHandler.ObserverTrgAfterInsert { public AccountTriggerHandler_FeedToXXX() {} public void onAfterInsert(List<SObject> newAccounts){ for(Account acc : (List<Account>)newAccounts){ // Some Process System.debug('onAfterInsert: ' + acc); } } } • After Insertの時だけ反応する
  • 24. #sfdg  中の仕組みについて (0)  登場するApexクラス BaseTriggerHandler Account TriggerHandler Account TriggerHandler_XXX • 実際にトリガ処理を実装するところ 呼び出す インタフェースを 実装 継承 Account Trigger
  • 25. #sfdg  中の仕組みについて (1) public without sharing class AccountTriggerHandler extends BaseTriggerHandler { private static Boolean InProcess = false; public AccountTriggerHandler(boolean pIsExecuting, Integer pSize){ super(pIsExecuting, pSize); } public void onBeforeInsert(Account[] newAccounts){ for(BaseTriggerHandler.ObserverTrgBeforeInsert observer : beforeInsertObservers){ newAccounts = (List<Account>)observer.onBeforeInsert(newAccounts); } } public void onAfterInsert(Account[] newAccounts){ for(BaseTriggerHandler.ObserverTrgAfterInsert observer : afterInsertObservers){ observer.onAfterInsert(newAccounts); } } ... • 各イベントに応じたインタフェースを実装した Apexクラスのメソッドを順次実行していく
  • 26. #sfdg  中の仕組みについて (2) public virtual class BaseTriggerHandler { 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); } public interface ObserverTrgBeforeDelete extends ObserverTrg { List<SObject> onBeforeDelete(List<SObject> delObjects); } public interface ObserverTrgAfterInsert extends ObserverTrg { void onAfterInsert(List<SObject> newObjects); } public interface ObserverTrgAfterUpdate extends ObserverTrg { void onAfterUpdate(List<SObject> oldObjects, List<SObject> newObjects, Map<Id, SObject> oldObjectMap, Map<Id, SObject> newObjectMap); } public interface ObserverTrgAfterDelete extends ObserverTrg { void onAfterDelete(List<SObject> delObjects); } • トリガイベントに応じたインタフェースを 定義しておく • 各種イベントで処理したい内容を持つApex クラスを用意し、そのイベント用のインタ フェースを実装する
  • 27. #sfdg  中の仕組みについて (3) public virtual class BaseTriggerHandler { ... protected Boolean IsExecuting = false; protected Integer BatchSize = 0; protected List<ObserverTrgBeforeInsert> beforeInsertObservers; protected List<ObserverTrgBeforeUpdate> beforeUpdateObservers; protected List<ObserverTrgBeforeDelete> beforeDeleteObservers; protected List<ObserverTrgAfterInsert> afterInsertObservers; protected List<ObserverTrgAfterUpdate> afterUpdateObservers; protected List<ObserverTrgAfterDelete> afterDeleteObservers; public BaseTriggerHandler(boolean param_IsExecuting, Integer param_Size){ this.IsExecuting = param_IsExecuting; this.BatchSize = param_Size; beforeInsertObservers = new List<ObserverTrgBeforeInsert>(); beforeUpdateObservers = new List<ObserverTrgBeforeUpdate>(); beforeDeleteObservers = new List<ObserverTrgBeforeDelete>(); afterInsertObservers = new List<ObserverTrgAfterInsert>(); afterUpdateObservers = new List<ObserverTrgAfterUpdate>(); afterDeleteObservers = new List<ObserverTrgAfterDelete>(); } • 各イベントで処理させるApexクラスを格 納するためのリスト • 各イベントでは、このリストにあるApex クラスを順次処理していく
  • 28. #sfdg  中の仕組みについて (4) public virtual class BaseTriggerHandler { ... public void addObserver(List<System.TriggerOperation> points, ObserverTrg ob){ for(System.TriggerOperation o : points){ switch on o { when BEFORE_INSERT { beforeInsertObservers.add((ObserverTrgBeforeInsert)ob); } when BEFORE_UPDATE { beforeUpdateObservers.add((ObserverTrgBeforeUpdate)ob); } when BEFORE_DELETE { beforeDeleteObservers.add((ObserverTrgBeforeDelete)ob); } when AFTER_INSERT { afterInsertObservers.add((ObserverTrgAfterInsert)ob); } when AFTER_UPDATE { afterUpdateObservers.add((ObserverTrgAfterUpdate)ob); } ...(略) when else {} } } } • 各イベントに応じたインタフェースを実 装したApexクラスをリストに追加する • トリガ起動時に、Apexクラスのインスタ ンスを、リストに追加していく
  • 29. #sfdg trigger AccountTrigger on Account (before insert, before update, after insert) { AccountTriggerHandler handler = new AccountTriggerHandler(Trigger.isExecuting, Trigger.size); handler.addObserver(new TriggerOperation[]{TriggerOperation.BEFORE_INSERT, TriggerOperation.BEFORE_UPDATE}, new AccountTriggerHandler_Validation()); handler.addObserver(new TriggerOperation[]{TriggerOperation.AFTER_INSERT}, new AccountTriggerHandler_FeedToXXX()); switch on Trigger.operationType { when BEFORE_INSERT { handler.onBeforeInsert(Trigger.new); } when BEFORE_UPDATE { handler.onBeforeUpdate(Trigger.old, Trigger.new, Trigger.oldMap, Trigger.newMap); } when AFTER_INSERT { handler.onAfterInsert(Trigger.new); } } } • 分割したApexクラスを、処理したい順番でト リガハンドラに保持させる  中の仕組みについて (5)
  • 30. #sfdg  メリット vs デメリット  メリット  各Apexクラスの責務が明確になって、メンテナンス性は維持できる  各Apexクラスの処理順序も制御できる  デメリット  分割するApexクラスの粒度 • あんまり細かく分けてもApexクラスが大量に作成されて、メンテナン ス性が落ちるかもしれない • チーム内で、分割する基準を共有しておく必要がある  トリガ処理の効率 • 分割したApexクラスごとにfor-loopが実行されるので、トリガハンドラ が単体の場合よりも処理効率は落ちる • (まだあまり大きな問題になったことはないけど...)
  • 31. #sfdg  Apexトリガは非常に有用だが、毎回ゼロから実装するのは手間がかかるし、ト リガ本体に全て詰め込むとメンテナス困難になる、という経緯からテンプレー トが考案され、広まった(と思う)  テンプレートを用意することで、重要なロジックの実装により集中できる  トリガハンドラに移譲する仕組みは良いが、継続的な改善でトリガハンドラ が巨大になってメンテナンス困難になる可能性もある  トリガハンドラに移譲する仕組みを発展させて、トリガハンドラを分割するこ とを検討してみた  巨大になりがちなトリガハンドラの可読性とメンテナンス性を維持する上で は有効(と思う)  今後の発展として、プロセスビルダーとApexトリガはどこまで共存できるか? について検討してみたい(※気が向いたら)
  • 32. #sfdg