Embracing Package-Based Development on the Lightning Platform: Separating Triggers into Multiple Packages

Consider a case where you might want to have a package hierarchy. As an ISV or Consultant, you might create a package for an industry, and then one or more packages for each business unit under that industry.

As a best practice, you should have a single trigger and trigger handler class per object. This allows you to control the order in which trigger operations can occur.

Let's say you use the package hierarchy below:

The tricky thing here is, while you will want to install the industryCommon package for every customer, the same isn't true for the child packages. For a given customer, you might want to install businessUnit1, businessUnit2, or both.

Given the package hierarchy above, it's not possible to have a single trigger on the Contact object. That is, until we leverage the power of Interfaces and the Type class.

To implement apex best practices, we will need to create a single trigger and trigger handler class in the industryCommon package. When we are done it will look like this:

We will then need to implement what I am going to call Trigger Consumers. These are apex classes that hold independent trigger logic in the businessUnit1 and businessUnit2 packages. Below is an example of a Trigger Consumer you might see in the businessUnit1 package:

To enable this separation of trigger logic into separate packages, we are going to create a Custom Metadata Type that handles the relationship between the Trigger Handler and its various Trigger Consumers. Each record will define the following:

  • Trigger Handler Name: the name of the Trigger Handler class this configuration is tied to
  • Trigger Consumer Name: the name of the Trigger Consumer that the above Trigger Handler should call
  • Execution Order: the order in which this Trigger Consumer should be executed when the above Trigger Handler is called
When we create a Trigger Consumer in a child package, we will need to include a record of this custom metadata type as well in order to tell the trigger handler to run the trigger consumer.

Finally, we need to define an Apex Interface. This is what allows us to dynamically call the Trigger Consumers from a Trigger Handler.

In order to hold all the Trigger Context Variables we need to enable our trigger logic, we'll use the following wrapper class:

And there you have it! Once you implement this design pattern, you'll be able to mix and match child packages while maintaining a single trigger per object. Happy coding!


Popular posts from this blog

Using SFDX & the Metadata API to Compare Page Layouts Between Salesforce Orgs