Unit of Work

The Unit of Work is a design pattern that is used to effectively arrange your code in situations when you have to perform multiple database operations. Instead of writing each DML statement separately and managing multiple collections to handle records, register all of the DML statements in the memory of your running program, then fire them at once at the end. This will allow you to significantly reduce the size of your code and avoid partial database updates. Sounds cool? Let’s see how you can do it.

There is a quite popular open source library: Apex Enterprise Patterns, which contains ready to use Unit of Wrok pattern implementation.

Imagine that you have to deal with the complex database model with several relationships and you try to import new records into the system. This is how the sample json data could look like:

{
    "records": [
        {
            "attributes": {
                "type": "User",
                "referenceId": "UserRef1"
            },
            "FirstName": "Michał",
            "LastName": "Pycek",
            "Accounts": {
                "records": [
                    {
                        "attributes": {
                            "type": "Account",
                            "referenceId": "AccountRef1"
                        },
                        "Name": "Michał Pycek Restaurant",
                        "Opportunities": {
                            "records": [
                                {
                                    "attributes": {
                                        "type": "Opportunity",
                                        "referenceId": "OpportunityRef1"
                                    },
                                    "Name": "Michał Pycek Opportunity"
                                },
                                {
                                    "attributes": {
                                        "type": "Opportunity",
                                        "referenceId": "OpportunityRef2"
                                    },
                                    "Name": "Michał Pycek Opportunity 2"
                                }
                            ]
                        }
                    },
                    {
                        "attributes": {
                            "type": "Account",
                            "referenceId": "AccountRef2"
                        },
                        "Name": "Michał Pycek Cafe",
                        "Opportunities": {
                            "records": [
                                {
                                    "attributes": {
                                        "type": "Opportunity",
                                        "referenceId": "OpportunityRef3"
                                    },
                                    "Name": "Michał Pycek Opportunity",
                                    "Quotes": {
                                        "records": [
                                            {
                                                "attributes": {
                                                    "type": "Quote",
                                                    "referenceId": "Quote1"
                                                },
                                                "Name": "Michał Pycek Quote"
                                            },
                                            {
                                                "attributes": {
                                                    "type": "Quote",
                                                    "referenceId": "Quote2"
                                                },
                                                "Name": "Michał Pycek Quote 2"
                                            }
                                        ]
                                    }
                                }
                            ]
                        }
                    }
                ]
            }
        }
    ]
}

You can see that there is a 3 level deep relationship: User -> Accounts -> Opportunities -> Quotes and just a few sample records.

Let’s take a look at the code that is written without Unit of Work pattern which struggles to achieve the goal:

public static void importSampleData(String jsonData) {

    // Parse JSON data
    SampleData sampleData = (SampleData) JSON.deserializeStrict(jsonData, SampleData.class);

    // Insert Users
    Map<String, User> usersById = new Map<String, User>();
    for(UserData userData: sampleData.users) {
        usersById.put(userData.referenceId, new User(
            FirstName = userData.FirstName,
            LastName = userData.LastName
        ));
    }
    insert usersById.values();

    // Insert Accounts
    Map<String, Account> accountsById = new Map<String, Account>();
    for(UserData userData: sampleData.users) {
        for(AccountData accountData: userData.accounts) {
            accountsById.put(accountData.referenceId, new Account(
                Name = accountData.Name,
                UserId = usersById.get(userData.referenceId).Id
            ));
        }
    }
    insert accountsById.values();

    // Insert Opportunities
    Map<String, Opportunity> opportunitiesById = new Map<String, Opportunity>();
    for(UserData userData: sampleData.users) {
        for(AccountData accountData: userData.accounts) {
            for(OpportunityData opportunityData: accountData.opportunites) {
                opportunitiesById.put(opportunityData.referenceId, new Opportunity(
                    Name = opportunityData.Name,
                    AccountId = accountsById.get(accountData.referenceId).Id
                ));
            }
        }
    }
    insert opportunitiesById.values();

    // Insert Quotes
    Map<String, Quote> quotesById = new Map<String, Quote>();
    for(UserData userData: sampleData.users) {
        for(AccountData accountData: userData.accounts) {
            for(OpportunityData opportunityData: accountData.opportunites) {
                for(QuoteData quoteData: opportunityData.quotes) {
                    quotesById.put(quoteData.referenceId, new Quote(
                        Name = quoteData.Name,
                        OpportunityId = opportunitiesById.get(opportunityData.referenceId).Id
                    ));
                }
            }
        }
    }
    insert quotesById.values();

}

You can clearly see that the code can quickly resemble spaghetti and if we would have a more complex data model to process it would be even worse. Over time we would add more and more loops and collections like Maps to the class to match newly created records ids with the rest of the related records. Hopefully there is a way to make it easier and keep your code clean at the same time. We just need to separate the actual DML statements from the logic and run all of them at the end as single Unit of Work.

Let’s take a look at the refactored version of the previous code:

public static void importSampleData(String jsonData) {

    // Parse JSON data
    SampleData sampleData = (SampleData) JSON.deserializeStrict(jsonData, SampleData.class);

    // Construct a Unit Of Work
    fflib_ISObjectUnitOfWork uow = Application.UnitOfWork.newInstance();

    // Create User records
    for(UserData userData: sampleData.users) {
        User user = new User(
            FirstName = userData.FirstName,
            LastName = userData.LastName
        );
        uow.registerNew(user);
        // Create Account records
        for(AccountData accountData: userData.accounts) {
            Account acc = new Account(
                Name = accountData.Name
            ));
            uow.registerNew(acc, Account.User, user);
            // Create Opportunity records
            for(OpportunityData opportunityData: accountData.opportunites) {
                Opportunity opp = new Opportunity(
                    Name = opportunityData.Name
                ));
                uow.registerNew(opp, Opportunity.Account, acc);
                // Create Quote records
                for(QuoteData quoteData: opportunityData.quotes) {
                    Quote quote = new Quote(
                        Name = quoteData.Name
                    ));
                    uow.registerNew(quote, Quote.Opportunity, opp);
                }
            }
        }
    }

    uow.commitWork();
}

Notice that the refactored version of the same solution is not only shorter but much easier to read and mantain in the future.

I hope that by this simple example you learned about the benefits of isolating parts of your code into the independent layers which is a key to any good software architecture.