Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow for different implementations of Application factories #298

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 115 additions & 7 deletions sfdx-source/apex-common/main/classes/fflib_Application.cls
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ public virtual class fflib_Application
return new fflib_SObjectUnitOfWork(objectTypes, dml);
}

/**
* @param mockUow A mock implementation for the unitOfWork factory
*/
@TestVisible
protected virtual void setMock(fflib_ISObjectUnitOfWork mockUow)
{
Expand Down Expand Up @@ -168,6 +171,24 @@ public virtual class fflib_Application
return serviceImpl.newInstance();
}

/**
* Creates or replaces an existing binding for another
*
* @param serviceInterfaceType The Interface type to replace its implementation
* @param replacementImplType The implementation type of the replacement
*/
public virtual void replaceWith(Type serviceInterfaceType, Type replacementImplType)
{
this.m_serviceInterfaceTypeByServiceImplType.put(
serviceInterfaceType,
replacementImplType
);
}

/**
* @param serviceInterfaceType The interface type to mock
* @param serviceImpl The mock implementation
*/
@TestVisible
protected virtual void setMock(Type serviceInterfaceType, Object serviceImpl)
{
Expand Down Expand Up @@ -246,6 +267,27 @@ public virtual class fflib_Application
return newInstance(domainSObjectType).selectSObjectsById(recordIds);
}

/**
* Helper method to query the given SObject records
* Internally creates an instance of the registered Selector and calls its
* selectSObjectById method.
* It assumes that all Ids are of the given SObjectType, no additional validation is done.
*
* @param recordIds The recordIds to query
* @param sObjectType The SObjectType of the Ids
*
* @return The queried records
* @exception fflib_Application.DeveloperException is thrown if the Ids set is empty
*/
public virtual List<SObject> selectById(Set<Id> recordIds, SObjectType sObjectType)
{
if (recordIds == null || recordIds.size() == 0)
throw new fflib_Application.DeveloperException('Invalid record Id\'s set');

return newInstance(sObjectType)
.selectSObjectsById(recordIds);
}

/**
* Helper method to query related records to those provided, for example
* if passed a list of Opportunity records and the Account Id field will
Expand All @@ -270,19 +312,44 @@ public virtual class fflib_Application
return selectById(relatedIds);
}

/**
* Creates or replaces an existing binding for another
*
* @param sObjectType The SObjectType of the selector to replace
* @param replacementImplType The implementation type of the replacement
*/
public virtual void replaceWith(SObjectType sObjectType, Type replacementImplType)
{
this.m_sObjectBySelectorType.put(sObjectType, replacementImplType);
}

/**
* @param selectorInstance The instance of the mocked selector
*/
@TestVisible
protected virtual void setMock(fflib_ISObjectSelector selectorInstance)
{
m_sObjectByMockSelector.put(selectorInstance.sObjectType(), selectorInstance);
}
}

/**
* @param sObjectType The SObjectType of the selector mock,
* avoids the need to stub the mock to return its SObjectType
* @param selectorInstance The instance of the mocked selector
*/
@TestVisible
protected virtual void setMock(SObjectType sObjectType, fflib_ISObjectSelector selectorInstance)
{
this.m_sObjectByMockSelector.put(sObjectType, selectorInstance);
}
}

/**
* Class implements a Domain class factory
**/
public virtual class DomainFactory implements fflib_IDomainFactory
{
protected fflib_Application.SelectorFactory m_selectorFactory;
protected fflib_ISelectorFactory m_selectorFactory;

protected Map<Object, Type> constructorTypeByObject;

Expand All @@ -302,10 +369,10 @@ public virtual class fflib_Application
* @param selectorFactory , e.g. Application.Selector
* @param constructorTypeByObject Map of Domain classes by ObjectType
**/
public DomainFactory(fflib_Application.SelectorFactory selectorFactory,
public DomainFactory(fflib_ISelectorFactory selectorFactory,
Map<Object, Type> constructorTypeByObject)
{
m_selectorFactory = selectorFactory;
this.m_selectorFactory = selectorFactory;
this.constructorTypeByObject = constructorTypeByObject;
this.mockDomainByObject = new Map<Object, fflib_IDomain>();
}
Expand All @@ -319,10 +386,10 @@ public virtual class fflib_Application
* @param selectorFactory, e.g. Application.Selector
* @param sObjectByDomainConstructorType Map of Apex classes by SObjectType
**/
public DomainFactory(fflib_Application.SelectorFactory selectorFactory,
public DomainFactory(fflib_ISelectorFactory selectorFactory,
Map<SObjectType, Type> sObjectByDomainConstructorType)
{
m_selectorFactory = selectorFactory;
this.m_selectorFactory = selectorFactory;
this.constructorTypeByObject = getConstructorTypeByObject(sObjectByDomainConstructorType);
this.mockDomainByObject = new Map<Object, fflib_IDomain>();
}
Expand All @@ -338,7 +405,6 @@ public virtual class fflib_Application
public virtual fflib_IDomain newInstance(Set<Id> recordIds)
{
return newInstance(m_selectorFactory.selectById(recordIds));

}

/**
Expand Down Expand Up @@ -412,18 +478,60 @@ public virtual class fflib_Application
);
}

/**
* Creates or replaces an existing binding for another
*
* @param sObjectType The SObjectType of the selector to replace
* @param replacementImplType The implementation type of the replacement
*/
public virtual void replaceWith(Schema.SObjectType sObjectType, Type replacementImplType)
{
this.constructorTypeByObject.put(
(Object) sObjectType,
replacementImplType
);
}

/**
* @param mockDomain The instance of the Domain mock
*/
@TestVisible
protected virtual void setMock(fflib_ISObjectDomain mockDomain)
{
mockDomainByObject.put((Object) mockDomain.sObjectType(), (fflib_IDomain) mockDomain);
}

/**
* @param mockDomain The instance of the Domain mock
*/
@TestVisible
protected virtual void setMock(fflib_IDomain mockDomain)
{
mockDomainByObject.put(mockDomain.getType(), mockDomain);
}

/**
* @param sObjectType The SObjectType of the Domain mock,
* avoids the need to stub the mock to return its SObjectType
* @param mockDomain The instance of the Domain mock
*/
@TestVisible
protected virtual void setMock(Schema.SObjectType sObjectType, fflib_ISObjectDomain mockDomain)
{
mockDomainByObject.put((Object) sObjectType, mockDomain);
}

/**
* @param domainType The ObjectType of the Domain mock,
* avoids the need to stub the mock to return its ObjectType
* @param mockDomain The instance of the Domain mock
*/
@TestVisible
protected virtual void setMock(Object domainType, fflib_IDomain mockDomain)
{
mockDomainByObject.put(domainType, mockDomain);
}

protected virtual Map<Object, Type> getConstructorTypeByObject(Map<SObjectType, Type> constructorTypeBySObjectType)
{
Map<Object, Type> result = new Map<Object, Type>();
Expand Down
58 changes: 58 additions & 0 deletions sfdx-source/apex-common/main/classes/fflib_IDomainFactory.cls
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,66 @@
**/
public interface fflib_IDomainFactory
{
/**
* Dynamically constructs an instance of a Domain class for the given record Ids
* Internally uses the Selector Factory to query the records before passing to a
* dynamically constructed instance of the application Apex Domain class
*
* @param recordIds A list of Id's of the same type
* @exception Throws an exception via the Selector Factory if the Ids are not all of the same SObjectType
*
* @return Instance of the Domain
**/
fflib_IDomain newInstance(Set<Id> recordIds);

/**
* Dynamically constructs an instance of the Domain class for the given records
* Will return a Mock implementation if one has been provided via setMock
*
* @param records A concrete list of records, e.g.; `List<Account>` or `List<SObject>`)
*
* @exception Throws an exception if the SObjectType cannot be determined from the list
* or the constructor for Domain class was not registered for the SObjectType
*
* @return Instance of the Domain containing the given records
**/
fflib_IDomain newInstance(List<SObject> records);

/**
* Dynamically constructs an instance of the Domain class for the given records
* Will return a Mock implementation if one has been provided via setMock
*
* @param objects A concrete list of Objects, e.g.; `List<Account>` or `List<SObject>`)
* @param objectType
*
* @exception Throws an exception if the SObjectType cannot be determined from the list
* or the constructor for Domain class was not registered for the SObjectType
*
* @return Instance of the Domain containing the given Objects
**/
fflib_IDomain newInstance(List<Object> objects, Object objectType);

/**
* Dynamically constructs an instance of the Domain class for the given records and SObjectType
* Will return a Mock implementation if one has been provided via setMock
*
* @param records A list records
* @param domainSObjectType SObjectType for list of records
*
* @exception Throws an exception if the SObjectType is not specified or if constructor for Domain class was not registered for the SObjectType
*
* @remark Will support List<SObject> but all records in the list will be assumed to be of
* the type specified in sObjectType
*
* @return Instance of the Domain containing the given records
**/
fflib_IDomain newInstance(List<SObject> records, SObjectType domainSObjectType);

/**
* Creates or replaces an existing binding for another
*
* @param sObjectType The SObjectType of the domain to replace
* @param replacementImplType The implementation type of the replacement
*/
void replaceWith(Schema.SObjectType sObjectType, Type replacementImplType);
}
55 changes: 55 additions & 0 deletions sfdx-source/apex-common/main/classes/fflib_ISelectorFactory.cls
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,62 @@
**/
public interface fflib_ISelectorFactory
{
/**
* Creates a new instance of the associated Apex Class implementing fflib_ISObjectSelector
* for the given SObjectType, or if provided via setMock returns the Mock implementation
*
* @param sObjectType An SObjectType token, e.g. Account.SObjectType
*
* @return Instance of fflib_ISObjectSelector
**/
fflib_ISObjectSelector newInstance(SObjectType sObjectType);

/**
* Helper method to query the given SObject records
* Internally creates an instance of the registered Selector and calls its
* selectSObjectById method
*
* @param recordIds The SObject record Ids, must be all the same SObjectType
* @exception Is thrown if the record Ids are not all the same or the SObjectType is not registered
*
* @return List of queried records
**/
List<SObject> selectById(Set<Id> recordIds);

/**
* Helper method to query the given SObject records
* Internally creates an instance of the registered Selector and calls its
* selectSObjectById method
*
* @param recordIds The SObject record Ids, must be all the same SObjectType
* @exception Is thrown if the record Ids are not all the same or the SObjectType is not registered
* @param sObjectType The SObjectType of the provided Ids
*
* @return List of queried records
**/
List<SObject> selectById(Set<Id> recordIds, Schema.SObjectType sObjectType);

/**
* Helper method to query related records to those provided, for example
* if passed a list of Opportunity records and the Account Id field will
* construct internally a list of Account Ids and call the registered
* Account selector to query the related Account records, e.g.
*
* List<Account> accounts =
* (List<Account>) Application.Selector.selectByRelationship(myOpps, Opportunity.AccountId);
*
* @param relatedRecords used to extract the related record Ids, e.g. Opportunity records
* @param relationshipField field in the passed records that contains the relationship records to query, e.g. Opportunity.AccountId
*
* @return List of queried records
**/
List<SObject> selectByRelationship(List<SObject> relatedRecords, SObjectField relationshipField);

/**
* Creates or replaces an existing binding for another
*
* @param sObjectType The SObjectType of the selector to replace
* @param replacementImplType The implementation type of the replacement
*/
void replaceWith(Schema.SObjectType sObjectType, Type replacementImplType);
}
18 changes: 18 additions & 0 deletions sfdx-source/apex-common/main/classes/fflib_IServiceFactory.cls
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,23 @@
**/
public interface fflib_IServiceFactory
{
/**
* Returns a new instance of the Apex class associated with the given Apex interface
* Will return any mock implementation of the interface provided via setMock
* Note that this method will not check the configured Apex class actually implements the interface
*
* @param serviceInterfaceType Apex interface type
* @exception Is thrown if there is no registered Apex class for the interface type
*
* @return Instance of the requested service class interface type
**/
Object newInstance(Type serviceInterfaceType);

/**
* Creates or replaces an existing binding for another
*
* @param serviceInterfaceType The Interface type to replace its implementation
* @param replacementImplType The implementation type of the replacement
*/
void replaceWith(Type serviceInterfaceType, Type replacementImplType);
}
Loading