UnitOfWork - wimvelzeboer/fflib-apex-extensions GitHub Wiki
Unit of Work is the concept related to the effective implementation of the repository pattern. non-generic repository pattern, generic repository pattern. Unit of Work is referred to as a single transaction that involves multiple operations.
In the Apex Enterprise Patterns there are two type of Unit Of Work
-
fflib_UnitOfWork
A generic pattern executing work items that can contain any logic -
fflib_SObjectUnitOfWork
A pattern specifically designed to handle database operations like Insert/Update/Delete/Publish/etc
The unit of work is designed to execute classes containing logic as a batch in a single call. These work items can be executed in realtime or as Queued Apex. When queued each work item can be executed in its own queued context or multiple work items in a single queued context as a separate UnitOfWork. It is also possible to chain multiple work items or UnitOfWorks to be executed in a queued context.
Scenario 1 | Scenario 2 | Scenario 3 | | (realtime) | (realtime) (queued) | (realtime) (queued) | | +- UOW -+ | +- UOW -+ | +- UOW -+ | item | | | item | | +-------+ | item | | | item | | +- UOW -+ | item | | +-------+ | | item | +-------+ | +- UOW -+ | | item | | | item | | +-------+ | | item | | | +-------+ | +-Item -+
The UnitOfWork accepts a number of work items and will execute them in a single batch.
In the following example you see two implementations of fflib_IDoWork
being added to the queue.
The two items are executed when the doWork()
method is invoked.
new fflib_UnitOfWork()
.addWork(new MyWorkItemA())
.addWork(new MyWorkItemB())
.doWork();
If we want a particular order of execution, then a priority can be added.
The item with the highest priority will be executed first.
Eventhough the MyWorkItemB
was added last in the list, it will be executed first as the priority "1" is higher then the default ("0") priority of MyWorItemA
.
new fflib_UnitOfWork()
.addWork(new MyWorkItemA())
.addWork(new MyWorkItemB().setPriority(1))
.doWork();
Work items can be added in the following ways to a UnitOfWork:
-
Add a single work item when constructing the UnitOfWork
fflib_IUnitOfWork uow = new fflib_UnitOfWork( new MyWorkItem() );
-
Adding a list of work items when constructing the UnitOfWork
List<fflib_IDoWork> workItems = new List<fflib_IDoWork>{ MyWorkItemA, MyWorkItemB };
fflib_IUnitOfWork uow = new fflib_UnitOfWork( workItems );
-
Add a single work item to an instance of the UnitOfWork
fflib_IUnitOfWork uow =
new fflib_UnitOfWork()
.addWork(new MyWorkItemA());
-
Adding a list of work items to an instance of the UnitOfWork
List<fflib_IDoWork> workItems = new List<fflib_IDoWork>{ MyWorkItemA, MyWorkItemB };
fflib_IUnitOfWork uow =
new fflib_UnitOfWork()
.addWork(workItems);
All the work items marked as queueable as gathered from the work queue and bundled together in a separate UnitOfWork. That UnitOfWork is executed in a single queueable context.
Let’s look at the following example where we create a UnitOfWork and mark two items for Queued execution
List<fflib_IDoWork> workItems = new List<fflib_IDoWork>
{
new MyWorkItemA(),
new MyWorkItemB().enableQueueable(),
new MyWorkItemC(),
new MyWorkItemD().enableQueueable()
}
new fflib_UnitOfWork(workItems).doWork();
In the work queue there will be 4 items. The UnitOfWork looks at them one by one, and will directly execute the non-queued items. It will put the queueable items in another queue for execution at a later stage. When the whole queue is processed, a new UnitOfWork is executed containing the queue with queueable items.
UnitOfWork Queue (Realtime) | (Queued context) | +-- UOW --+ | | item A -|---> doWork() | | | +- Queue -+ | | item B -|----------------> | item B | | | | | | | +-- UOW --+ | item C -|---> doWork() | |====|==> | item B -|---> doWork(); | | | | | | item D -|---> doWork(); | item D -|----------------> | item D | | +---------+ +---------+ +---------+ | |
When a single work item is required to be executed in its own execution context and cannot be combined with other work items, then it needs to be marked as dedicated queueable.
Let’s look this example with a UnitOfWork containing two queued items and one dedicated queueable item.
List<fflib_IDoWork> workItems = new List<fflib_IDoWork>
{
new MyWorkItemA().enableQueueable(),
new MyWorkItemB().enableQueueable(),
new MyWorkItemC().enableQueueable(true) // true = marks the item a dedicated queueable
new MyWorkItemD().enableQueueable(),
}
new fflib_UnitOfWork(workItems).doWork();
Now there will be four items in the work queue. When the UnitOfWork looks at it, it will put the items marked as queueable in another queue and once it finds an item marked for execution in a dedicated queued context, it will directly enqueue the item for execution. Finally it will enqueue another UnitOfWork with the queue of queueable items.
Note
|
Notice in the overview below, that the UnitOfWork (that is executed in realtime) doesn’t do any calls to a doWork(). This is because all the work items are marked as queueable. |
UnitOfWork Queue Realtime | 1st Queued context | 2nd Queued context | | +-- UOW --+ +- Queue -+ | | | item A -|----->| item A | | | | item B -|----->| item B | | | | | | item D | | | | | +---------+ | | | | ^ | | | | | | | | | | item C -|--------------------|--> doWork() | | | | | | | +-- UOW --+ | item D -|---------+ | | | | item A -|---> doWork(); +---------+ +-------|---------------------|---> | item B -|---> doWork(); | | | item D -|---> doWork(); | | +---------+ | |
In some cases the queued work items need to be executed one after the other, the chaining of queueable work items will provide a solution for that.
Let’s look at an example:
new fflib_UnitOfWork()
.addNext( new MyWorkItemA() )
.addNext(
new fflib_UnitOfWork()
.addWork( new MyWorkItemB() )
.addWork( new MyWorkItemC() ) )
.addNext( new MyWorkItemD() )
.doWork();
This creates an UnitOfWork with an empty queue, but with a list of work marked as "do next". Every item will be executed in its own (queued) execution context, one after the other, in the order as they are given to the UnitOfWork.
UnitOfWork Queue Realtime | 1st Queued ctx | 2nd Queued ctx | 3nd Queued ctx | | | +-- UOW ----+ | | | | | | | | | queue is | | | | | empty | | | | +-----------+ | | | | next work | | | | | | | | | | item A -|--|---> doWork() | +-- UOW --+ | | UoW -|--|-----------------|-->| item B -|--> doWork() | | - item B | | | | item C -|--> doWork() | | - item C | | | +---------+ | | | | | | | item D -|--|-----------------|---------------------------|--> doWork() +-----------+ | | | | | |
Since the work items or UnitOfWork are executed in another execution context, exceptions that occur cannot be reported in the "realtime" execution context. Each work item needs to take care of handling and reporting their errors.
Tip
|
You can use the fflib_Logger functionality for error reporting.
|