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.
November 29, 2017 at 7:29 pm
Great overview – much easier to read and useful than the existing SF trailhead.
LikeLike
November 29, 2017 at 7:48 pm
Thanks Alec!
LikeLike
January 26, 2018 at 12:14 pm
Line –> uow.registerNew(contact, Contact.AccountId, a) is wrong. If put “contact” as name os the object, apex cant difference between the class name and the object name. If put instead “contacto”, the problem is solved.
LikeLike
January 26, 2018 at 2:55 pm
Thanks for the feedback. Which language are you using within your org? I suspect this is a language issue, the line looks fine, but I’m an English user.
LikeLiked by 1 person
July 30, 2020 at 11:20 am
Great overview, helped a lot to understand the underlying principles better.
Came across your post while trying to understand the “Apply Unit of Work Principles in Apex” trail of SF. Hopefully you can help me understand one little thing: If I want to insert some accounts, contacts (related to these accounts) and notes (related to these contacts) the order in the UnitOfWork constructor doesn’t seem to make a difference concerning Accounts:
Account, Contact, Note works as well as Contact, Note, Account
The first is how I would have understood it. I really don’t get, why the second order works also.
LikeLike
August 1, 2020 at 10:09 am
Thanks Bernd!
If I understand correctly, your question is why if I add records to a unit of work do they all get inserted in the right order?
If that’s the case, then the reason is that the unit of work (UOW) is configured in your main application class the order in which sObject types should be processed. Take a look at the following sample:
https://github.com/rodriguezartav/fflib-simple-sample/blob/master/src/classes/t3_Application.cls
When you add a record to a UOW the framework can determine the type of sObject it is and will place it into a “bucket” of records of the same type. At the end when you call the uow.commitWork(); method the framework will run through the order of sObject types you defined and then will process all records stored in those “buckets” together.
So what this means is that it doesn’t matter which order you add records to your UOW, the framework will figure out the correct order for you based on the order you want in your application.
If I didn’t manage to answer your question correctly, please let me know and I’ll be happy to help.
LikeLike
August 5, 2020 at 8:01 am
Thanks alot for your answer. I guess you are talking about about the order of register-calls. This part I get. But the configuration in the constructor still leaves me puzzled.
Regarding the example in the trail challenge I get, that I have to configure the order correctly in the constructor. An Account needs to be there first, so a contact can relate to it. The same applies to Contact and Note. But I found out, that I can also list the Account as the last of the three SObjectTypes (I hope the following code is readable…)
// Works and represents the way I understood it:
fflib_SObjectUnitOfWork uow = new fflib_SObjectUnitOfWork(
new Schema.SObjectType[] {
Account.SObjectType,
Contact.SObjectType,
Note.SObjectType });
// Also works, but I don’t get why
fflib_SObjectUnitOfWork uow = new fflib_SObjectUnitOfWork(
new Schema.SObjectType[] {
Contact.SObjectType,
Note.SObjectType,
Account.SObjectType });
LikeLike
August 5, 2020 at 11:40 am
The sObjects in this particular case could be inserted how you’ve shown in your second example, though I’m curious to see if the relationship fields are correctly populated. Is this the case for you? Would you be able to share a more complete code example?
LikeLike
August 5, 2020 at 1:34 pm
You are right, relationships are not correctly populated in the second example. The challenge in the trail doesn’t seem to check the relationships. Instead it is satisfied as long as the asserts don’t fail. These however only check for the total inserted objects:
System.assertEquals(100, [Select Id from Account].size());
System.assertEquals(500, [Select Id from Contact].size());
System.assertEquals(500, [Select Id from Note].size());
Thank you very much for your time and help.
LikeLike
August 5, 2020 at 1:49 pm
You’re very welcome! Best of luck!
LikeLike
October 2, 2020 at 9:22 am
Hello Adam,
Thanks for the clear explanation! Taking the UoW pattern one step further, could it be used in a LWC to allow us to run some basic validation of our data before we save it to the database?
For example, we have a product and we want to give a discount. Every time that we alter the discount, the total price should be changed in our screen and the appropriate warnings (if any) should appear. Once we finish our modifications and we are sure about the discounts we want to give (in a list of products), we can save everything to the DB once.
LikeLike
October 2, 2020 at 9:42 am
Hello Stelios,
You may have to achieve this in a few different combined ways. Please note, I’ve now tested or validated the approach.
Perhaps this class may help with the total price calculation: https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_class_System_Formula.htm
To see any validation errors I would keep this to when you actually save and then present the errors back to the user.
Hope this helps.
LikeLike
October 5, 2020 at 10:55 am
Thank you for your reply Adam! The formula class indeed seems a nice suggestion.
LikeLike
October 5, 2020 at 10:56 am
No problem! Best of luck and let me know if it doesn’t work out for you.
LikeLike