SlideShare une entreprise Scribd logo
1  sur  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

Contenu connexe

Tendances

大量データを扱う際のクイックTips インデックス&スキニーテーブル編-
大量データを扱う際のクイックTips インデックス&スキニーテーブル編-大量データを扱う際のクイックTips インデックス&スキニーテーブル編-
大量データを扱う際のクイックTips インデックス&スキニーテーブル編-Salesforce Developers Japan
 
イミュータブルデータモデル(入門編)
イミュータブルデータモデル(入門編)イミュータブルデータモデル(入門編)
イミュータブルデータモデル(入門編)Yoshitaka Kawashima
 
データ連携の新しいカタチ - 変更データキャプチャ/プラットフォームイベントを MuleSoft Anypoint Platform と組み合わせて試してみよう
データ連携の新しいカタチ - 変更データキャプチャ/プラットフォームイベントを MuleSoft Anypoint Platform と組み合わせて試してみようデータ連携の新しいカタチ - 変更データキャプチャ/プラットフォームイベントを MuleSoft Anypoint Platform と組み合わせて試してみよう
データ連携の新しいカタチ - 変更データキャプチャ/プラットフォームイベントを MuleSoft Anypoint Platform と組み合わせて試してみようSalesforce Developers Japan
 
認定 Integration Architecture デザイナー試験を復習してみた
認定 Integration Architecture デザイナー試験を復習してみた認定 Integration Architecture デザイナー試験を復習してみた
認定 Integration Architecture デザイナー試験を復習してみたTakahito Miyamoto
 
Einstein Analyticsでのデータ取り込みと加工
Einstein Analyticsでのデータ取り込みと加工Einstein Analyticsでのデータ取り込みと加工
Einstein Analyticsでのデータ取り込みと加工Salesforce Developers Japan
 
Salesforceでの大規模データの取り扱い
Salesforceでの大規模データの取り扱いSalesforceでの大規模データの取り扱い
Salesforceでの大規模データの取り扱いSalesforce Developers Japan
 
Salesforceのサイトゲストユーザについて
SalesforceのサイトゲストユーザについてSalesforceのサイトゲストユーザについて
SalesforceのサイトゲストユーザについてTaiki Yoshikawa
 
Salesforce integration architecture 20200529
Salesforce integration architecture 20200529Salesforce integration architecture 20200529
Salesforce integration architecture 20200529Hiroki Iida
 
SalesforceにおけるCDC(変更データキャプチャ)の実装・活用法について
SalesforceにおけるCDC(変更データキャプチャ)の実装・活用法についてSalesforceにおけるCDC(変更データキャプチャ)の実装・活用法について
SalesforceにおけるCDC(変更データキャプチャ)の実装・活用法についてTakashi Hatamoto
 
Apexトリガと標準自動化プロセスの違い
Apexトリガと標準自動化プロセスの違いApexトリガと標準自動化プロセスの違い
Apexトリガと標準自動化プロセスの違いYoshinari KUWAYAMA
 
SFDG_画面フローとLightningWebComponentのハイブリッド開発について.pptx
SFDG_画面フローとLightningWebComponentのハイブリッド開発について.pptxSFDG_画面フローとLightningWebComponentのハイブリッド開発について.pptx
SFDG_画面フローとLightningWebComponentのハイブリッド開発について.pptxToru Inoue
 
ドキュメントを作りたくなってしまう魔法のツールSphinx
ドキュメントを作りたくなってしまう魔法のツールSphinxドキュメントを作りたくなってしまう魔法のツールSphinx
ドキュメントを作りたくなってしまう魔法のツールSphinxTakayuki Shimizukawa
 
世界一わかりやすいClean Architecture
世界一わかりやすいClean Architecture世界一わかりやすいClean Architecture
世界一わかりやすいClean ArchitectureAtsushi Nakamura
 
Salesforce開発プロジェクトの進め方とアプリケーションライフサイクルマネジメント
Salesforce開発プロジェクトの進め方とアプリケーションライフサイクルマネジメントSalesforce開発プロジェクトの進め方とアプリケーションライフサイクルマネジメント
Salesforce開発プロジェクトの進め方とアプリケーションライフサイクルマネジメントSalesforce Developers Japan
 
Force.com canvas入門ガイド
Force.com canvas入門ガイドForce.com canvas入門ガイド
Force.com canvas入門ガイドKazuki Nakajima
 

Tendances (20)

Visualforceを使ってみよう
Visualforceを使ってみようVisualforceを使ってみよう
Visualforceを使ってみよう
 
大量データを扱う際のクイックTips インデックス&スキニーテーブル編-
大量データを扱う際のクイックTips インデックス&スキニーテーブル編-大量データを扱う際のクイックTips インデックス&スキニーテーブル編-
大量データを扱う際のクイックTips インデックス&スキニーテーブル編-
 
イミュータブルデータモデル(入門編)
イミュータブルデータモデル(入門編)イミュータブルデータモデル(入門編)
イミュータブルデータモデル(入門編)
 
データ連携の新しいカタチ - 変更データキャプチャ/プラットフォームイベントを MuleSoft Anypoint Platform と組み合わせて試してみよう
データ連携の新しいカタチ - 変更データキャプチャ/プラットフォームイベントを MuleSoft Anypoint Platform と組み合わせて試してみようデータ連携の新しいカタチ - 変更データキャプチャ/プラットフォームイベントを MuleSoft Anypoint Platform と組み合わせて試してみよう
データ連携の新しいカタチ - 変更データキャプチャ/プラットフォームイベントを MuleSoft Anypoint Platform と組み合わせて試してみよう
 
認定 Integration Architecture デザイナー試験を復習してみた
認定 Integration Architecture デザイナー試験を復習してみた認定 Integration Architecture デザイナー試験を復習してみた
認定 Integration Architecture デザイナー試験を復習してみた
 
基本設計+詳細設計の書き方 社内勉強会0304
基本設計+詳細設計の書き方 社内勉強会0304基本設計+詳細設計の書き方 社内勉強会0304
基本設計+詳細設計の書き方 社内勉強会0304
 
Einstein Analyticsでのデータ取り込みと加工
Einstein Analyticsでのデータ取り込みと加工Einstein Analyticsでのデータ取り込みと加工
Einstein Analyticsでのデータ取り込みと加工
 
Salesforceでの大規模データの取り扱い
Salesforceでの大規模データの取り扱いSalesforceでの大規模データの取り扱い
Salesforceでの大規模データの取り扱い
 
Apexデザインパターン
ApexデザインパターンApexデザインパターン
Apexデザインパターン
 
Salesforceのサイトゲストユーザについて
SalesforceのサイトゲストユーザについてSalesforceのサイトゲストユーザについて
Salesforceのサイトゲストユーザについて
 
Salesforce 開発入門
Salesforce 開発入門Salesforce 開発入門
Salesforce 開発入門
 
Salesforce integration architecture 20200529
Salesforce integration architecture 20200529Salesforce integration architecture 20200529
Salesforce integration architecture 20200529
 
SalesforceにおけるCDC(変更データキャプチャ)の実装・活用法について
SalesforceにおけるCDC(変更データキャプチャ)の実装・活用法についてSalesforceにおけるCDC(変更データキャプチャ)の実装・活用法について
SalesforceにおけるCDC(変更データキャプチャ)の実装・活用法について
 
Salesforce DX & GitHub Deep Dive
Salesforce DX & GitHub Deep DiveSalesforce DX & GitHub Deep Dive
Salesforce DX & GitHub Deep Dive
 
Apexトリガと標準自動化プロセスの違い
Apexトリガと標準自動化プロセスの違いApexトリガと標準自動化プロセスの違い
Apexトリガと標準自動化プロセスの違い
 
SFDG_画面フローとLightningWebComponentのハイブリッド開発について.pptx
SFDG_画面フローとLightningWebComponentのハイブリッド開発について.pptxSFDG_画面フローとLightningWebComponentのハイブリッド開発について.pptx
SFDG_画面フローとLightningWebComponentのハイブリッド開発について.pptx
 
ドキュメントを作りたくなってしまう魔法のツールSphinx
ドキュメントを作りたくなってしまう魔法のツールSphinxドキュメントを作りたくなってしまう魔法のツールSphinx
ドキュメントを作りたくなってしまう魔法のツールSphinx
 
世界一わかりやすいClean Architecture
世界一わかりやすいClean Architecture世界一わかりやすいClean Architecture
世界一わかりやすいClean Architecture
 
Salesforce開発プロジェクトの進め方とアプリケーションライフサイクルマネジメント
Salesforce開発プロジェクトの進め方とアプリケーションライフサイクルマネジメントSalesforce開発プロジェクトの進め方とアプリケーションライフサイクルマネジメント
Salesforce開発プロジェクトの進め方とアプリケーションライフサイクルマネジメント
 
Force.com canvas入門ガイド
Force.com canvas入門ガイドForce.com canvas入門ガイド
Force.com canvas入門ガイド
 

Similaire à 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
 
Google App Engine for Java
Google App Engine for JavaGoogle App Engine for Java
Google App Engine for JavaTakuya Tsuchida
 

Similaire à 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 フレームワークの徹底比較!
 
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もいいぞ
 
Google App Engine for Java
Google App Engine for JavaGoogle App Engine for Java
Google App Engine for Java
 

Plus de 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
 

Plus de 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