Improvements to the trigger handler
Ability to disable trigger action in meta-data, and adds a stateful option Signed-off-by: WimVelzeboer <wimvelzeboer@protonmail.com>
This commit is contained in:
+27
-1
@@ -66,7 +66,6 @@ public virtual inherited sharing class fflib_TriggerActionConfig
|
||||
else if (triggerAction.ExecutionContext__c == 'Dedicated Queued') this.dedicatedQueued = true;
|
||||
|
||||
// Get SObjectType
|
||||
System.debug('triggerAction: '+triggerAction);
|
||||
String ObjectTypeApiName =
|
||||
String.isNotBlank(triggerAction.ObjectType__c)
|
||||
? triggerAction.ObjectType__r?.QualifiedApiName?.toLowerCase().trim()
|
||||
@@ -74,6 +73,14 @@ public virtual inherited sharing class fflib_TriggerActionConfig
|
||||
this.sObjectType = Schema.describeSObjects(new List<String> {ObjectTypeApiName}).get(0).getSObjectType();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the Implementation Type of the Trigger Action
|
||||
*/
|
||||
public Type getImplementationType()
|
||||
{
|
||||
return this.implementationType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the sequence number of the trigger action
|
||||
*/
|
||||
@@ -90,6 +97,14 @@ public virtual inherited sharing class fflib_TriggerActionConfig
|
||||
return (this.dedicatedQueued != null && this.dedicatedQueued);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns TRUE if the trigger action needs to be kept in memory and reused by the next TriggerHandler run
|
||||
*/
|
||||
public Boolean isStateful()
|
||||
{
|
||||
return this.stateful;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns TRUE if the trigger action can be executed in an Queueable context
|
||||
*/
|
||||
@@ -107,4 +122,15 @@ public virtual inherited sharing class fflib_TriggerActionConfig
|
||||
{
|
||||
return (fflib_ITriggerAction) implementationType.newInstance();
|
||||
}
|
||||
|
||||
public Boolean hasTriggerContext(TriggerOperation triggerOperation)
|
||||
{
|
||||
return (triggerOperation == System.TriggerOperation.BEFORE_INSERT && beforeInsert)
|
||||
|| (triggerOperation == System.TriggerOperation.BEFORE_UPDATE && beforeUpdate)
|
||||
|| (triggerOperation == System.TriggerOperation.BEFORE_DELETE && beforeDelete)
|
||||
|| (triggerOperation == System.TriggerOperation.AFTER_DELETE && afterDelete)
|
||||
|| (triggerOperation == System.TriggerOperation.AFTER_INSERT && afterInsert)
|
||||
|| (triggerOperation == System.TriggerOperation.AFTER_UNDELETE && afterUndelete)
|
||||
|| (triggerOperation == System.TriggerOperation.AFTER_UPDATE && afterUpdate);
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -65,12 +65,12 @@ public virtual inherited sharing class fflib_TriggerActionConfigSelector
|
||||
/**
|
||||
* @return Query all fflib_TriggerAction__mdt records
|
||||
*/
|
||||
public virtual List<fflib_TriggerAction__mdt> selectAll()
|
||||
public virtual List<fflib_TriggerAction__mdt> selectAllActive()
|
||||
{
|
||||
return (List <fflib_TriggerAction__mdt>) Database.query(
|
||||
|
||||
String.format(
|
||||
'SELECT {0} FROM {1}',
|
||||
'SELECT {0} FROM {1} WHERE IsActive__c=TRUE',
|
||||
new List<String>
|
||||
{
|
||||
getFieldListString() + ', ObjectType__r.QualifiedApiName',
|
||||
|
||||
@@ -48,6 +48,12 @@ public virtual with sharing class fflib_TriggerHandler
|
||||
|
||||
protected fflib_ITriggerContext triggerContext;
|
||||
|
||||
/**
|
||||
* Retain instance of Trigger Action classes implementing stateful
|
||||
*/
|
||||
@TestVisible
|
||||
private static Map<Type, fflib_ITriggerAction> triggerActionStateByClass;
|
||||
|
||||
/**
|
||||
* Provides access to the trigger action configurations and allowing it to be mocked in unit-tests
|
||||
*/
|
||||
@@ -101,7 +107,9 @@ public virtual with sharing class fflib_TriggerHandler
|
||||
fflib_UnitOfWork unitOfWork = new fflib_UnitOfWork();
|
||||
for (fflib_ITriggerActionConfig triggerActionConfig : configs)
|
||||
{
|
||||
fflib_ITriggerAction triggerAction = triggerActionConfig.newInstance();
|
||||
if (isValidExecutionContext(ctx, triggerActionConfig) == false) continue;
|
||||
|
||||
fflib_ITriggerAction triggerAction = getTriggerActionInstance(triggerActionConfig);
|
||||
|
||||
triggerAction.setContext(ctx);
|
||||
triggerAction.setSequence(triggerActionConfig.getSequence());
|
||||
@@ -117,12 +125,29 @@ public virtual with sharing class fflib_TriggerHandler
|
||||
unitOfWork.doWork();
|
||||
}
|
||||
|
||||
private Boolean isValidExecutionContext(fflib_ITriggerContext ctx, fflib_ITriggerActionConfig config)
|
||||
{
|
||||
return config.hasTriggerContext(ctx.getTriggerOperation());
|
||||
}
|
||||
|
||||
private fflib_ITriggerAction getTriggerActionInstance(fflib_ITriggerActionConfig triggerActionConfig)
|
||||
{
|
||||
if (triggerActionConfig.isStateful()
|
||||
&& triggerActionStateByClass != null
|
||||
&& triggerActionStateByClass.containsKey(triggerActionConfig.getImplementationType()))
|
||||
{
|
||||
return triggerActionStateByClass.get(triggerActionConfig.getImplementationType());
|
||||
}
|
||||
|
||||
return triggerActionConfig.newInstance();
|
||||
}
|
||||
|
||||
@TestVisible
|
||||
protected void loadConfiguration()
|
||||
{
|
||||
if (configs.isEmpty() == false) return;
|
||||
|
||||
for (fflib_TriggerAction__mdt action : new fflib_TriggerActionConfigSelector().selectAll())
|
||||
for (fflib_TriggerAction__mdt action : new fflib_TriggerActionConfigSelector().selectAllActive())
|
||||
{
|
||||
configs.add( new fflib_TriggerActionConfig(action));
|
||||
}
|
||||
|
||||
+19
@@ -29,16 +29,33 @@
|
||||
*/
|
||||
public interface fflib_ITriggerActionConfig
|
||||
{
|
||||
/**
|
||||
* @return Returns the Implementation Type of the Trigger Action
|
||||
*/
|
||||
System.Type getImplementationType();
|
||||
|
||||
/**
|
||||
* @return Returns the sequence number of the trigger action
|
||||
*/
|
||||
Integer getSequence();
|
||||
|
||||
/**
|
||||
* @param triggerOperation The Trigger operation to validate the configuration against.
|
||||
*
|
||||
* @return Returns TRUE is the given operation type matches the one of the configuration
|
||||
*/
|
||||
Boolean hasTriggerContext(TriggerOperation triggerOperation);
|
||||
|
||||
/**
|
||||
* @return Returns TRUE if the trigger action should be executed in a dedicated Queueable context
|
||||
*/
|
||||
Boolean isDedicatedQueued();
|
||||
|
||||
/**
|
||||
* @return Returns TRUE if the trigger action needs to be kept in memory and reused by the next TriggerHandler run
|
||||
*/
|
||||
Boolean isStateful();
|
||||
|
||||
/**
|
||||
* @return Returns TRUE if the trigger action can be executed in an Queueable context
|
||||
*/
|
||||
@@ -50,4 +67,6 @@ public interface fflib_ITriggerActionConfig
|
||||
* @return Instance of the Trigger Action class
|
||||
*/
|
||||
fflib_ITriggerAction newInstance();
|
||||
|
||||
|
||||
}
|
||||
+1
-1
@@ -32,5 +32,5 @@ public interface fflib_ITriggerActionConfigSelector
|
||||
/**
|
||||
* @return Query all fflib_TriggerAction__mdt records
|
||||
*/
|
||||
List<fflib_TriggerAction__mdt> selectAll();
|
||||
List<fflib_TriggerAction__mdt> selectAllActive();
|
||||
}
|
||||
+4
@@ -24,6 +24,10 @@
|
||||
<behavior>Required</behavior>
|
||||
<field>NamespacePrefix</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>IsActive__c</field>
|
||||
</layoutItems>
|
||||
</layoutColumns>
|
||||
<style>TwoColumnsTopToBottom</style>
|
||||
</layoutSections>
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>IsActive__c</fullName>
|
||||
<defaultValue>true</defaultValue>
|
||||
<externalId>false</externalId>
|
||||
<fieldManageability>DeveloperControlled</fieldManageability>
|
||||
<label>Is Active</label>
|
||||
<type>Checkbox</type>
|
||||
</CustomField>
|
||||
@@ -252,7 +252,49 @@ private class fflib_TriggerHandlerTest
|
||||
System.assert(ctx.isUpdate());
|
||||
}
|
||||
|
||||
@IsTest
|
||||
private static void itShouldUseTheStatefulTriggerActionClass()
|
||||
{
|
||||
fflib_LogEntry__c record = new fflib_LogEntry__c(LogLevel__c = 'INFO', Message__c = 'Unit Test');
|
||||
List<fflib_LogEntry__c> records = new List<fflib_LogEntry__c> {record};
|
||||
|
||||
fflib_ApexMocks mocks = new fflib_ApexMocks();
|
||||
fflib_ITriggerAction triggerActionMock = (fflib_ITriggerAction) mocks.mock(fflib_ITriggerAction.class);
|
||||
|
||||
mocks.startStubbing();
|
||||
mocks.when(triggerActionMock.isQueueable()).thenReturn(false);
|
||||
mocks.when(triggerActionMock.isNotQueueable()).thenReturn(true);
|
||||
mocks.stopStubbing();
|
||||
|
||||
fflib_TriggerHandler.triggerActionStateByClass =
|
||||
new Map<Type, fflib_ITriggerAction>{ MyTriggerAction.class => triggerActionMock };
|
||||
|
||||
fflib_TriggerAction__mdt triggerAction = new fflib_TriggerAction__mdt(
|
||||
ObjectType__c = null,
|
||||
ObjectTypeAlternate__c = Schema.fflib_LogEntry__c.SObjectType.getDescribe().getName(),
|
||||
BeforeInsert__c = true,
|
||||
ImplementationType__c = 'fflib_TriggerHandlerTest.MyTriggerAction',
|
||||
ExecutionContext__c = 'Realtime',
|
||||
Stateful__c = true
|
||||
);
|
||||
|
||||
fflib_TriggerHandler triggerHandler = new fflib_TriggerHandler();
|
||||
String ctxClassName = triggerHandler.triggerContextType.getName() + '.Constructor';
|
||||
fflib_TriggerContext ctx =
|
||||
(fflib_TriggerContext)
|
||||
((fflib_IDomainConstructor) Type.forName(ctxClassName).newInstance())
|
||||
.construct(new List<fflib_LogEntry__c> {record});
|
||||
ctx.triggerOperation = System.TriggerOperation.BEFORE_INSERT;
|
||||
|
||||
// WHEN a record is inserted
|
||||
System.Test.startTest();
|
||||
triggerHandler.addTriggerActionConfig(triggerAction);
|
||||
triggerHandler.setTriggerContext(ctx).run();
|
||||
System.Test.stopTest();
|
||||
|
||||
// THEN the stateful trigger action should be re-used
|
||||
((fflib_ITriggerAction) mocks.verify(triggerActionMock)).doWork();
|
||||
}
|
||||
|
||||
public class MyTriggerAction extends fflib_TriggerAction // Todo - rename handler to action
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user