Skip to content

Commit 1305d6e

Browse files
authored
Merge pull request apex-enterprise-patterns#144 from ImJohnMDaniel/master
Added new registerDirty method to UOW
2 parents 87f57cd + e632990 commit 1305d6e

File tree

2 files changed

+80
-51
lines changed

2 files changed

+80
-51
lines changed

fflib/src/classes/fflib_ISObjectUnitOfWork.cls

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,15 @@ public interface fflib_ISObjectUnitOfWork
5050
* @param record An existing record
5151
**/
5252
void registerDirty(SObject record);
53+
/**
54+
* Register an existing record to be updated when commitWork is called,
55+
* you may also provide a reference to the parent record instance (should also be registered as new separatly)
56+
*
57+
* @param record A newly created SObject instance to be inserted during commitWork
58+
* @param relatedToParentField A SObjectField reference to the child field that associates the child record with its parent
59+
* @param relatedToParentRecord A SObject instance of the parent record (should also be registered as new separatly)
60+
**/
61+
void registerDirty(SObject record, Schema.sObjectField relatedToParentField, SObject relatedToParentRecord);
5362
/**
5463
* Register a list of existing records to be updated during the commitWork method
5564
*

fflib/src/classes/fflib_SObjectUnitOfWork.cls

Lines changed: 71 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,22 @@
22
* Copyright (c), FinancialForce.com, inc
33
* All rights reserved.
44
*
5-
* Redistribution and use in source and binary forms, with or without modification,
5+
* Redistribution and use in source and binary forms, with or without modification,
66
* are permitted provided that the following conditions are met:
77
*
8-
* - Redistributions of source code must retain the above copyright notice,
8+
* - Redistributions of source code must retain the above copyright notice,
99
* this list of conditions and the following disclaimer.
10-
* - Redistributions in binary form must reproduce the above copyright notice,
11-
* this list of conditions and the following disclaimer in the documentation
10+
* - Redistributions in binary form must reproduce the above copyright notice,
11+
* this list of conditions and the following disclaimer in the documentation
1212
* and/or other materials provided with the distribution.
13-
* - Neither the name of the FinancialForce.com, inc nor the names of its contributors
14-
* may be used to endorse or promote products derived from this software without
13+
* - Neither the name of the FinancialForce.com, inc nor the names of its contributors
14+
* may be used to endorse or promote products derived from this software without
1515
* specific prior written permission.
1616
*
17-
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18-
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19-
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
20-
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
17+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18+
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19+
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
20+
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
2121
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2222
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
2323
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
@@ -28,24 +28,24 @@
2828
* Provides an implementation of the Enterprise Application Architecture Unit Of Work, as defined by Martin Fowler
2929
* http://martinfowler.com/eaaCatalog/unitOfWork.html
3030
*
31-
* "When you're pulling data in and out of a database, it's important to keep track of what you've changed; otherwise,
32-
* that data won't be written back into the database. Similarly you have to insert new objects you create and
31+
* "When you're pulling data in and out of a database, it's important to keep track of what you've changed; otherwise,
32+
* that data won't be written back into the database. Similarly you have to insert new objects you create and
3333
* remove any objects you delete."
3434
*
35-
* "You can change the database with each change to your object model, but this can lead to lots of very small database calls,
36-
* which ends up being very slow. Furthermore it requires you to have a transaction open for the whole interaction, which is
35+
* "You can change the database with each change to your object model, but this can lead to lots of very small database calls,
36+
* which ends up being very slow. Furthermore it requires you to have a transaction open for the whole interaction, which is
3737
* impractical if you have a business transaction that spans multiple requests. The situation is even worse if you need to
3838
* keep track of the objects you've read so you can avoid inconsistent reads."
3939
*
40-
* "A Unit of Work keeps track of everything you do during a business transaction that can affect the database. When you're done,
40+
* "A Unit of Work keeps track of everything you do during a business transaction that can affect the database. When you're done,
4141
* it figures out everything that needs to be done to alter the database as a result of your work."
4242
*
4343
* In an Apex context this pattern provides the following specific benifits
4444
* - Applies bulkfication to DML operations, insert, update and delete
4545
* - Manages a business transaction around the work and ensures a rollback occurs (even when exceptions are later handled by the caller)
46-
* - Honours dependency rules between records and updates dependent relationships automatically during the commit
46+
* - Honours dependency rules between records and updates dependent relationships automatically during the commit
4747
*
48-
* Please refer to the testMethod's in this class for example usage
48+
* Please refer to the testMethod's in this class for example usage
4949
*
5050
* TODO: Need to complete the 100% coverage by covering parameter exceptions in tests
5151
* TODO: Need to add some more test methods for more complex use cases and some unexpected (e.g. registerDirty and then registerDeleted)
@@ -55,25 +55,25 @@ public virtual class fflib_SObjectUnitOfWork
5555
implements fflib_ISObjectUnitOfWork
5656
{
5757
private List<Schema.SObjectType> m_sObjectTypes = new List<Schema.SObjectType>();
58-
58+
5959
private Map<String, List<SObject>> m_newListByType = new Map<String, List<SObject>>();
60-
60+
6161
private Map<String, Map<Id, SObject>> m_dirtyMapByType = new Map<String, Map<Id, SObject>>();
62-
62+
6363
private Map<String, Map<Id, SObject>> m_deletedMapByType = new Map<String, Map<Id, SObject>>();
64-
64+
6565
private Map<String, Relationships> m_relationships = new Map<String, Relationships>();
6666

6767
private List<IDoWork> m_workList = new List<IDoWork>();
6868

6969
private SendEmailWork m_emailWork = new SendEmailWork();
70-
70+
7171
private IDML m_dml;
72-
72+
7373
/**
7474
* Interface describes work to be performed during the commitWork method
7575
**/
76-
public interface IDoWork
76+
public interface IDoWork
7777
{
7878
void doWork();
7979
}
@@ -84,7 +84,7 @@ public virtual class fflib_SObjectUnitOfWork
8484
void dmlUpdate(List<SObject> objList);
8585
void dmlDelete(List<SObject> objList);
8686
}
87-
87+
8888
public class SimpleDML implements IDML
8989
{
9090
public void dmlInsert(List<SObject> objList){
@@ -111,15 +111,15 @@ public virtual class fflib_SObjectUnitOfWork
111111
public fflib_SObjectUnitOfWork(List<Schema.SObjectType> sObjectTypes, IDML dml)
112112
{
113113
m_sObjectTypes = sObjectTypes.clone();
114-
114+
115115
for(Schema.SObjectType sObjectType : m_sObjectTypes)
116116
{
117117
// register the type
118118
handleRegisterType(sObjectType);
119119
}
120120

121121
m_workList.add(m_emailWork);
122-
122+
123123
m_dml = dml;
124124
}
125125

@@ -166,7 +166,7 @@ public virtual class fflib_SObjectUnitOfWork
166166
{
167167
m_emailWork.registerEmail(email);
168168
}
169-
169+
170170
/**
171171
* Register a newly created SObject instance to be inserted when commitWork is called
172172
*
@@ -191,7 +191,7 @@ public virtual class fflib_SObjectUnitOfWork
191191
}
192192

193193
/**
194-
* Register a newly created SObject instance to be inserted when commitWork is called,
194+
* Register a newly created SObject instance to be inserted when commitWork is called,
195195
* you may also provide a reference to the parent record instance (should also be registered as new separatly)
196196
*
197197
* @param record A newly created SObject instance to be inserted during commitWork
@@ -202,16 +202,16 @@ public virtual class fflib_SObjectUnitOfWork
202202
{
203203
if(record.Id != null)
204204
throw new UnitOfWorkException('Only new records can be registered as new');
205-
String sObjectType = record.getSObjectType().getDescribe().getName();
205+
String sObjectType = record.getSObjectType().getDescribe().getName();
206206
if(!m_newListByType.containsKey(sObjectType))
207207
throw new UnitOfWorkException(String.format('SObject type {0} is not supported by this unit of work', new String[] { sObjectType }));
208-
m_newListByType.get(sObjectType).add(record);
208+
m_newListByType.get(sObjectType).add(record);
209209
if(relatedToParentRecord!=null && relatedToParentField!=null)
210210
registerRelationship(record, relatedToParentField, relatedToParentRecord);
211211
}
212-
212+
213213
/**
214-
* Register a relationship between two records that have yet to be inserted to the database. This information will be
214+
* Register a relationship between two records that have yet to be inserted to the database. This information will be
215215
* used during the commitWork phase to make the references only when related records have been inserted to the database.
216216
*
217217
* @param record An existing or newly created record
@@ -220,12 +220,12 @@ public virtual class fflib_SObjectUnitOfWork
220220
*/
221221
public void registerRelationship(SObject record, Schema.sObjectField relatedToField, SObject relatedTo)
222222
{
223-
String sObjectType = record.getSObjectType().getDescribe().getName();
223+
String sObjectType = record.getSObjectType().getDescribe().getName();
224224
if(!m_newListByType.containsKey(sObjectType))
225225
throw new UnitOfWorkException(String.format('SObject type {0} is not supported by this unit of work', new String[] { sObjectType }));
226226
m_relationships.get(sObjectType).add(record, relatedToField, relatedTo);
227227
}
228-
228+
229229
/**
230230
* Register an existing record to be updated during the commitWork method
231231
*
@@ -235,12 +235,32 @@ public virtual class fflib_SObjectUnitOfWork
235235
{
236236
if(record.Id == null)
237237
throw new UnitOfWorkException('New records cannot be registered as dirty');
238-
String sObjectType = record.getSObjectType().getDescribe().getName();
238+
String sObjectType = record.getSObjectType().getDescribe().getName();
239239
if(!m_dirtyMapByType.containsKey(sObjectType))
240240
throw new UnitOfWorkException(String.format('SObject type {0} is not supported by this unit of work', new String[] { sObjectType }));
241-
m_dirtyMapByType.get(sObjectType).put(record.Id, record);
241+
m_dirtyMapByType.get(sObjectType).put(record.Id, record);
242242
}
243243

244+
/**
245+
* Register an existing record to be updated when commitWork is called,
246+
* you may also provide a reference to the parent record instance (should also be registered as new separatly)
247+
*
248+
* @param record A newly created SObject instance to be inserted during commitWork
249+
* @param relatedToParentField A SObjectField reference to the child field that associates the child record with its parent
250+
* @param relatedToParentRecord A SObject instance of the parent record (should also be registered as new separatly)
251+
**/
252+
public void registerDirty(SObject record, Schema.sObjectField relatedToParentField, SObject relatedToParentRecord)
253+
{
254+
if(record.Id == null)
255+
throw new UnitOfWorkException('New records cannot be registered as dirty');
256+
String sObjectType = record.getSObjectType().getDescribe().getName();
257+
if(!m_dirtyMapByType.containsKey(sObjectType))
258+
throw new UnitOfWorkException(String.format('SObject type {0} is not supported by this unit of work', new String[] { sObjectType }));
259+
m_dirtyMapByType.get(sObjectType).put(record.Id, record);
260+
if(relatedToParentRecord!=null && relatedToParentField!=null)
261+
registerRelationship(record, relatedToParentField, relatedToParentRecord);
262+
}
263+
244264
/**
245265
* Register a list of existing records to be updated during the commitWork method
246266
*
@@ -263,12 +283,12 @@ public virtual class fflib_SObjectUnitOfWork
263283
{
264284
if(record.Id == null)
265285
throw new UnitOfWorkException('New records cannot be registered for deletion');
266-
String sObjectType = record.getSObjectType().getDescribe().getName();
286+
String sObjectType = record.getSObjectType().getDescribe().getName();
267287
if(!m_deletedMapByType.containsKey(sObjectType))
268288
throw new UnitOfWorkException(String.format('SObject type {0} is not supported by this unit of work', new String[] { sObjectType }));
269-
m_deletedMapByType.get(sObjectType).put(record.Id, record);
289+
m_deletedMapByType.get(sObjectType).put(record.Id, record);
270290
}
271-
291+
272292
/**
273293
* Register a list of existing records to be deleted during the commitWork method
274294
*
@@ -281,7 +301,7 @@ public virtual class fflib_SObjectUnitOfWork
281301
this.registerDeleted(record);
282302
}
283303
}
284-
304+
285305
/**
286306
* Takes all the work that has been registered with the UnitOfWork and commits it to the database
287307
**/
@@ -290,22 +310,22 @@ public virtual class fflib_SObjectUnitOfWork
290310
// notify we're starting the commit work
291311
onCommitWorkStarting();
292312

293-
// Wrap the work in its own transaction
313+
// Wrap the work in its own transaction
294314
Savepoint sp = Database.setSavePoint();
295315
Boolean wasSuccessful = false;
296316
try
297-
{
317+
{
298318
// notify we're starting the DML operations
299319
onDMLStarting();
300320
// Insert by type
301321
for(Schema.SObjectType sObjectType : m_sObjectTypes)
302322
{
303323
m_relationships.get(sObjectType.getDescribe().getName()).resolve();
304324
m_dml.dmlInsert(m_newListByType.get(sObjectType.getDescribe().getName()));
305-
}
325+
}
306326
// Update by type
307327
for(Schema.SObjectType sObjectType : m_sObjectTypes)
308-
m_dml.dmlUpdate(m_dirtyMapByType.get(sObjectType.getDescribe().getName()).values());
328+
m_dml.dmlUpdate(m_dirtyMapByType.get(sObjectType.getDescribe().getName()).values());
309329
// Delete by type (in reverse dependency order)
310330
Integer objectIdx = m_sObjectTypes.size() - 1;
311331
while(objectIdx>=0)
@@ -340,7 +360,7 @@ public virtual class fflib_SObjectUnitOfWork
340360
onCommitWorkFinished(wasSuccessful);
341361
}
342362
}
343-
363+
344364
private class Relationships
345365
{
346366
private List<Relationship> m_relationships = new List<Relationship>();
@@ -351,7 +371,7 @@ public virtual class fflib_SObjectUnitOfWork
351371
for(Relationship relationship : m_relationships)
352372
relationship.Record.put(relationship.RelatedToField, relationship.RelatedTo.Id);
353373
}
354-
374+
355375
public void add(SObject record, Schema.sObjectField relatedToField, SObject relatedTo)
356376
{
357377
// Relationship to resolve
@@ -362,20 +382,20 @@ public virtual class fflib_SObjectUnitOfWork
362382
m_relationships.add(relationship);
363383
}
364384
}
365-
385+
366386
private class Relationship
367387
{
368388
public SObject Record;
369389
public Schema.sObjectField RelatedToField;
370390
public SObject RelatedTo;
371391
}
372-
392+
373393
/**
374394
* UnitOfWork Exception
375395
**/
376396
public class UnitOfWorkException extends Exception {}
377397

378-
/**
398+
/**
379399
* Internal implementation of Messaging.sendEmail, see outer class registerEmail method
380400
**/
381401
private class SendEmailWork implements IDoWork
@@ -396,5 +416,5 @@ public virtual class fflib_SObjectUnitOfWork
396416
{
397417
if(emails.size() > 0) Messaging.sendEmail(emails);
398418
}
399-
}
419+
}
400420
}

0 commit comments

Comments
 (0)