SlideShare a Scribd company logo
#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

Salesforceの標準オブジェクトについて復習してみた
Salesforceの標準オブジェクトについて復習してみたSalesforceの標準オブジェクトについて復習してみた
Salesforceの標準オブジェクトについて復習してみた
y-maeda
 
ソーシャルゲームのためのデータベース設計
ソーシャルゲームのためのデータベース設計ソーシャルゲームのためのデータベース設計
ソーシャルゲームのためのデータベース設計
Yoshinori Matsunobu
 
GoによるWebアプリ開発のキホン
GoによるWebアプリ開発のキホンGoによるWebアプリ開発のキホン
GoによるWebアプリ開発のキホン
Akihiko Horiuchi
 
What's new in Spring Boot 2.6 ?
What's new in Spring Boot 2.6 ?What's new in Spring Boot 2.6 ?
What's new in Spring Boot 2.6 ?
土岐 孝平
 
[Cloud OnAir] BigQuery の一般公開データセットを 利用した実践的データ分析 2019年3月28日 放送
[Cloud OnAir] BigQuery の一般公開データセットを 利用した実践的データ分析 2019年3月28日 放送[Cloud OnAir] BigQuery の一般公開データセットを 利用した実践的データ分析 2019年3月28日 放送
[Cloud OnAir] BigQuery の一般公開データセットを 利用した実践的データ分析 2019年3月28日 放送
Google Cloud Platform - Japan
 
F#入門 ~関数プログラミングとは何か~
F#入門 ~関数プログラミングとは何か~F#入門 ~関数プログラミングとは何か~
F#入門 ~関数プログラミングとは何か~
Nobuhisa Koizumi
 
【第26回Elasticsearch勉強会】Logstashとともに振り返る、やっちまった事例ごった煮
【第26回Elasticsearch勉強会】Logstashとともに振り返る、やっちまった事例ごった煮【第26回Elasticsearch勉強会】Logstashとともに振り返る、やっちまった事例ごった煮
【第26回Elasticsearch勉強会】Logstashとともに振り返る、やっちまった事例ごった煮
Hibino Hisashi
 
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」SQLアンチパターン 幻の第26章「とりあえず削除フラグ」
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」
Takuto Wada
 
Apexトリガと標準自動化プロセスの違い
Apexトリガと標準自動化プロセスの違いApexトリガと標準自動化プロセスの違い
Apexトリガと標準自動化プロセスの違い
Yoshinari KUWAYAMA
 
Java ORマッパー選定のポイント #jsug
Java ORマッパー選定のポイント #jsugJava ORマッパー選定のポイント #jsug
Java ORマッパー選定のポイント #jsug
Masatoshi Tada
 
大規模ソーシャルゲーム開発から学んだPHP&MySQL実践テクニック
大規模ソーシャルゲーム開発から学んだPHP&MySQL実践テクニック大規模ソーシャルゲーム開発から学んだPHP&MySQL実践テクニック
大規模ソーシャルゲーム開発から学んだPHP&MySQL実践テクニック
infinite_loop
 
マイクロにしすぎた結果がこれだよ!
マイクロにしすぎた結果がこれだよ!マイクロにしすぎた結果がこれだよ!
マイクロにしすぎた結果がこれだよ!
mosa siru
 
自社で実運用中!Power Apps・Power Automate 活用事例
自社で実運用中!Power Apps・Power Automate 活用事例自社で実運用中!Power Apps・Power Automate 活用事例
自社で実運用中!Power Apps・Power Automate 活用事例
Teruchika Yamada
 
GraphQLのsubscriptionで出来ること
GraphQLのsubscriptionで出来ることGraphQLのsubscriptionで出来ること
GraphQLのsubscriptionで出来ること
Shingo Fukui
 
ドメイン駆動設計 ( DDD ) をやってみよう
ドメイン駆動設計 ( DDD ) をやってみようドメイン駆動設計 ( DDD ) をやってみよう
ドメイン駆動設計 ( DDD ) をやってみよう
増田 亨
 
イミュータブルデータモデル(世代編)
イミュータブルデータモデル(世代編)イミュータブルデータモデル(世代編)
イミュータブルデータモデル(世代編)
Yoshitaka Kawashima
 
PostgreSQLの行レベルセキュリティと SpringAOPでマルチテナントの ユーザー間情報漏洩を防止する (JJUG CCC 2021 Spring)
PostgreSQLの行レベルセキュリティと SpringAOPでマルチテナントの ユーザー間情報漏洩を防止する (JJUG CCC 2021 Spring)PostgreSQLの行レベルセキュリティと SpringAOPでマルチテナントの ユーザー間情報漏洩を防止する (JJUG CCC 2021 Spring)
PostgreSQLの行レベルセキュリティと SpringAOPでマルチテナントの ユーザー間情報漏洩を防止する (JJUG CCC 2021 Spring)
Koichiro Matsuoka
 
[社内勉強会]ワークフローエンジンdigdag研究&プロダクトF.O.Xに導入
[社内勉強会]ワークフローエンジンdigdag研究&プロダクトF.O.Xに導入[社内勉強会]ワークフローエンジンdigdag研究&プロダクトF.O.Xに導入
[社内勉強会]ワークフローエンジンdigdag研究&プロダクトF.O.Xに導入
Takahiro Moteki
 
MySQLで論理削除と正しく付き合う方法
MySQLで論理削除と正しく付き合う方法MySQLで論理削除と正しく付き合う方法
MySQLで論理削除と正しく付き合う方法
yoku0825
 
Laravelを用いたゲームサーバーのチューニング
Laravelを用いたゲームサーバーのチューニングLaravelを用いたゲームサーバーのチューニング
Laravelを用いたゲームサーバーのチューニング
NOW PRODUCTION
 

What's hot (20)

Salesforceの標準オブジェクトについて復習してみた
Salesforceの標準オブジェクトについて復習してみたSalesforceの標準オブジェクトについて復習してみた
Salesforceの標準オブジェクトについて復習してみた
 
ソーシャルゲームのためのデータベース設計
ソーシャルゲームのためのデータベース設計ソーシャルゲームのためのデータベース設計
ソーシャルゲームのためのデータベース設計
 
GoによるWebアプリ開発のキホン
GoによるWebアプリ開発のキホンGoによるWebアプリ開発のキホン
GoによるWebアプリ開発のキホン
 
What's new in Spring Boot 2.6 ?
What's new in Spring Boot 2.6 ?What's new in Spring Boot 2.6 ?
What's new in Spring Boot 2.6 ?
 
[Cloud OnAir] BigQuery の一般公開データセットを 利用した実践的データ分析 2019年3月28日 放送
[Cloud OnAir] BigQuery の一般公開データセットを 利用した実践的データ分析 2019年3月28日 放送[Cloud OnAir] BigQuery の一般公開データセットを 利用した実践的データ分析 2019年3月28日 放送
[Cloud OnAir] BigQuery の一般公開データセットを 利用した実践的データ分析 2019年3月28日 放送
 
F#入門 ~関数プログラミングとは何か~
F#入門 ~関数プログラミングとは何か~F#入門 ~関数プログラミングとは何か~
F#入門 ~関数プログラミングとは何か~
 
【第26回Elasticsearch勉強会】Logstashとともに振り返る、やっちまった事例ごった煮
【第26回Elasticsearch勉強会】Logstashとともに振り返る、やっちまった事例ごった煮【第26回Elasticsearch勉強会】Logstashとともに振り返る、やっちまった事例ごった煮
【第26回Elasticsearch勉強会】Logstashとともに振り返る、やっちまった事例ごった煮
 
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」SQLアンチパターン 幻の第26章「とりあえず削除フラグ」
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」
 
Apexトリガと標準自動化プロセスの違い
Apexトリガと標準自動化プロセスの違いApexトリガと標準自動化プロセスの違い
Apexトリガと標準自動化プロセスの違い
 
Java ORマッパー選定のポイント #jsug
Java ORマッパー選定のポイント #jsugJava ORマッパー選定のポイント #jsug
Java ORマッパー選定のポイント #jsug
 
大規模ソーシャルゲーム開発から学んだPHP&MySQL実践テクニック
大規模ソーシャルゲーム開発から学んだPHP&MySQL実践テクニック大規模ソーシャルゲーム開発から学んだPHP&MySQL実践テクニック
大規模ソーシャルゲーム開発から学んだPHP&MySQL実践テクニック
 
マイクロにしすぎた結果がこれだよ!
マイクロにしすぎた結果がこれだよ!マイクロにしすぎた結果がこれだよ!
マイクロにしすぎた結果がこれだよ!
 
自社で実運用中!Power Apps・Power Automate 活用事例
自社で実運用中!Power Apps・Power Automate 活用事例自社で実運用中!Power Apps・Power Automate 活用事例
自社で実運用中!Power Apps・Power Automate 活用事例
 
GraphQLのsubscriptionで出来ること
GraphQLのsubscriptionで出来ることGraphQLのsubscriptionで出来ること
GraphQLのsubscriptionで出来ること
 
ドメイン駆動設計 ( DDD ) をやってみよう
ドメイン駆動設計 ( DDD ) をやってみようドメイン駆動設計 ( DDD ) をやってみよう
ドメイン駆動設計 ( DDD ) をやってみよう
 
イミュータブルデータモデル(世代編)
イミュータブルデータモデル(世代編)イミュータブルデータモデル(世代編)
イミュータブルデータモデル(世代編)
 
PostgreSQLの行レベルセキュリティと SpringAOPでマルチテナントの ユーザー間情報漏洩を防止する (JJUG CCC 2021 Spring)
PostgreSQLの行レベルセキュリティと SpringAOPでマルチテナントの ユーザー間情報漏洩を防止する (JJUG CCC 2021 Spring)PostgreSQLの行レベルセキュリティと SpringAOPでマルチテナントの ユーザー間情報漏洩を防止する (JJUG CCC 2021 Spring)
PostgreSQLの行レベルセキュリティと SpringAOPでマルチテナントの ユーザー間情報漏洩を防止する (JJUG CCC 2021 Spring)
 
[社内勉強会]ワークフローエンジンdigdag研究&プロダクトF.O.Xに導入
[社内勉強会]ワークフローエンジンdigdag研究&プロダクトF.O.Xに導入[社内勉強会]ワークフローエンジンdigdag研究&プロダクトF.O.Xに導入
[社内勉強会]ワークフローエンジンdigdag研究&プロダクトF.O.Xに導入
 
MySQLで論理削除と正しく付き合う方法
MySQLで論理削除と正しく付き合う方法MySQLで論理削除と正しく付き合う方法
MySQLで論理削除と正しく付き合う方法
 
Laravelを用いたゲームサーバーのチューニング
Laravelを用いたゲームサーバーのチューニングLaravelを用いたゲームサーバーのチューニング
Laravelを用いたゲームサーバーのチューニング
 

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_hack
kimukou_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
 
Apexデザインパターン
ApexデザインパターンApexデザインパターン
Apexデザインパターン
Salesforce Developers Japan
 
ReduxとSwiftの組み合わせ:改訂版
ReduxとSwiftの組み合わせ:改訂版ReduxとSwiftの組み合わせ:改訂版
ReduxとSwiftの組み合わせ:改訂版
Fumiya Sakai
 
Dotnetconf2017
Dotnetconf2017Dotnetconf2017
Dotnetconf2017
Yoshiyuki Taniguchi
 
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
 
ログにまつわるエトセトラ
ログにまつわるエトセトラログにまつわるエトセトラ
ログにまつわるエトセトラ
菊池 佑太
 
JavaScript 実践講座 Framework, Tool, Performance
JavaScript 実践講座 Framework, Tool, PerformanceJavaScript 実践講座 Framework, Tool, Performance
JavaScript 実践講座 Framework, Tool, Performance
クラスメソッド株式会社
 
Heroku Postgres
Heroku PostgresHeroku 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 JPAkira Takahashi
 
データマイニング+WEB勉強会資料第6回
データマイニング+WEB勉強会資料第6回データマイニング+WEB勉強会資料第6回
データマイニング+WEB勉強会資料第6回Naoyuki Yamada
 
Azure で Serverless 初心者向けタッチ&トライ
Azure で Serverless 初心者向けタッチ&トライAzure で Serverless 初心者向けタッチ&トライ
Azure で Serverless 初心者向けタッチ&トライ
Masanobu Sato
 
Das 2015
Das 2015Das 2015
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 Service
Takahiro 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_releasenote
Takahiro Yonei
 
SDUG Tokyo Meetup#7 About ReleaseNote
SDUG Tokyo Meetup#7 About ReleaseNoteSDUG Tokyo Meetup#7 About ReleaseNote
SDUG Tokyo Meetup#7 About ReleaseNote
Takahiro 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#5
Takahiro Yonei
 
Cloudforce2012 LT
Cloudforce2012 LTCloudforce2012 LT
Cloudforce2012 LT
Takahiro 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#2
Takahiro 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#1
Takahiro 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