From 3a3a1ac01da7fa790094ac644f09aa81c5f71aaf Mon Sep 17 00:00:00 2001 From: wimvelzeboer Date: Tue, 5 Apr 2022 19:31:08 +0100 Subject: [PATCH] Replace implementations on the fly Adds a method to change implementations in realtime using the Application class for Domains, Services and Selectors. Useful when you want to dynamically assign/change implementation while doing A/B testing or feature switching. --- .../main/classes/fflib_Application.cls | 47 +++++++++++++ .../main/classes/fflib_IDomainFactory.cls | 66 +++++++++++++++++++ .../main/classes/fflib_ISelectorFactory.cls | 42 ++++++++++++ .../main/classes/fflib_IServiceFactory.cls | 18 +++++ 4 files changed, 173 insertions(+) diff --git a/sfdx-source/apex-common/main/classes/fflib_Application.cls b/sfdx-source/apex-common/main/classes/fflib_Application.cls index 889c8163320..955a7ecb15f 100644 --- a/sfdx-source/apex-common/main/classes/fflib_Application.cls +++ b/sfdx-source/apex-common/main/classes/fflib_Application.cls @@ -168,6 +168,20 @@ 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 + ); + } + @TestVisible protected virtual void setMock(Type serviceInterfaceType, Object serviceImpl) { @@ -270,6 +284,17 @@ 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); + } + @TestVisible protected virtual void setMock(fflib_ISObjectSelector selectorInstance) { @@ -412,6 +437,28 @@ public virtual class fflib_Application ); } + /** + * 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 + */ + public virtual void replaceWith(SObjectType sObjectType, Type replacementImplType) + { + replaceWith((Object) sObjectType, replacementImplType); + } + + /** + * Creates or replaces an existing binding for another + * + * @param objectType The objectType of the domain to replace + * @param replacementImplType The implementation type of the replacement + */ + public virtual void replaceWith(Object objectType, Type replacementImplType) + { + this.constructorTypeByObject.put( objectType, replacementImplType); + } + @TestVisible protected virtual void setMock(fflib_ISObjectDomain mockDomain) { diff --git a/sfdx-source/apex-common/main/classes/fflib_IDomainFactory.cls b/sfdx-source/apex-common/main/classes/fflib_IDomainFactory.cls index 1e0f982d5ae..0a134dbe61a 100644 --- a/sfdx-source/apex-common/main/classes/fflib_IDomainFactory.cls +++ b/sfdx-source/apex-common/main/classes/fflib_IDomainFactory.cls @@ -25,8 +25,74 @@ **/ 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 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` or `List`) + * + * @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 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` or `List`) + * @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 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 sObjectType 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 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 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); + + /** + * Creates or replaces an existing binding for another + * + * @param objectType The objectType of the domain to replace + * @param replacementImplType The implementation type of the replacement + */ + void replaceWith(Object objectType, Type replacementImplType); } \ No newline at end of file diff --git a/sfdx-source/apex-common/main/classes/fflib_ISelectorFactory.cls b/sfdx-source/apex-common/main/classes/fflib_ISelectorFactory.cls index a39095b289b..9a7da918f2e 100644 --- a/sfdx-source/apex-common/main/classes/fflib_ISelectorFactory.cls +++ b/sfdx-source/apex-common/main/classes/fflib_ISelectorFactory.cls @@ -25,7 +25,49 @@ **/ 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 selectById(Set 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 + * construct internally a list of Account Ids and call the registered + * Account selector to query the related Account records, e.g. + * + * List accounts = + * (List) 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 selectByRelationship(List 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); } \ No newline at end of file diff --git a/sfdx-source/apex-common/main/classes/fflib_IServiceFactory.cls b/sfdx-source/apex-common/main/classes/fflib_IServiceFactory.cls index 93fa4124cc7..3fd95ec1c01 100644 --- a/sfdx-source/apex-common/main/classes/fflib_IServiceFactory.cls +++ b/sfdx-source/apex-common/main/classes/fflib_IServiceFactory.cls @@ -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); } \ No newline at end of file