diff --git a/README.md b/README.md
index efb3821..f446ea6 100644
--- a/README.md
+++ b/README.md
@@ -6,6 +6,16 @@ This extension package is dependent on the following packages:
- [fflib-apex-mocks](https://github.com/apex-enterprise-patterns/fflib-apex-mocks)
- [fflib-apex-common](https://github.com/apex-enterprise-patterns/fflib-apex-common)
+## Extensions pack contents
+These features are offered in this extension package:
+
+- [**Application class redesigned**](https://github.com/wimvelzeboer/fflib-apex-extensions/wiki/application-factories)
A Force-DI like feature using dependency injection with the Separation of Concerns design pattern.
+- [**Persitant Logger**](https://github.com/wimvelzeboer/fflib-apex-extensions/wiki/Logger)
+Ever want to log error messages in a Custom Object? The fflib_Logger will do it for you, even if you have a database-rollback.
+- [**Extended domain features**](https://github.com/wimvelzeboer/fflib-apex-extensions/wiki/Domain-Layer)
Tons of extra methods in the domains to access the (S)Objects.
+- [**Criteria evaluator**](https://github.com/wimvelzeboer/fflib-apex-extensions/wiki/criteria-based-filter)
Ability to use the same criteria filters for domains and selectors. Build SOQL WHERE clauses in an object-oriented manner. It can also evaluate a list of records against predefined conditions "(1 AND 2 AND (3 OR 4))".
+- [**Generic UnitOfWork**](https://github.com/wimvelzeboer/fflib-apex-extensions/wiki/UnitOfWork)
Ever want to execute a bunch of work items in a dynamic manner, and be able to control their order of execution, and run some of it in a separate execution context? Wow... that's a lot to ask.. But, the UnitOfWork will do that for you!
Be ware, this fflib_**UnitOfWork** is something different than the fflib_**SObject**UnitOfwork
+
## Wiki
A full overview of the features in this extension pack and how to use them
can be found in [the Wiki pages of this repository](https://github.com/wimvelzeboer/fflib-apex-extensions/wiki).
@@ -40,6 +50,7 @@ Some of the changes that have major impact are listed here;
### October 2021
- **Logger functionality**
A replacement of System.Debug that is using Platform events to store log messages in a Custom Object for you to monitor
- **API Upgrade to Winter'22**
All Apex classes are upgraded to API 53.0
+- **UnitOfWork**
Generic UnitOfWork, capable of executing work items in batch at a single instance. Each item will be executed based on its priority and can be queued in a separate execution context when necessary.
### August 2021
**Added criteria based on formula; (1 OR 2) AND 3)**
diff --git a/sfdx-source/apex-extensions/main/unitOfWork/classes/fflib_DoWork.cls b/sfdx-source/apex-extensions/main/unitOfWork/classes/fflib_DoWork.cls
new file mode 100644
index 0000000..94530ce
--- /dev/null
+++ b/sfdx-source/apex-extensions/main/unitOfWork/classes/fflib_DoWork.cls
@@ -0,0 +1,113 @@
+/**
+ * File Name: fflib_DoWork
+ * Description: Abstract class containing generic functionality to order, prioritise and define an execution context for the work item
+ *
+ * @author architect ir. Wilhelmus G.J. Velzeboer
+ *
+ * Copyright (c), W.G.J. Velzeboer,
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above author notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * - Neither the name of the author nor the names of its contributors
+ * may be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+public abstract class fflib_DoWork implements fflib_IDoWork, Comparable
+{
+ protected Integer priority = 0;
+ protected Boolean dedicatedQueueable;
+
+ public virtual Integer compareTo(Object compareToObject)
+ {
+ fflib_IDoWork compareTo = (fflib_IDoWork) compareToObject;
+ if (priority == compareTo.getPriority()) return 0;
+ if (priority > compareTo.getPriority()) return -1;
+ return 1;
+ }
+
+ /**
+ * Enable the work item to be executed in separate UnitOfWork executed in a Queueable context
+ *
+ * @return Instance of itself to allow for chaining
+ */
+ public virtual fflib_IDoWork enableQueueable()
+ {
+ return enableQueueable(false);
+ }
+
+ /**
+ * Enable the work item to be executed as Queueable, not combined with other work items
+ *
+ * @param dedicated When true, execute in a dedicated Queueable context.
+ *
+ * @return Instance of itself to allow for chaining
+ */
+ public virtual fflib_IDoWork enableQueueable(Boolean dedicated)
+ {
+ dedicatedQueueable = dedicated;
+ return this;
+ }
+
+ /**
+ * Gets the priority of this work item. The higher the number the higher the priority
+ *
+ * @return Returns the priority
+ */
+ public virtual Integer getPriority()
+ {
+ return this.priority;
+ }
+
+ /**
+ * @return Returns true when the work item is intended to run in real-time
+ */
+ public virtual Boolean isNotQueueable()
+ {
+ return !isQueueable();
+ }
+
+ /**
+ * @return Returns true when the work item is intended to be executed in separate UnitOfWork executed in a Queueable context
+ */
+ public virtual Boolean isQueueable()
+ {
+ return (dedicatedQueueable != null);
+ }
+
+ /**
+ * @return Returns true when the work item is intended to be executed as Queueable, not combined with other work items
+ */
+ public virtual Boolean isDedicatedQueueable()
+ {
+ return (dedicatedQueueable != null && dedicatedQueueable);
+ }
+
+ /**
+ * Sets the priority value of the work item, this is used to sort the work items
+ *
+ * @param priority The priority value, the working with the highest priority value will be executed first
+ *
+ * @return Instance of itself to allow for chaining
+ */
+ public virtual fflib_IDoWork setPriority(Integer priority)
+ {
+ this.priority = priority;
+ return this;
+ }
+}
\ No newline at end of file
diff --git a/sfdx-source/apex-extensions/main/unitOfWork/classes/fflib_DoWork.cls-meta.xml b/sfdx-source/apex-extensions/main/unitOfWork/classes/fflib_DoWork.cls-meta.xml
new file mode 100644
index 0000000..f928c8e
--- /dev/null
+++ b/sfdx-source/apex-extensions/main/unitOfWork/classes/fflib_DoWork.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 53.0
+ Active
+
diff --git a/sfdx-source/apex-extensions/main/unitOfWork/classes/fflib_UnitOfWork.cls b/sfdx-source/apex-extensions/main/unitOfWork/classes/fflib_UnitOfWork.cls
new file mode 100644
index 0000000..3ba9f17
--- /dev/null
+++ b/sfdx-source/apex-extensions/main/unitOfWork/classes/fflib_UnitOfWork.cls
@@ -0,0 +1,253 @@
+/**
+ * File Name: fflib_UnitOfWork
+ * Description: Unit Of Work executing a batch of work items at a single instance
+ *
+ * @author architect ir. Wilhelmus G.J. Velzeboer
+ *
+ * Copyright (c), W.G.J. Velzeboer,
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above author notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * - Neither the name of the author nor the names of its contributors
+ * may be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+public virtual class fflib_UnitOfWork
+ extends fflib_DoWork
+ implements fflib_IUnitOfWork, fflib_IDoWork, Queueable
+{
+ protected List work;
+ protected List doNext;
+
+ public fflib_UnitOfWork()
+ {
+ this(new List());
+ }
+
+ public fflib_UnitOfWork(fflib_IDoWork workItem)
+ {
+ this(new List{ workItem });
+ }
+
+ public fflib_UnitOfWork(List workItems)
+ {
+ this(workItems, new List());
+ }
+
+ public fflib_UnitOfWork(List workItems, fflib_UnitOfWork nextWork)
+ {
+ this(workItems, new List{ nextWork });
+ }
+
+ public fflib_UnitOfWork(List workItems, List nextWork)
+ {
+ this.work = workItems;
+ this.doNext = nextWork;
+ }
+
+ /**
+ * Adds a single work item is executed in a dedicated Queued context after the UnitOfWork is finished
+ *
+ * @param nextWork work item to be executed after the UnitOfWork is finished
+ *
+ * @return Instance of itself to allow for method chaining
+ */
+ public virtual fflib_IUnitOfWork addNext(fflib_IDoWork nextWork)
+ {
+ return addNext((fflib_IUnitOfWork) new fflib_UnitOfWork(nextWork));
+ }
+
+ /**
+ * Adds a UnitOfWork to be executed in a Queued context after the UnitOfWork is finished
+ *
+ * @param nextWork UnitOfWork to be executed after the UnitOfWork is finished
+ *
+ * @return Instance of itself to allow for method chaining
+ */
+ public virtual fflib_IUnitOfWork addNext(fflib_IUnitOfWork nextWork)
+ {
+ return addNext(new List{ nextWork });
+ }
+
+ /**
+ * Adds an ordered list of UnitOfWork to be executed each in a separate Queued context after the UnitOfWork is finished
+ *
+ * @param nextWork UnitOfWork to be executed after the UnitOfWork is finished
+ *
+ * @return Instance of itself to allow for method chaining
+ */
+ public virtual fflib_IUnitOfWork addNext(List nextWork)
+ {
+ doNext.addAll(nextWork);
+ return this;
+ }
+
+ /**
+ * Add a single work item to the queue
+ *
+ * @param work work item to be added
+ *
+ * @return Instance of itself to allow for method chaining
+ */
+ public virtual fflib_UnitOfWork addWork(fflib_IDoWork work)
+ {
+ this.work.add(work);
+ return this;
+ }
+
+ /**
+ * Adds a list of work items to the queue
+ *
+ * @param work work items to be added
+ *
+ * @return Instance of itself to allow for method chaining
+ */
+ public virtual fflib_UnitOfWork addWork(List work)
+ {
+ this.work.addAll(work);
+ return this;
+ }
+
+ /**
+ * Do the work contained in the UnitOfWork
+ */
+ public virtual void doWork()
+ {
+ this.work.sort();
+ doRealTimeWork();
+ doQueuedWork();
+ doNextWork();
+ }
+
+ /**
+ * Execute the work when being invoked from a queued context
+ *
+ * @param ctx QueueableContext
+ */
+ public virtual void execute(QueueableContext ctx)
+ {
+ doWork();
+ }
+
+ /**
+ * Do start with the execution of the next work item to be executed in a Queued context
+ */
+ protected virtual void doNextWork()
+ {
+ if (this.doNext.isEmpty()) return;
+
+ fflib_IUnitOfWork nextWork = this.doNext.remove(0);
+ nextWork.addNext(this.doNext);
+ enqueueJob(nextWork);
+ }
+
+ /**
+ * Put the given UnitOfWork in the Queue for execution
+ *
+ * @param nextWork The UnitOfWork to be executed in a Queued context
+ */
+ protected virtual void enqueueJob(fflib_IUnitOfWork nextWork)
+ {
+ if (Test.isRunningTest())
+ {
+ nextWork.doWork();
+ }
+ else
+ {
+ System.enqueueJob(nextWork);
+ }
+ }
+
+ /**
+ * Put the given work item in the Queue for execution
+ *
+ * @param work The work item to be executed in a Queued context
+ */
+ protected virtual void enqueueJob(fflib_IDoWork work)
+ {
+ if (Test.isRunningTest())
+ {
+ work.doWork();
+ }
+ else
+ {
+ System.enqueueJob(work);
+ }
+ }
+
+ /**
+ * Do start executing the work items marked for execution in a Queued context.
+ * Either in a dedicated Queued context or batched together in another UnitOfWork
+ */
+ protected virtual void doQueuedWork()
+ {
+ if (this.work.isEmpty()) return;
+
+ List workToQueue = new List();
+ for (fflib_IDoWork workItem : work)
+ {
+ if (workItem.isNotQueueable()) continue;
+
+ if (workItem.isDedicatedQueueable())
+ {
+ enqueueJob(workItem);
+ }
+ else
+ {
+ workToQueue.add(workItem.enableQueueable(null));
+ }
+ }
+
+ if (workToQueue.isEmpty()) return;
+
+ enqueueJob((fflib_IUnitOfWork) new fflib_UnitOfWork(workToQueue));
+ }
+
+ /**
+ * Do start executing the work items which are not marked for Queued context.
+ */
+ protected virtual void doRealTimeWork()
+ {
+ if (this.work.isEmpty()) return;
+
+ for (fflib_IDoWork workItem : work)
+ {
+ if (workItem.isQueueable()) continue;
+
+ doWork(workItem);
+ }
+ }
+
+ /**
+ * @param workItem
+ */
+ protected virtual void doWork(fflib_IDoWork workItem)
+ {
+ try
+ {
+ workItem.doWork();
+ }
+ catch (Exception e)
+ {
+ throw new WorkItemException('WorkItem failed to execute with error: ' + e.getMessage() + ' ' + e.getStackTraceString());
+ }
+ }
+
+ public class WorkItemException extends Exception {}
+}
\ No newline at end of file
diff --git a/sfdx-source/apex-extensions/main/unitOfWork/classes/fflib_UnitOfWork.cls-meta.xml b/sfdx-source/apex-extensions/main/unitOfWork/classes/fflib_UnitOfWork.cls-meta.xml
new file mode 100644
index 0000000..f928c8e
--- /dev/null
+++ b/sfdx-source/apex-extensions/main/unitOfWork/classes/fflib_UnitOfWork.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 53.0
+ Active
+
diff --git a/sfdx-source/apex-extensions/main/unitOfWork/classes/interfaces/fflib_IDoWork.cls b/sfdx-source/apex-extensions/main/unitOfWork/classes/interfaces/fflib_IDoWork.cls
new file mode 100644
index 0000000..6480692
--- /dev/null
+++ b/sfdx-source/apex-extensions/main/unitOfWork/classes/interfaces/fflib_IDoWork.cls
@@ -0,0 +1,84 @@
+/**
+ * File Name: fflib_IDoWork
+ * Description: Interface defining the method signatures for a work item used by the UnitOfWork
+ *
+ * @author architect ir. Wilhelmus G.J. Velzeboer
+ *
+ * Copyright (c), W.G.J. Velzeboer,
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above author notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * - Neither the name of the author nor the names of its contributors
+ * may be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+public interface fflib_IDoWork extends Comparable
+{
+ /**
+ * Do the work of this work item
+ */
+ void doWork();
+
+ /**
+ * Enable the work item to be executed in separate UnitOfWork executed in a Queueable context
+ *
+ * @return Instance of itself to allow for chaining
+ */
+ fflib_IDoWork enableQueueable();
+
+ /**
+ * Enable the work item to be executed as Queueable, not combined with other work items
+ *
+ * @param dedicated When true, execute in a dedicated Queueable context.
+ *
+ * @return Instance of itself to allow for chaining
+ */
+ fflib_IDoWork enableQueueable(Boolean dedicated);
+
+ /**
+ * Gets the priority of this work item. The higher the number the higher the priority
+ *
+ * @return Returns the priority
+ */
+ Integer getPriority();
+
+ /**
+ * @return Returns true when the work item is intended to be executed in separate UnitOfWork executed in a Queueable context
+ */
+ Boolean isQueueable();
+
+ /**
+ * @return Returns true when the work item is intended to run in real-time
+ */
+ Boolean isNotQueueable();
+
+ /**
+ * @return Returns true when the work item is intended to be executed as Queueable, not combined with other work items
+ */
+ Boolean isDedicatedQueueable();
+
+ /**
+ * Sets the priority value of the work item, this is used to sort the work items
+ *
+ * @param priority The priority value, the working with the highest priority value will be executed first
+ *
+ * @return Instance of itself to allow for chaining
+ */
+ fflib_IDoWork setPriority(Integer priority);
+}
\ No newline at end of file
diff --git a/sfdx-source/apex-extensions/main/unitOfWork/classes/interfaces/fflib_IDoWork.cls-meta.xml b/sfdx-source/apex-extensions/main/unitOfWork/classes/interfaces/fflib_IDoWork.cls-meta.xml
new file mode 100644
index 0000000..f928c8e
--- /dev/null
+++ b/sfdx-source/apex-extensions/main/unitOfWork/classes/interfaces/fflib_IDoWork.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 53.0
+ Active
+
diff --git a/sfdx-source/apex-extensions/main/unitOfWork/classes/interfaces/fflib_IUnitOfWork.cls b/sfdx-source/apex-extensions/main/unitOfWork/classes/interfaces/fflib_IUnitOfWork.cls
new file mode 100644
index 0000000..276f1ae
--- /dev/null
+++ b/sfdx-source/apex-extensions/main/unitOfWork/classes/interfaces/fflib_IUnitOfWork.cls
@@ -0,0 +1,82 @@
+/**
+ * File Name: fflib_IUnitOfWork
+ * Description: Interface class with method signatures for the Unit Of Work
+ *
+ * @author architect ir. Wilhelmus G.J. Velzeboer
+ *
+ * Copyright (c), W.G.J. Velzeboer,
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above author notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * - Neither the name of the author nor the names of its contributors
+ * may be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+public interface fflib_IUnitOfWork
+{
+ /**
+ * Adds a single work item is executed in a dedicated Queued context after the UnitOfWork is finished
+ *
+ * @param nextWork work item to be executed after the UnitOfWork is finished
+ *
+ * @return Instance of itself to allow for method chaining
+ */
+ fflib_IUnitOfWork addNext(fflib_IDoWork nextWork);
+
+ /**
+ * Adds a UnitOfWork to be executed in a Queued context after the UnitOfWork is finished
+ *
+ * @param nextWork UnitOfWork to be executed after the UnitOfWork is finished
+ *
+ * @return Instance of itself to allow for method chaining
+ */
+ fflib_IUnitOfWork addNext(fflib_IUnitOfWork nextWork);
+
+ /**
+ * Adds an ordered list of UnitOfWork to be executed each in a separate Queued context after the UnitOfWork is finished
+ *
+ * @param nextWork UnitOfWork to be executed after the UnitOfWork is finished
+ *
+ * @return Instance of itself to allow for method chaining
+ */
+ fflib_IUnitOfWork addNext(List nextWork);
+
+ /**
+ * Add a single work item to the queue
+ *
+ * @param work work item to be added
+ *
+ * @return Instance of itself to allow for method chaining
+ */
+ fflib_IUnitOfWork addWork(fflib_IDoWork work);
+
+ /**
+ * Adds a list of work items to the queue
+ *
+ * @param work work items to be added
+ *
+ * @return Instance of itself to allow for method chaining
+ */
+ fflib_IUnitOfWork addWork(List work);
+
+ /**
+ * Do the work contained in the UnitOfWork
+ */
+ void doWork();
+}
\ No newline at end of file
diff --git a/sfdx-source/apex-extensions/main/unitOfWork/classes/interfaces/fflib_IUnitOfWork.cls-meta.xml b/sfdx-source/apex-extensions/main/unitOfWork/classes/interfaces/fflib_IUnitOfWork.cls-meta.xml
new file mode 100644
index 0000000..f928c8e
--- /dev/null
+++ b/sfdx-source/apex-extensions/main/unitOfWork/classes/interfaces/fflib_IUnitOfWork.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 53.0
+ Active
+
diff --git a/sfdx-source/apex-extensions/tests/classes/work/fflib_UnitOfWorkTest.cls b/sfdx-source/apex-extensions/tests/classes/work/fflib_UnitOfWorkTest.cls
new file mode 100644
index 0000000..620490e
--- /dev/null
+++ b/sfdx-source/apex-extensions/tests/classes/work/fflib_UnitOfWorkTest.cls
@@ -0,0 +1,160 @@
+/**
+ * File Name: fflib_UnitOfWorkTest
+ * Description: Unit Test class for fflib_UnitOfWork implementation
+ *
+ * @author architect ir. Wilhelmus G.J. Velzeboer
+ *
+ * Copyright (c), W.G.J. Velzeboer,
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above author notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * - Neither the name of the author nor the names of its contributors
+ * may be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+@IsTest(IsParallel=true)
+private class fflib_UnitOfWorkTest
+{
+ public static List orderOfExecution = new List();
+
+ @IsTest
+ static void itShouldDoAllTheWork()
+ {
+ // GIVEN - Three different work items added to the fflib_DoWork
+ Work workA = new Work();
+ Work workB = new Work();
+ Work workC = new Work();
+ fflib_UnitOfWork myWork = new fflib_UnitOfWork().addWork(
+ new List {workA, workB, workC}
+ );
+
+ // WHEN - The work is being executed
+ System.Test.startTest();
+ myWork.doWork();
+ System.Test.stopTest();
+
+ // THEN - Then all three work items should be executed
+ System.assert(workA.didWork, 'Work item A did not execute');
+ System.assert(workB.didWork, 'Work item B did not execute');
+ System.assert(workC.didWork, 'Work item C did not execute');
+ System.assertEquals(3, [SELECT COUNT() FROM fflib_LogEntry__c]);
+ }
+
+ @IsTest
+ static void itShouldOnlyDoTheRealTimeWorkAndQueueTheOther()
+ {
+ // GIVEN - Three different work items added to the fflib_DoWork
+ Work workA = new Work();
+ Work workB = new Work();
+ Work workC = (Work) new Work().enableQueueable();
+ fflib_UnitOfWork myWork = new fflib_UnitOfWork().addWork(
+ new List {workA, workB, workC}
+ );
+ final Integer numberOfQueueableJobs = Limits.getQueueableJobs();
+
+ // WHEN - The work is being executed
+ myWork.doWork(); // Execute without start-stop test due to Salesforce limitation
+
+ // THEN - Then all three work items should be executed
+ System.assert(workA.didWork, 'Work item A did not execute');
+ System.assert(workB.didWork, 'Work item B did not execute');
+ System.assert(workC.didWork, 'Work item C was executed in realtime while is should be queued');
+ System.assertEquals(3, [SELECT COUNT() FROM fflib_LogEntry__c]);
+ }
+
+ @IsTest
+ static void itShouldExecuteAllTheChainedQueuedWork()
+ {
+ // GIVEN - Three different work items added to the fflib_DoWork to be executed in a chained queueable
+ Work workA = new Work();
+ Work workB = new Work();
+ Work workC = new Work();
+ fflib_IUnitOfWork myWork =
+ new fflib_UnitOfWork()
+ .addNext(workA)
+ .addNext(workB)
+ .addNext(workC);
+
+ final Integer numberOfQueueableJobs = Limits.getQueueableJobs();
+
+ // WHEN - The work is being executed
+ myWork.doWork(); // Execute without start-stop test due to Salesforce limitation
+
+ // THEN - Then all three work items should be executed
+ System.assertEquals(3, [SELECT COUNT() FROM fflib_LogEntry__c]);
+ }
+
+ @IsTest
+ static void itShouldExecuteAllItemsInTheCorrectOrder()
+ {
+ // GIVEN - Three different work items added to the fflib_DoWork
+ Work workA = (Work) new WorkA().setPriority(1);
+ Work workB = (Work) new WorkB().setPriority(2);
+ Work workC = (Work) new WorkC().setPriority(3);
+ fflib_UnitOfWork myWork = new fflib_UnitOfWork().addWork(
+ new List {workA, workB, workC}
+ );
+
+ // WHEN - The work is being executed
+ System.Test.startTest();
+ myWork.doWork();
+ System.Test.stopTest();
+
+ // THEN - Then all three work items should be executed
+ System.assert(orderOfExecution.get(0).contains('WorkC'), 'Expected Work C with priority 3 to be executed first');
+ System.assert(orderOfExecution.get(1).contains('WorkB'), 'Expected Work B with priority 2 to be executed second');
+ System.assert(orderOfExecution.get(2).contains('WorkA'), 'Expected Work B with priority 1 to be executed last');
+ }
+
+ private virtual class Work extends fflib_DoWork
+ {
+ public Boolean didWork = false;
+
+ public virtual void doWork()
+ {
+ this.didWork = true;
+ insert new fflib_LogEntry__c(LogLevel__c = 'INFO', Message__c = 'Work got executed');
+ }
+ }
+
+ private class WorkA extends Work
+ {
+ public override void doWork()
+ {
+ orderOfExecution.add('WorkA');
+ }
+ }
+
+ private class WorkB extends Work
+ {
+ public override void doWork()
+ {
+ orderOfExecution.add('WorkB');
+ }
+ }
+
+ private class WorkC extends Work
+ {
+ public override void doWork()
+ {
+ orderOfExecution.add('WorkC');
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/sfdx-source/apex-extensions/tests/classes/work/fflib_UnitOfWorkTest.cls-meta.xml b/sfdx-source/apex-extensions/tests/classes/work/fflib_UnitOfWorkTest.cls-meta.xml
new file mode 100644
index 0000000..f928c8e
--- /dev/null
+++ b/sfdx-source/apex-extensions/tests/classes/work/fflib_UnitOfWorkTest.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 53.0
+ Active
+