From dbb11f2e1e8d0280969895469fc204412e09ba30 Mon Sep 17 00:00:00 2001 From: WimVelzeboer Date: Tue, 12 Oct 2021 22:40:22 +0100 Subject: [PATCH] Add a generic UnitOfWork functionality --- README.md | 11 + .../main/unitOfWork/classes/fflib_DoWork.cls | 113 ++++++++ .../classes/fflib_DoWork.cls-meta.xml | 5 + .../unitOfWork/classes/fflib_UnitOfWork.cls | 253 ++++++++++++++++++ .../classes/fflib_UnitOfWork.cls-meta.xml | 5 + .../classes/interfaces/fflib_IDoWork.cls | 84 ++++++ .../interfaces/fflib_IDoWork.cls-meta.xml | 5 + .../classes/interfaces/fflib_IUnitOfWork.cls | 82 ++++++ .../interfaces/fflib_IUnitOfWork.cls-meta.xml | 5 + .../classes/work/fflib_UnitOfWorkTest.cls | 160 +++++++++++ .../work/fflib_UnitOfWorkTest.cls-meta.xml | 5 + 11 files changed, 728 insertions(+) create mode 100644 sfdx-source/apex-extensions/main/unitOfWork/classes/fflib_DoWork.cls create mode 100644 sfdx-source/apex-extensions/main/unitOfWork/classes/fflib_DoWork.cls-meta.xml create mode 100644 sfdx-source/apex-extensions/main/unitOfWork/classes/fflib_UnitOfWork.cls create mode 100644 sfdx-source/apex-extensions/main/unitOfWork/classes/fflib_UnitOfWork.cls-meta.xml create mode 100644 sfdx-source/apex-extensions/main/unitOfWork/classes/interfaces/fflib_IDoWork.cls create mode 100644 sfdx-source/apex-extensions/main/unitOfWork/classes/interfaces/fflib_IDoWork.cls-meta.xml create mode 100644 sfdx-source/apex-extensions/main/unitOfWork/classes/interfaces/fflib_IUnitOfWork.cls create mode 100644 sfdx-source/apex-extensions/main/unitOfWork/classes/interfaces/fflib_IUnitOfWork.cls-meta.xml create mode 100644 sfdx-source/apex-extensions/tests/classes/work/fflib_UnitOfWorkTest.cls create mode 100644 sfdx-source/apex-extensions/tests/classes/work/fflib_UnitOfWorkTest.cls-meta.xml 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 +