In my previous blog post I gave a basic introduction as to what FFLIB is and why it’s a good idea to use such a framework in your project.

This time I’d like to give a more technical introduction into the domain layer and give examples on how to use it.

What is the domain layer again?

This is the layer in your application where you’ll manipulate or work directly with sObjects. If you’re used to using a trigger framework then the domain layer is similar idea, but is also tries to help keep your logic deduplicated and central.

In the domain layer you’ll have a separate class for each of the sObjects you need to write custom logic for. It’s also a good moment to discuss naming conventions at this point. Name your domain classes as plural of the name of the sObject they are associated with. For example, the domain class for the Opportunity object would be called Opprtunities. Why? It encourages the conveying of working in bulk, not singular records.

What does the domain layer in FFLIB offer?

You get out of the domain layer a good framework for handling and routing of trigger events, a structure for validating records and the possibility to extend with your own logic which you may wish to use elsewhere in your application.

As per best practices in Salesforce, there should only ever be one trigger per sObject and one corresponding trigger handler class. The same applies and is encouraged in FFLIB.

Setting up the domain layer

We’ll be setting up a domain layer class in the next section to work with opportunities.

The first thing which we need to do is to create our Opportunties class which will extend the fflib_SObjectDomain class.

public class Opportunities extends fflib_SObjectDomain {

}

Extending the class fflib_SObjectDomain defines the class as a domain layer and provides additional functionality specific to this layer.

Next we need to add in a bit of boilerplate code due to Apex not fully supporting reflection. We need to add the code to allow the framework to redirect the list of sObjects being worked on back into the constructor.

public class Constructor implements fflib_SObjectDomain.IConstructable {
   public Opportunities(List<Opportunity> records){
      super(records);
   }

   public class Constructor implements fflib_SObjectDomain.IConstructable {
      public fflib_SObjectDomain construct(List<SObject> records){
         return new Opportunities(records);
      }
   }
}

What happens is that when the framework comes to work with the domain class it will automatically find the inner-class and instantiate it, and as it implements fflib_SObjectDomain.IConstructable it will be able to call the construct method. The purpose of the construct method is then to instantiate the class directly and pass the list of records into the constructor.

The constructor can then pass the records into the super class so that we can then access the records from anywhere within our domain class.

public Opportunities(List<Opportunity> records){
   super(records);
}

At this point we have a very simple domain layer which doesn’t do anything exciting yet, but we are ready to use it. The next task is to update our opportunity trigger to point to our new domain class.

trigger OpportunitiesTrigger on Opportunity (
   after delete,
   after insert,
   after update,
   before delete,
   before insert,
   before update){
   fflib_SObjectDomain.triggerHandler(Opportunities.class);
}

Validation

When saving records it is a common activity to validate records inside triggers. FFLIB offers two methods for validating records; one for when creating and the other for all other trigger events.

These two methods are called automatically by the framework and before executing any custom logic within the trigger event handler methods (more on these shortly).

Validating new records

If you need to validate only records when they are inserted then override the zero-argument method onValidate, such as the example below.

public override void onValidate(){
   for(Opportunity record : (List<Opportunity>) records){
      if(record.IsClosed && record.isWon){
         record.addError('Cannot a create closed won opportunity');
      }
   }
}

Validating existing records

Unlike validation of new records, often when validating existing records we need access to the original stored values in order to do some sort of comparison between the old and new.

public override void onValidate(Map<Id, SObject> previousMap){
   for(Opportunity record : (List<Opportunity>) records){
      Opportunity previous = previousMap.get(record.Id);

      if(previous.IsClosed && !record.isClosed){
         record.addError('Cannot a create closed won opportunity');
      }
   }
}

In the above example we are able to compare the current opportunity’s against their original values and if required add an error to the record.

Applying defaults when creating records

The framework also provides the ability to set the default values for records when inserting into the database. So rather than intertwining this within your business logic you can define this in a central place within the domain class.

public override void onApplyDefaults(){
   for(Opportunity record : (List<Opportunity>) records){
      // Set the type if the user hasn't selected one yet
      if(record.Type == null){
         record.Type = 'Unknown';
      }

      // Override the stage name and set back to the default
      record.StageName = 'Working';
   }
}

Handling trigger events

There are the following trigger events available:

  • Before insert
  • Before update
  • Before delete
  • After insert
  • After update
  • After undelete

You can define trigger event handler methods within your domain class to hook into those events to work with the records. The method names are defined as such:

  • onBeforeInsert
  • onBeforeUpdate
public override void onBeforeUpdate(){
   for(Opportunity record : (List<Opportunity>) records){
      Opportunity previous = (Opportunity) previousMap.get(record.Id);

      if(previous.IsClosed && !record.isClosed){
         record.addError('Cannot a create closed won opportunity');
      }
   }
}

Conclusion

This is only a small introduction to the domain class and there are certain aspects which we need to cover again in more detail next time. In the upcoming posts I’ll be showing how to improve upon adding error messages to records to aid unit testing and how you can mix in using services and the unit of work pattern.

If you want to skip ahead and find more complete examples on how to use the domain layer in FFLIB check out Financialforces’ sample code Github repository.