Skip to content

AfterDelete.Dispatcher

Collects the deleted records its predicate accepts and hands them over once per chunk. Enqueue a job, send one email or publish events.

Interface

apex
public class AfterDelete {
    public interface Dispatcher extends Handler {
        Boolean dispatchOnAfterDeleteWhen(TriggerHandler.DeleteRecord record);
        void dispatchOnAfterDelete(TriggerHandler.DeleteRecords records);
    }
}
  • dispatchOnAfterDeleteWhen(record): called once per record in the chunk; it only selects records. Return true to include the record in the dispatch.
  • dispatchOnAfterDelete(records): called once, after every record was checked, with the qualified records; not called when none qualified.

Example

apex
public with sharing class ContactDispatcher implements AfterDelete.Dispatcher {
    public Boolean dispatchOnAfterDeleteWhen(TriggerHandler.DeleteRecord record) {
        return record.isNotNull(Contact.Email);
    }

    public void dispatchOnAfterDelete(TriggerHandler.DeleteRecords records) {
        System.enqueueJob(new ContactSyncJob(records.getIdsOf(Contact.AccountId)));
    }

    public class ContactSyncJob implements Queueable {
        private Set<Id> recordIds;

        public ContactSyncJob(Set<Id> recordIds) {
            this.recordIds = recordIds;
        }

        public void execute(QueueableContext context) {
        }
    }
}
apex
public with sharing class ContactRemovalSyncDispatcher implements AfterDelete.Dispatcher {
    public Boolean dispatchOnAfterDeleteWhen(TriggerHandler.DeleteRecord record) {
        return record.isNotBlank(Contact.Email) && record.isNull(Contact.MasterRecordId);
    }

    public void dispatchOnAfterDelete(TriggerHandler.DeleteRecords records) {
        System.enqueueJob(new RemovalSyncJob(records.getValuesOf(Contact.Email)));
    }

    public class RemovalSyncJob implements Queueable, Database.AllowsCallouts {
        private Set<String> emails;

        public RemovalSyncJob(Set<String> emails) {
            this.emails = emails;
        }

        public void execute(QueueableContext context) {
        }
    }
}

Good to Know

  • Pass values, not the deleted Ids. The job runs after the commit, when the deleted rows can no longer be queried. Pass the values it needs, or the Ids of records that still exist.
  • No synchronous callouts. A callout from a trigger throws a System.CalloutException. Call out from a Queueable that implements Database.AllowsCallouts.
  • No unit of work. DML here runs at once and is not merged with the Writers' unit. Use a Writer for record writes.
  • Merge losers arrive here too. Skip them with record.isNull(Contact.MasterRecordId) when a merge should not count as a removal.
  • Writer wins. A class that also implements AfterDelete.Writer runs only as a Writer.

Test

apex
@IsTest
static void dispatchOnAfterDeleteEnqueuesOneJob() {
    // Setup
    Contact oldContact = new Contact(LastName = 'Doe', Email = 'jane@acme.com');
    TriggerHandler.DeleteRecords records = new TriggerHandler.DeleteTriggerRecords(new List<TriggerHandler.TriggerRecord>{ new TriggerHandler.TriggerRecord(null, oldContact) });

    // Test
    new ContactDispatcher().dispatchOnAfterDelete(records);

    // Verify
    Assert.areEqual(1, Limits.getQueueableJobs(), 'One job should be enqueued for the chunk.');
}