Salesforce Design Patterns - Unit Of Work
Written by Zackary Frazier, posted on 2024-12-15
- SALESFORCE
- TESTING
Foreword
It's really unfortunate that although there are those who contribute to the Salesforce open source ecosystem, most Salesforce open source work gets forgotten. Unless some archivist dives through their past work and finds it, there's no central repository of previously completed work to be found. It feels like it's required to describe and re-describe tools that have existed for over a decade because otherwise no one would know what these things are.
The Unit of What?
Dreamforce in 2012 was an exciting time to be a Salesforce developer. This was the year the Apex common library was first demo'd, displaying that Salesforce doesn't have to be a flustercluck of random wonton Apex code yeeted into production with no overarching organization. It was groundbreaking.
Though a bit dated by today's standard, one component that did stand the test of time was the Unit of Work as implemented by Anthony Fawcett.
A Unit of Work keeps track of everything you do during a business transaction that can affect the database. When you're done, it figures out everything that needs to be done to alter the database as a result of your work.
- Martin Fowler
Standalone Implementation
Unfortunately, with all due respect, the version implemented by Anthony Fawcett is a bit complicated. It requires a lot of configuration and setup to use, and to get the most out of it, the entire codebase would need to be refactored.
This isn't necessarily a bad thing, but as someone who's worked as a consultant, refactoring a client's entire codebase to use a new framework is a bold suggestion to say the least.
What's really needed is a plug-and-play Unit of Work implementation that can be dropped into any org and immediately used with minimal configuration.
Thus I present you, the standalone Unit of Work.
This implementation was built to be installable into any org without any necessary configuration, with sensible defaults. It is still configurable, but it was built to work out of the box.
For those who know the Common Library implementation, the methods contain a similer naming convention.
- registerClean
- registerDelete
- registerUndelete
- registerDirty
- commitWork
The registry methods are used to register upserts, deletes, and undeletes. The registerDirty method registers parent-child relationships, where the order of the upsert matters.
The commitWork()
method commits all registered DML interactions into Salesforce. On failure, it rolls back to the point that the unit of work was instantiated, making it's database interactions atomic.
In an ideal world, the entirety of the transaction would only be committed at the end of the Apex transaction. However, I understand that this may not be realistically doable. So I designed this such that commitWork()
can be called multiple times, and at any point of failure, it rolls back to the original save point.
Stubbed Unit of Work
One massive benefit of the Unit of Work is that, short of stubbing the entire database, it's one of the few ways to implement real unit tests in Salesforce. With all DML being centralized in one Apex class, by stubbing that one Apex class, you can test your code without having to interact with the Database. This is huge. DML operations are the biggest pain point in Salesforce execution time.
DML operations executed in tests classes are why deployments on small orgs can take over two hours, and also crash for no apparent reason.
For this reason, this implementation also includes a stubbed unit of work class for unit tests. It will return the results it's configured to return, based on values passed into it's constructor. It can return success or failure messages, based on what's required for your tests. It can be passed into your controllers and trigger handlers via dependency injection, using the IUnitOfWork interface as a foundation.
This handles DML stubbing, to stub SOQL, there are three options I know of, but that goes a bit outside the scope of this article. However, I will drop links to tried and true solutions to stubbing SOQL.