Events - wimvelzeboer/fflib-apex-extensions GitHub Wiki

Apex Enterprise Patterns Events

Contents

Apex Enterprise Patterns Events are a revolutionary new way of executing business logic. It fully replaces the way Triggers and TriggerHandlers worked, and goes far beyond Platform Events.

An event consists out of an event name and an optional payload with data passed along with the event. Events can be invoked anywhere in Apex code and in Flows. There are different types of events that defines in which execution context the event listeners will be executed.

Event listeners contain small pieces of logic that usually start by validating the event and its payload and then invoke business logic (Service Layer). Listeners can be configured in a dynamic way, via either Custom Metadata or records on a Custom Object. Due to this dynamic way of configuration different users can have different event listeners based on the security model (via record sharing).
A certain listener can even be prevented from execution at the moment when an event is published.

Event Types

Real-Time

This will publish an event in real-time and any listener to that event will respond immediately in the same execution context where the event is published.

Imagine you need to validate an Account record to populate all empty fields with default values. The following code snippet will do all that for you, without needing to know which processes are responsible for doing that.

// Here you create you draft record
Account record = new Account(Name = 'Dummy');

// Invoke the event to complete the record
fflib_Event.newInstance()
  .addPayload(record)
  .applyDefaultsEvent(Schema.Account.SObjectType)
  .publish();

System.debug(record);
// {Name = 'Dummy', Rating = 'Low', NumberOfEmployees = 0}

In the background two event listeners were activated to do the following;

  • Add a default rating of 'Low'

  • Set the Number Of Employees to the number of Contact records linked to the Account record.
    This record has no record Id and must be a new record so, the number is zero.

This is an example of realtime events. The moment when the publish method is invoked all the listeners will be executed. You can imagine when there are many listeners then it is likely to hit a Salesforce limit. Those limits are a friendly reminder that we need to re-think our architecture.

Near Real-Time

Near real-time events are often the solution when dealing with limit issues. Each event is executed in its own execution context and all listeners run in that same context. It can either use the standard Salesforce Platform Events, or be executed in as Queued Apex.

Let’s take another example. Here we have a listener that listens to an after insert event of a Contact record. It then recalculates the Account.NumberOfEmployees value based on the number of Contact records. then we would get something like the following code snippet:

Account account = [SELECT Id, NumberOfEmployees FROM Account WHERE Name = 'Dummy' LIMIT 1];
System.debug(account.NumberOfEmployees);  // output: 0;

Contact contact = new Contact( LastName = 'Smith' );
insert contact;

fflib_Event.newInstance()
  .addPayload(Account.Id)  // The data send along with this event.
  .afterInsertEvent(Schema.Contact.SObjectType)  // Constructs the event name.
  .nearRealTimeEvent()  // When it should be published
  .publish();

// Now the NumberOfEmployees is still zero
// But after a few minutes...
// The NumberOfEmployees is set to one

Instead of updating the Account record via an Apex Trigger as part of the onAfterInsert, an event is published via Platform Events. Because it is a near Real-Time event we are limited in the size of the payload, therefore we only pass the record Id(s).
We also want to be very specific in the type of the event, that only executes the business logic that we need. For the event name we chose an update request event on the NumberOfEmployees field.

Different Types

Near real-time events can be executed in three different ways:

  • platformEvent
    These events are published via the standard Salesforce Platform events feature.

  • queuedEvent
    When published the event handler will be executed as a Queueable Apex

  • nearRealTimeEvent
    Based on the available resources (limits) the event will be either published as Queued Apex or as a Platform event. The preference for which one that will be used first is configurable, but Queued Apex will be used first by default until 50% of its limit, to allow listeners to enqueue other jobs in the chain.

Future event

Events can also be schedules to be executed at a pre-defined moment in time. A scheduled job runs at certain intervals to check if there are future events overdue, it will group the events by their event name and publishes each event in a near real-time context (Platform Events) in a batched manner containing maximum of 200 event payloads.

Creating a future event is similar to a real-time event, with one difference that we provide a DateTime value with the call to the `publish' method.

Account record = new Account(Name = 'Dummy');
insert record;

fflib_Event.newInstance()
  .addPayload(record.Id)  // The data send along with this event.
  .updateEvent(Schema.Account.Rating)  // Constructs the event name
  .publish(DateTime.newInstance(2022,02,02,02,02,00)); // It is published on 2022-02-02 02:02:00

Event Names

An event name is a unique value that identifies the event. Event listeners are listening to this unique name, and will be executed once it appears.

Event names can me any String of a maximum of 80 characters and is case-sensitive. This can potentially cause a lot of issues with typos in the event name. Therefore we have a number of methods that construct event names.

All the follow methods have four method overload accepting the following arguments:

  • eventMethod( Schema.SObjectType sObjectType )

  • eventMethod( Schema.SObjectField SObjectField )

  • eventMethod( Object obj )

  • eventMethod( String objectName )

Generic event name constructor methods:

Method name Description

updateEvent

Requesting an update for the given object.
The Object itself is either passed on with the event as payload or the payload contains a unique Id to the object.

The well known TriggerHandler events:

Method name Description

applyDefaultsEvent

Requesting to populate the record with default values.

beforeInsertEvent

Automatically published in real-time by the triggerHandler of the framework, on a onBefore + onInsert trigger operation

afterInsertEvent

Automatically published in real-time by the triggerHandler of the framework, on a onAfter + onInsert trigger operation

beforeUpdateEvent

Automatically published in real-time by the triggerHandler of the framework, on a onBefore + onUpdate trigger operation

afterUpdateEvent

Automatically published in real-time by the triggerHandler of the framework, on a onAfter + onUpdate trigger operation

beforeDeleteEvent

Automatically published in real-time by the triggerHandler of the framework, on a onBefore + onDelete trigger operation

afterDeleteEvent

Automatically published in real-time by the triggerHandler of the framework, on a onAfter + onDelete trigger operation

afterUnDeleteEvent

Automatically published in real-time by the triggerHandler of the framework, on a onAfter + onUnDelete trigger operation

validateEvent

Automatically published in real-time by the triggerHandler of the framework, on a onBefore + onInsert and onAfter + onUpdate trigger operation

If none of the standard event name constructors are useful, then the eventName will accept any name you want. Do know that the standard event name constructors generate a name starting with "E0", therefore avoid event names with that prefix.

Account record = new Account(Name = 'Dummy', NumberOfEmployees = 500);

fflib_Event.newInstance()
  .addPayload(record)
  .eventName('RecalculculateRating')
  .publish();

System.debug(record.Rating) // Output: 'Hot'

Payloads

Every event can have a payload containing data passed onto the listener.

Accepted Data Types Real-time Near real-time Future

Id

Available

Available

Available

Set<Id>

Available

Available

Available

Object

Available

Not advised

NOT Available

List<SObject>
List<Object>
instance of fflib_IDomain

Available

NOT Available

NOT Available

Emitter

The emitter will retrieve all the listeners for the published event at hand, sort the listeners in the correct order , adds the queueable listeners to the queue, and will execute their logic and deliver the payload to them.

Listeners

Listeners perform the business logic upon the moment a certain event occurs.

Listener Types

There are two types of events:

  • Realtime

  • Queued

Listener Registry

The listeners for an event are listed into registries. One application registry based on Custom Metadata, the user registry is using a private readonly Custom Object.

Application Registry

Listeners that always run when the event is published
Based on Custom Medata

User Registry

Listeners that only run when the user has access to listen to the published event
Based on Custom Object and standard record sharing (Object = Private record sharing, no Create/Update/Delete object access)
And they use conditions to pre validate if the listener should be used for the event.

⚠️ **GitHub.com Fallback** ⚠️