This is the next in the series of FFLIB posts in which I’m trying to cover in the upcoming months. In my last post I covered the selector layer and how it can really help centralise your queries in your application. The next logical layer in FFLIB to cover now is the unit of work pattern

The unit of work pattern (UOW) is aimed at solving some of the common design issues in Salesforce applications when dealing database operations and bringing additional functionality to make your life as a developer much easier.

Unit of work

In this post we’re going to cover the basic usage of the UOW pattern from FFLIB and how it can be utilised. Going forward I’ll be covering how to set up your application to be truly scalable and how best to use the UOW pattern.

Just like my previous posts so far have been, we’ll only be focusing on just how to use this particular layer within the enterprise design patterns. Certain aspects are not best practice on how to use the framework, but are here to help you gain a better understanding of the functionality. Soon I’ll be adding posts on how to bring all of the layers together to get a great structure which is scalable.

So what is it?

UOW pattern is an approach to centralise all database activities into a single wrapper class which manages transaction management, populating of lookup fields automatically and allowing developers to safely commit the work without disrupting other layers within the application, whilst also removing duplication throughout the whole code base by removing repetitive boiler plate code.

Order of sObject’s

In most Salesforce implementations we have to insert, update and delete sObjects in a particular order based on their type. For example, you would normally insert an account and then it’s related opportunities. The reason is simple, we need the account ID in order to correctly link the opportunity to the account.

To use the UOW pattern we also need to instruct it which order of sObjects should be worked with first before working with others.

fflib_ISObjectUnitOfWork uow = new fflib_SObjectUnitOfWork(
   new List<Schema.SObjectType>{
      Account.sObjectType,
      Contact.sObjectType,
      Opportunity.sObjectType,
      OpportunityLineItem.sObjectType
   }
);

Above you can see we added first the account as it is needed to be inserted first in order to populate the lookup ID’s on the contact and opportunity sObjects. Likewise, opportunity line item is defined last as we’re dependent on the account and opportunity records being inserted first.

This means no matter what order we add records to the UOW layer, they will be inserted, updated or deleted in the correct order we originally defined.

Populating relationship fields

It is quite a common scenario for developers to have to code is the populating of relationship fields on child records with their parent records newly inserted ID. Look at the following example, does it look familiar?

public PageReference save(){
   // Create the account and insert it immediately
   // so we can get the ID of the record
   Account a = new Account(Name = 'Quirky Apex');
   insert a;
   
   // Create the contacts and link it to the account
   List<Contact> contacts = new List<Contact>();

   for(Integer i=0; i < 10; i++){
      Contact contact = new Contact(
         LastName = 'QA' + i,
         AccountId = a.Id
      );
      contacts.add(contact);
   }
   
   // Insert all of the contacts
   insert contacts;

   return null;
}

If it does, then I suspect that you have duplicated code within your project doing the same repeating thing over and over again! This is precisely where the unit of work pattern can really help you. Now take a look at this example.

public PageReference save(){
   // Set up the unit of work
   fflib_ISObjectUnitOfWork uow = new fflib_SObjectUnitOfWork(
      new List<Schema.SObjectType>{
         Account.sObjectType,
         Contact.sObjectType
      }
   );

   // Create the account and register it as a 
   // new record to be inserted
   Account a = new Account(Name = 'Quirky Apex');
   uow.registerNew(a);

   for(Integer i=0; i < 10; i++){
      // Create the contact and link it to the account through
      // the AccountId field on the contact sObject
      Contact contact = new Contact(
         LastName = 'QA' + i,
         AccountId = a.Id
      );
      uow.registerNew(contact, Contact.AccountId, a);
   }

   uow.commitWork();
   return null;
}

The first thing you may notice is that there is a little more code involved. As mentioned earlier, this is a basic introduction on how to use UOW. The amount of code we have to use to use UOW can be reduced, but that is for a future post.

Let’s recap and go over what he code example is doing:

  • We create a new instance of the unit of work layer.
  • A new account record is created, but not yet inserted.
  • The unit of work instance is requested to register a new record for insertion.
  • A new contact record is created, but not yet inserted.
  • The contact is now registered as a new record to be inserted, but is pointing towards the account.
  • Finally, the unit of work is asked to commit the work.

The linking of the child to the parent record is really simple and so much less complicated.

Transaction control

When we have to perform multiple DML operations against the database it is important to have good transaction management. What do I mean by this? Let’s take a look at the following block of code.

try {
   insert account;
   insert contacts;
   insert opportunities;
} catch(Exception ex){
   ApexPages.addMessages(ex);
}

You can see that we first insert the accounts, then the contacts and finally the opportunities. It’s all enclosed within a try catch block. In this example you can see that when a DML exception is thrown it’s caught and added to the page messages. Nice and exactly what we want.

But, is it?

If any of the insert’s fail and throw a DML exception we would ideally not like any of the records to be committed. In the example code above a DML exception could thrown from any of the three operations. When Salesforce executes the code in the try block it will keep executing every line until an exception is encountered and then will transfer execution over to the catch block to resume executing. The lines before the exception are still successfully executed and are not rolled back. So if we encountered an exception when inserting the opportunities, then the accounts and contacts would still be inserted.

There is a solution to this and it’s save points. A save point is a roll back point where the database can “undo” all operations which took place after this point in time. Using our example above we can set a save point before we start to insert into the database and rollback in the event of an exception. Simple. The new code below demonstrates how you can achieve this.

SavePoint sp = Database.setSavePoint();

try {
   insert account;
   insert contacts;
   insert opportunities;
} catch(Exception ex){
   Database.rollback(sp);
   ApexPages.addMessages(ex);
}

As applications grow in size this type of issue can become more frequent as it can be easy to miss these small important pieces. Additionally, there are a couple more issues which need to be addressed when it comes to transaction management.

The first, as we’ll be covering later, we cannot perform any type of DML before a call out takes place. If that happens an exception will be thrown. Save points also count as DML so we need to be careful where we use them just like normal DML to avoid hitting limits.

The second issue is performance. When there are several dependent layers in your application and they are all having their own save points and rollbacks, it’s very expensive on the database to manage all of those.

Lastly, all DML statements are wrapped in try catch block and if any exception is thrown, it will roll back everything! This he super useful as you can always be reassured that you will not have an inconsistent database.

Uncommitted work exceptions

One platform limit which can be very frustrating to work around is the limitation of not being able to perform any sort of DML before invoking a web service callout. Any attempt to do so will result in the Uncommitted work pending exception.

These exceptions can be avoid by passing the UOW instance around our application and calling the commitWork method at a safe point. To illustrate how this can be achieved I’d like to cover a common scenario I’ve come across is where all HTTP callouts are performed in a dedicated class which also writes to the database to log the request and responses.

First we start off with a controller method which starts with setting up the UOW layer and then requests for two callouts to be made via dedicated classes.

public PageReference save(){
   // Set up the unit of work
   fflib_ISObjectUnitOfWork uow = new fflib_SObjectUnitOfWork(
      new List<Schema.SObjectType>{
         Log__c.sObjectType
      }
   );
   
   // Make a callout and pass the instance of the uow
   MyCalloutClass.doCallout(uow);
   AnotherCalloutClass.doCallout(uow);
   
   // Commit all of the work at a safe moment
   uow.commitWork();
}

Let’s take a look at the MyCalloutClass.

public class MyCalloutClass {

   public static void doCallout(fflib_ISObjectUnitOfWork uow){
      HttpRequest request = new HttpRequest();
      request.setEndpoint('http://my.endpoint.com');

      uow.registerNew(
         new Log__c( Request__c = request.toString() )
      );
      
      HTTP http = new HTTP();
      HttpResponse response = http.send(request);

      uow.registerNew(
         new Log__c( Response__c = response.toString() )
      );
   }

}

Now let’s also imagine the AnotherCalloutClass class does the exact same as the above class, but makes a different callout. If we didn’t use the UOW pattern and inserted the logs immediately after the initial callout, then the second attempt to make a callout would fail due to the uncommitted work pending exception being thrown.

The UOW pattern allows us as developers to control exactly when to request to commit the work without disrupting other areas in our applications.

Boundary use cases

The pattern isn’t designed to cover every conceivable different scenario we have to work with on the platform, but it probably does cover a good 90%.

When the pattern doesn’t exactly fit into your scenario it’s perfectly fine to alter the way you code. Don’t try and bend the tool into something it wasn’t designed for. It’s a tool to help relieve some of the issues we normally have to code for.