Skip to content

Commit f4ced88

Browse files
committed
Allow for different implementations of Application factories
1 parent 1e5eb56 commit f4ced88

File tree

6 files changed

+398
-9
lines changed

6 files changed

+398
-9
lines changed

sfdx-source/apex-common/main/classes/fflib_Application.cls

Lines changed: 130 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,9 @@ public virtual class fflib_Application
112112
return new fflib_SObjectUnitOfWork(objectTypes, dml);
113113
}
114114

115+
/**
116+
* @param mockUow A mock implementation for the unitOfWork factory
117+
*/
115118
@TestVisible
116119
protected virtual void setMock(fflib_ISObjectUnitOfWork mockUow)
117120
{
@@ -168,6 +171,24 @@ public virtual class fflib_Application
168171
return serviceImpl.newInstance();
169172
}
170173

174+
/**
175+
* Creates or replaces an existing binding for another
176+
*
177+
* @param serviceInterfaceType The Interface type to replace its implementation
178+
* @param replacementImplType The implementation type of the replacement
179+
*/
180+
public virtual void replaceWith(Type serviceInterfaceType, Type replacementImplType)
181+
{
182+
this.m_serviceInterfaceTypeByServiceImplType.put(
183+
serviceInterfaceType,
184+
replacementImplType
185+
);
186+
}
187+
188+
/**
189+
* @param serviceInterfaceType The interface type to mock
190+
* @param serviceImpl The mock implementation
191+
*/
171192
@TestVisible
172193
protected virtual void setMock(Type serviceInterfaceType, Object serviceImpl)
173194
{
@@ -246,6 +267,27 @@ public virtual class fflib_Application
246267
return newInstance(domainSObjectType).selectSObjectsById(recordIds);
247268
}
248269

270+
/**
271+
* Helper method to query the given SObject records
272+
* Internally creates an instance of the registered Selector and calls its
273+
* selectSObjectById method.
274+
* It assumes that all Ids are of the given SObjectType, no additional validation is done.
275+
*
276+
* @param recordIds The recordIds to query
277+
* @param sObjectType The SObjectType of the Ids
278+
*
279+
* @return The queried records
280+
* @exception fflib_Application.DeveloperException is thrown if the Ids set is empty
281+
*/
282+
public virtual List<SObject> selectById(Set<Id> recordIds, SObjectType sObjectType)
283+
{
284+
if (recordIds == null || recordIds.size() == 0)
285+
throw new fflib_Application.DeveloperException('Invalid record Id\'s set');
286+
287+
return newInstance(sObjectType)
288+
.selectSObjectsById(recordIds);
289+
}
290+
249291
/**
250292
* Helper method to query related records to those provided, for example
251293
* if passed a list of Opportunity records and the Account Id field will
@@ -270,19 +312,44 @@ public virtual class fflib_Application
270312
return selectById(relatedIds);
271313
}
272314

315+
/**
316+
* Creates or replaces an existing binding for another
317+
*
318+
* @param sObjectType The SObjectType of the selector to replace
319+
* @param replacementImplType The implementation type of the replacement
320+
*/
321+
public virtual void replaceWith(SObjectType sObjectType, Type replacementImplType)
322+
{
323+
this.m_sObjectBySelectorType.put(sObjectType, replacementImplType);
324+
}
325+
326+
/**
327+
* @param selectorInstance The instance of the mocked selector
328+
*/
273329
@TestVisible
274330
protected virtual void setMock(fflib_ISObjectSelector selectorInstance)
275331
{
276332
m_sObjectByMockSelector.put(selectorInstance.sObjectType(), selectorInstance);
277-
}
333+
}
334+
335+
/**
336+
* @param sObjectType The SObjectType of the selector mock,
337+
* avoids the need to stub the mock to return its SObjectType
338+
* @param selectorInstance The instance of the mocked selector
339+
*/
340+
@TestVisible
341+
protected virtual void setMock(SObjectType sObjectType, fflib_ISObjectSelector selectorInstance)
342+
{
343+
this.m_sObjectByMockSelector.put(sObjectType, selectorInstance);
344+
}
278345
}
279346

280347
/**
281348
* Class implements a Domain class factory
282349
**/
283350
public virtual class DomainFactory implements fflib_IDomainFactory
284351
{
285-
protected fflib_Application.SelectorFactory m_selectorFactory;
352+
protected fflib_ISelectorFactory m_selectorFactory;
286353

287354
protected Map<Object, Type> constructorTypeByObject;
288355

@@ -302,10 +369,10 @@ public virtual class fflib_Application
302369
* @param selectorFactory , e.g. Application.Selector
303370
* @param constructorTypeByObject Map of Domain classes by ObjectType
304371
**/
305-
public DomainFactory(fflib_Application.SelectorFactory selectorFactory,
372+
public DomainFactory(fflib_ISelectorFactory selectorFactory,
306373
Map<Object, Type> constructorTypeByObject)
307374
{
308-
m_selectorFactory = selectorFactory;
375+
this.m_selectorFactory = selectorFactory;
309376
this.constructorTypeByObject = constructorTypeByObject;
310377
this.mockDomainByObject = new Map<Object, fflib_IDomain>();
311378
}
@@ -319,10 +386,10 @@ public virtual class fflib_Application
319386
* @param selectorFactory, e.g. Application.Selector
320387
* @param sObjectByDomainConstructorType Map of Apex classes by SObjectType
321388
**/
322-
public DomainFactory(fflib_Application.SelectorFactory selectorFactory,
389+
public DomainFactory(fflib_ISelectorFactory selectorFactory,
323390
Map<SObjectType, Type> sObjectByDomainConstructorType)
324391
{
325-
m_selectorFactory = selectorFactory;
392+
this.m_selectorFactory = selectorFactory;
326393
this.constructorTypeByObject = getConstructorTypeByObject(sObjectByDomainConstructorType);
327394
this.mockDomainByObject = new Map<Object, fflib_IDomain>();
328395
}
@@ -338,7 +405,22 @@ public virtual class fflib_Application
338405
public virtual fflib_IDomain newInstance(Set<Id> recordIds)
339406
{
340407
return newInstance(m_selectorFactory.selectById(recordIds));
408+
}
341409

410+
/**
411+
* Dynamically constructs an instance of a Domain class for the given record Ids
412+
* Internally uses the Selector Factory to query the records before passing to a
413+
* dynamically constructed instance of the application Apex Domain class
414+
*
415+
* @param recordIds A list of Id's of the same type
416+
* @param sObjectType The SObjectType of the given record Ids
417+
*
418+
* @return Instance of a Domain containing the queried records
419+
* @exception Throws an exception via the Selector Factory if the Ids are not all of the same SObjectType
420+
**/
421+
public virtual fflib_IDomain newInstance(Set<Id> recordIds, Schema.SObjectType sObjectType)
422+
{
423+
return newInstance(m_selectorFactory.selectById(recordIds, sObjectType), sObjectType);
342424
}
343425

344426
/**
@@ -412,18 +494,60 @@ public virtual class fflib_Application
412494
);
413495
}
414496

497+
/**
498+
* Creates or replaces an existing binding for another
499+
*
500+
* @param sObjectType The SObjectType of the selector to replace
501+
* @param replacementImplType The implementation type of the replacement
502+
*/
503+
public virtual void replaceWith(Schema.SObjectType sObjectType, Type replacementImplType)
504+
{
505+
this.constructorTypeByObject.put(
506+
(Object) sObjectType,
507+
replacementImplType
508+
);
509+
}
510+
511+
/**
512+
* @param mockDomain The instance of the Domain mock
513+
*/
415514
@TestVisible
416515
protected virtual void setMock(fflib_ISObjectDomain mockDomain)
417516
{
418517
mockDomainByObject.put((Object) mockDomain.sObjectType(), (fflib_IDomain) mockDomain);
419518
}
420519

520+
/**
521+
* @param mockDomain The instance of the Domain mock
522+
*/
421523
@TestVisible
422524
protected virtual void setMock(fflib_IDomain mockDomain)
423525
{
424526
mockDomainByObject.put(mockDomain.getType(), mockDomain);
425527
}
426528

529+
/**
530+
* @param sObjectType The SObjectType of the Domain mock,
531+
* avoids the need to stub the mock to return its SObjectType
532+
* @param mockDomain The instance of the Domain mock
533+
*/
534+
@TestVisible
535+
protected virtual void setMock(Schema.SObjectType sObjectType, fflib_ISObjectDomain mockDomain)
536+
{
537+
mockDomainByObject.put((Object) sObjectType, mockDomain);
538+
}
539+
540+
/**
541+
* @param domainType The ObjectType of the Domain mock,
542+
* avoids the need to stub the mock to return its ObjectType
543+
* @param mockDomain The instance of the Domain mock
544+
*/
545+
@TestVisible
546+
protected virtual void setMock(Object domainType, fflib_IDomain mockDomain)
547+
{
548+
mockDomainByObject.put(domainType, mockDomain);
549+
}
550+
427551
protected virtual Map<Object, Type> getConstructorTypeByObject(Map<SObjectType, Type> constructorTypeBySObjectType)
428552
{
429553
Map<Object, Type> result = new Map<Object, Type>();

sfdx-source/apex-common/main/classes/fflib_IDomainFactory.cls

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,82 @@
2525
**/
2626
public interface fflib_IDomainFactory
2727
{
28+
/**
29+
* Dynamically constructs an instance of a Domain class for the given record Ids
30+
* Internally uses the Selector Factory to query the records before passing to a
31+
* dynamically constructed instance of the application Apex Domain class
32+
*
33+
* @param recordIds A list of Id's of the same type
34+
* @exception Throws an exception via the Selector Factory if the Ids are not all of the same SObjectType
35+
*
36+
* @return Instance of the Domain
37+
**/
2838
fflib_IDomain newInstance(Set<Id> recordIds);
39+
40+
/**
41+
* Dynamically constructs an instance of a Domain class for the given record Ids
42+
* Internally uses the Selector Factory to query the records before passing to a
43+
* dynamically constructed instance of the application Apex Domain class
44+
*
45+
* @param recordIds A list of Id's of the same type
46+
* @param sObjectType The Schema.SObjectType of the record Ids,
47+
* Providing this parameter will omit the framework from checking if the Id's are all the same
48+
* and of which SObjectType they are.
49+
*
50+
* @exception Throws an exception via the Selector Factory if the Ids are not all of the same SObjectType
51+
*
52+
* @return Instance of the Domain
53+
**/
54+
fflib_IDomain newInstance(Set<Id> recordIds, Schema.SObjectType sObjectType);
55+
56+
/**
57+
* Dynamically constructs an instance of the Domain class for the given records
58+
* Will return a Mock implementation if one has been provided via setMock
59+
*
60+
* @param records A concrete list of records, e.g.; `List<Account>` or `List<SObject>`)
61+
*
62+
* @exception Throws an exception if the SObjectType cannot be determined from the list
63+
* or the constructor for Domain class was not registered for the SObjectType
64+
*
65+
* @return Instance of the Domain containing the given records
66+
**/
2967
fflib_IDomain newInstance(List<SObject> records);
68+
69+
/**
70+
* Dynamically constructs an instance of the Domain class for the given records
71+
* Will return a Mock implementation if one has been provided via setMock
72+
*
73+
* @param objects A concrete list of Objects, e.g.; `List<Account>` or `List<SObject>`)
74+
* @param objectType
75+
*
76+
* @exception Throws an exception if the SObjectType cannot be determined from the list
77+
* or the constructor for Domain class was not registered for the SObjectType
78+
*
79+
* @return Instance of the Domain containing the given Objects
80+
**/
3081
fflib_IDomain newInstance(List<Object> objects, Object objectType);
82+
83+
/**
84+
* Dynamically constructs an instance of the Domain class for the given records and SObjectType
85+
* Will return a Mock implementation if one has been provided via setMock
86+
*
87+
* @param records A list records
88+
* @param domainSObjectType SObjectType for list of records
89+
*
90+
* @exception Throws an exception if the SObjectType is not specified or if constructor for Domain class was not registered for the SObjectType
91+
*
92+
* @remark Will support List<SObject> but all records in the list will be assumed to be of
93+
* the type specified in sObjectType
94+
*
95+
* @return Instance of the Domain containing the given records
96+
**/
3197
fflib_IDomain newInstance(List<SObject> records, SObjectType domainSObjectType);
98+
99+
/**
100+
* Creates or replaces an existing binding for another
101+
*
102+
* @param sObjectType The SObjectType of the domain to replace
103+
* @param replacementImplType The implementation type of the replacement
104+
*/
105+
void replaceWith(Schema.SObjectType sObjectType, Type replacementImplType);
32106
}

sfdx-source/apex-common/main/classes/fflib_ISelectorFactory.cls

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,62 @@
2525
**/
2626
public interface fflib_ISelectorFactory
2727
{
28+
/**
29+
* Creates a new instance of the associated Apex Class implementing fflib_ISObjectSelector
30+
* for the given SObjectType, or if provided via setMock returns the Mock implementation
31+
*
32+
* @param sObjectType An SObjectType token, e.g. Account.SObjectType
33+
*
34+
* @return Instance of fflib_ISObjectSelector
35+
**/
2836
fflib_ISObjectSelector newInstance(SObjectType sObjectType);
37+
38+
/**
39+
* Helper method to query the given SObject records
40+
* Internally creates an instance of the registered Selector and calls its
41+
* selectSObjectById method
42+
*
43+
* @param recordIds The SObject record Ids, must be all the same SObjectType
44+
* @exception Is thrown if the record Ids are not all the same or the SObjectType is not registered
45+
*
46+
* @return List of queried records
47+
**/
2948
List<SObject> selectById(Set<Id> recordIds);
49+
50+
/**
51+
* Helper method to query the given SObject records
52+
* Internally creates an instance of the registered Selector and calls its
53+
* selectSObjectById method
54+
*
55+
* @param recordIds The SObject record Ids, must be all the same SObjectType
56+
* @exception Is thrown if the record Ids are not all the same or the SObjectType is not registered
57+
* @param sObjectType The SObjectType of the provided Ids
58+
*
59+
* @return List of queried records
60+
**/
61+
List<SObject> selectById(Set<Id> recordIds, Schema.SObjectType sObjectType);
62+
63+
/**
64+
* Helper method to query related records to those provided, for example
65+
* if passed a list of Opportunity records and the Account Id field will
66+
* construct internally a list of Account Ids and call the registered
67+
* Account selector to query the related Account records, e.g.
68+
*
69+
* List<Account> accounts =
70+
* (List<Account>) Application.Selector.selectByRelationship(myOpps, Opportunity.AccountId);
71+
*
72+
* @param relatedRecords used to extract the related record Ids, e.g. Opportunity records
73+
* @param relationshipField field in the passed records that contains the relationship records to query, e.g. Opportunity.AccountId
74+
*
75+
* @return List of queried records
76+
**/
3077
List<SObject> selectByRelationship(List<SObject> relatedRecords, SObjectField relationshipField);
78+
79+
/**
80+
* Creates or replaces an existing binding for another
81+
*
82+
* @param sObjectType The SObjectType of the selector to replace
83+
* @param replacementImplType The implementation type of the replacement
84+
*/
85+
void replaceWith(Schema.SObjectType sObjectType, Type replacementImplType);
3186
}

sfdx-source/apex-common/main/classes/fflib_IServiceFactory.cls

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,23 @@
2525
**/
2626
public interface fflib_IServiceFactory
2727
{
28+
/**
29+
* Returns a new instance of the Apex class associated with the given Apex interface
30+
* Will return any mock implementation of the interface provided via setMock
31+
* Note that this method will not check the configured Apex class actually implements the interface
32+
*
33+
* @param serviceInterfaceType Apex interface type
34+
* @exception Is thrown if there is no registered Apex class for the interface type
35+
*
36+
* @return Instance of the requested service class interface type
37+
**/
2838
Object newInstance(Type serviceInterfaceType);
39+
40+
/**
41+
* Creates or replaces an existing binding for another
42+
*
43+
* @param serviceInterfaceType The Interface type to replace its implementation
44+
* @param replacementImplType The implementation type of the replacement
45+
*/
46+
void replaceWith(Type serviceInterfaceType, Type replacementImplType);
2947
}

0 commit comments

Comments
 (0)