Merge pull request #51 from wimvelzeboer/feature/ImprovedDynamicApplicationFactory

Improve performance of Dynamic Application Factories
This commit is contained in:
William Velzeboer
2023-09-01 15:51:10 +01:00
committed by GitHub
22 changed files with 416 additions and 139 deletions
+33
View File
@@ -0,0 +1,33 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="RunAllTest" type="com.illuminatedcloud.intellij" factoryName="Apex Unit Tests" show_console_on_std_err="false" show_console_on_std_out="false">
<option name="allTests" value="true" />
<option name="allowRunningInParallel" value="false" />
<option name="apexLogLevel" value="DEBUG" />
<option name="calloutsLogLevel" value="INFO" />
<option name="connectionName" value="apex-extensions" />
<option name="connectionType" value="SFDX" />
<option name="contextConfiguration" value="false" />
<option name="databaseLogLevel" value="INFO" />
<option name="directoryPath" />
<option name="logLevelsInitialized" value="true" />
<option name="moduleToTestClassNames">
<map />
</option>
<option name="nbaLogLevel" value="INFO" />
<option name="profilingLogLevel" value="INFO" />
<option name="projectPathOnTarget" />
<option name="selectedOptions">
<list />
</option>
<option name="systemLogLevel" value="DEBUG" />
<option name="testClassToTestMethodNames">
<map />
</option>
<option name="testSuiteName" />
<option name="validationLogLevel" value="INFO" />
<option name="visualforceLogLevel" value="INFO" />
<option name="waveLogLevel" value="INFO" />
<option name="workflowLogLevel" value="INFO" />
<method v="2" />
</configuration>
</component>
@@ -37,6 +37,7 @@ public virtual class fflib_AppBinding
protected System.Type implementationType { get; set; } protected System.Type implementationType { get; set; }
protected System.Type interfaceType { get; set; } protected System.Type interfaceType { get; set; }
protected String namespacePrefix { get; set; } protected String namespacePrefix { get; set; }
protected String packageName { get; set; }
protected fflib_SharingMode sharingMode { get; set; } protected fflib_SharingMode sharingMode { get; set; }
public Integer compareTo(Object compareTo) public Integer compareTo(Object compareTo)
@@ -89,6 +90,14 @@ public virtual class fflib_AppBinding
return this.namespacePrefix; return this.namespacePrefix;
} }
/**
* @return Returns the Application Package Name for the binding
*/
public String getPackageName()
{
return this.packageName;
}
/** /**
* @return Returns the number of the order sequence in which the binding should be returned * @return Returns the number of the order sequence in which the binding should be returned
*/ */
@@ -188,6 +197,19 @@ public virtual class fflib_AppBinding
return this; return this;
} }
/**
* Sets the Application Second Generation Package Name
*
* @param applicationPackageName The name of the 2GP package
*
* @return An instance of itself to enable method chaining
*/
public fflib_IAppBinding setPackageName(String applicationPackageName)
{
this.packageName = applicationPackageName;
return this;
}
/** /**
* Set the order number so that the resolver can sort the bindings. * Set the order number so that the resolver can sort the bindings.
* The higher the number, the more priority. * The higher the number, the more priority.
@@ -43,6 +43,7 @@ public with sharing class fflib_AppBindingMetaDataModule
.setImplementationType(getSystemType(binding, fflib_AppBinding__mdt.ImplementationType__c)) .setImplementationType(getSystemType(binding, fflib_AppBinding__mdt.ImplementationType__c))
.setInterfaceType(getSystemType(binding, fflib_AppBinding__mdt.InterfaceType__c)) .setInterfaceType(getSystemType(binding, fflib_AppBinding__mdt.InterfaceType__c))
.setNamespacePrefix(binding.NamespacePrefix) .setNamespacePrefix(binding.NamespacePrefix)
.setPackageName(binding.ApplicationPackage__r.DeveloperName)
.setSequence(binding.BindingSequence__c) .setSequence(binding.BindingSequence__c)
.setSharingMode(getSharingModeFromString(binding.SharingMode__c)) .setSharingMode(getSharingModeFromString(binding.SharingMode__c))
); );
@@ -132,6 +132,11 @@ public virtual class fflib_AppBindingResolver
return this; return this;
} }
public fflib_IAppBindingResolver byPackageName(String packageName)
{
this.bindingToResolve.setPackageName(packageName);
return this;
}
/** /**
* Handles lazy loading of modules, if no custom module is configured (via addModule) the default module is returned * Handles lazy loading of modules, if no custom module is configured (via addModule) the default module is returned
@@ -63,6 +63,67 @@ public virtual class fflib_AppBindings
} }
} }
public List<fflib_IAppBinding> getBindings()
{
return this.bindings;
}
public Map<Object, Type> getImplementationByBindingObject()
{
this.bindings.sort();
Map<Object, Type> result = new Map<Object, Type>();
for (fflib_IAppBinding binding : bindings)
{
Object bindingObject = binding.getBindingObject();
if (result.containsKey(bindingObject)) continue; // Skip bindings with lower sequence
result.put(bindingObject, binding.getImplementationType());
}
return result;
}
public Map<Type, Type> getImplementationByInterface()
{
this.bindings.sort();
Map<Type, Type> result = new Map<Type, Type>();
for (fflib_IAppBinding binding : bindings)
{
Type interfaceType = binding.getInterfaceType();
if (result.containsKey(interfaceType)) continue; // Skip bindings with lower sequence
result.put(interfaceType, binding.getImplementationType());
}
return result;
}
public Map<SObjectType, Type> getImplementationBySObjectType()
{
Map<SObjectType, Type> result = new Map<SObjectType, Type>();
for (fflib_IAppBinding binding : bindings)
{
Object bindingObject = binding.getBindingObject();
if (bindingObject instanceof SObjectType && result.containsKey((SObjectType) bindingObject) == false)
{
result.put((SObjectType) bindingObject, binding.getImplementationType());
}
}
return result;
}
public List<System.Type> getImplementations()
{
List<System.Type> result = new List<System.Type>();
for (fflib_IAppBinding binding : bindings)
{
result.add(
binding.getImplementationType()
);
}
return result;
}
public fflib_IAppBindings selectByObject(Object bindingObject) public fflib_IAppBindings selectByObject(Object bindingObject)
{ {
if (null == bindingObject) return this; if (null == bindingObject) return this;
@@ -149,21 +210,18 @@ public virtual class fflib_AppBindings
return new fflib_AppBindings(result); return new fflib_AppBindings(result);
} }
public List<fflib_IAppBinding> getBindings() public fflib_IAppBindings selectByPackageName(String packageName)
{ {
return this.bindings; if (null == packageName) return this;
}
public List<System.Type> getImplementations() List<fflib_IAppBinding> result = new List<fflib_IAppBinding>();
{
List<System.Type> result = new List<System.Type>();
for (fflib_IAppBinding binding : bindings) for (fflib_IAppBinding binding : bindings)
{ {
result.add( if (binding.getPackageName() != packageName) continue;
binding.getImplementationType()
); result.add(binding);
} }
return result; return new fflib_AppBindings(result);
} }
public List<Object> getObjects() public List<Object> getObjects()
@@ -75,6 +75,7 @@ public virtual without sharing class fflib_AppBindingsSelector extends fflib_SOb
return (List<fflib_AppBinding__mdt>) Database.query( return (List<fflib_AppBinding__mdt>) Database.query(
newQueryFactory() newQueryFactory()
.selectField('ApplicationPackage__r.DeveloperName')
.selectField('BindingObject__r.QualifiedApiName') .selectField('BindingObject__r.QualifiedApiName')
.toSOQL()); .toSOQL());
*/ */
@@ -84,7 +85,9 @@ public virtual without sharing class fflib_AppBindingsSelector extends fflib_SOb
'SELECT {0} FROM {1}', 'SELECT {0} FROM {1}',
new List<String> new List<String>
{ {
getFieldListString() + ', BindingObject__r.QualifiedApiName', getFieldListString()
+ ', BindingObject__r.QualifiedApiName'
+ ', ApplicationPackage__r.DeveloperName',
getSObjectType().getDescribe().getName() getSObjectType().getDescribe().getName()
} }
) )
@@ -30,6 +30,8 @@
*/ */
public abstract class fflib_DynamicAppFactory public abstract class fflib_DynamicAppFactory
{ {
protected String packageName;
protected fflib_SharingMode sharingMode protected fflib_SharingMode sharingMode
{ {
get get
@@ -33,9 +33,8 @@ public virtual class fflib_DynamicDomainFactory
extends fflib_DynamicAppFactory extends fflib_DynamicAppFactory
implements fflib_IDomainFactory implements fflib_IDomainFactory
{ {
protected Map<Object, fflib_IDomain> mockDomainByObjectType = new Map<Object, fflib_IDomain>();
protected fflib_ISelectorFactory selectorFactory; protected fflib_ISelectorFactory selectorFactory;
protected fflib_ClassicDomainFactory domainFactory;
/** /**
* Class constructor * Class constructor
@@ -44,9 +43,25 @@ public virtual class fflib_DynamicDomainFactory
* @param selectorFactory A reference to the selector factory instance * @param selectorFactory A reference to the selector factory instance
*/ */
public fflib_DynamicDomainFactory(fflib_IAppBindingResolver resolver, fflib_ISelectorFactory selectorFactory) public fflib_DynamicDomainFactory(fflib_IAppBindingResolver resolver, fflib_ISelectorFactory selectorFactory)
{
this(resolver, selectorFactory, null);
}
/**
* Class constructor
*
* @param resolver A reference to the resolve instance
* @param selectorFactory A reference to the selector factory instance
* @param packageName The package name
*/
public fflib_DynamicDomainFactory(
fflib_IAppBindingResolver resolver,
fflib_ISelectorFactory selectorFactory,
String packageName)
{ {
this.resolver = resolver; this.resolver = resolver;
this.selectorFactory = selectorFactory; this.selectorFactory = selectorFactory;
this.packageName = packageName;
} }
public fflib_IDomain newInstance(Set<Id> recordIds) public fflib_IDomain newInstance(Set<Id> recordIds)
@@ -61,42 +76,16 @@ public virtual class fflib_DynamicDomainFactory
public fflib_IDomain newInstance(List<SObject> records) public fflib_IDomain newInstance(List<SObject> records)
{ {
SObjectType sObjectType = records.getSObjectType(); if (domainFactory == null) loadBindings();
if (sObjectType == null) return this.domainFactory.newInstance(records);
throw new DeveloperException('Could not determine the SObjectType of the provided records');
return newInstance(
(List<Object>) records,
(Object) sObjectType
);
} }
public fflib_IDomain newInstance(List<Object> objects, Object objectType) public fflib_IDomain newInstance(List<Object> objects, Object objectType)
{ {
if (hasMock(objectType)) return (fflib_IDomain) getMock(objectType); if (domainFactory == null) loadBindings();
List<fflib_IAppBinding> bindings = resolver.byObject(objectType) return this.domainFactory.newInstance(objects, objectType);
.byType(fflib_AppBindingType.Domain)
.resolve();
if (bindings.isEmpty())
throw new DeveloperException('Unable to find domain binding for ' + objectType);
System.Type domainImplementationType = bindings.get(0).getImplementationType();
Object instance = domainImplementationType.newInstance();
if (!(instance instanceof fflib_IDomainConstructor))
throw new DeveloperException(
String.format(
'Domain implementation {0} should be an instance of fflib_IDomainConstructor',
new List<String> { domainImplementationType.getName() }
)
);
return ((fflib_IDomainConstructor) domainImplementationType.newInstance())
.construct(objects);
} }
public fflib_IDomain newInstance(List<SObject> records, SObjectType domainSObjectType) public fflib_IDomain newInstance(List<SObject> records, SObjectType domainSObjectType)
@@ -106,22 +95,47 @@ public virtual class fflib_DynamicDomainFactory
public void setMock(fflib_ISObjectDomain mockDomain) public void setMock(fflib_ISObjectDomain mockDomain)
{ {
super.setMock(mockDomain.getType(), mockDomain); if (domainFactory == null) loadBindings();
this.domainFactory.setMock(mockDomain);
} }
public void setMock(Schema.SObjectType sObjectType, fflib_ISObjectDomain mockDomain) public void setMock(Schema.SObjectType sObjectType, fflib_ISObjectDomain mockDomain)
{ {
super.setMock(sObjectType, mockDomain); if (domainFactory == null) loadBindings();
this.domainFactory.setMock(sObjectType, mockDomain);
} }
public void setMock(fflib_IDomain mockDomain) public void setMock(fflib_IDomain mockDomain)
{ {
super.setMock(mockDomain.getType(), mockDomain); if (domainFactory == null) loadBindings();
this.domainFactory.setMock(mockDomain);
} }
public void setMock(Object domainType, fflib_IDomain mockDomain) public void setMock(Object domainType, fflib_IDomain mockDomain)
{ {
super.setMock(domainType, mockDomain); if (domainFactory == null) loadBindings();
this.domainFactory.setMock(domainType, mockDomain);
}
private void loadBindings()
{
fflib_IAppBindings appBindings = new fflib_AppBindings(resolver.resolve());
// If a package name is provide we should filter on package name
if (String.isNotBlank(this.packageName))
{
appBindings = appBindings.selectByPackageName(this.packageName);
}
Map<Object, Type> constructorByObject =
appBindings.selectByType(fflib_AppBindingType.Domain)
.getImplementationByBindingObject();
this.domainFactory = new fflib_ClassicDomainFactory(this.selectorFactory, constructorByObject);
} }
public class DeveloperException extends Exception {} public class DeveloperException extends Exception {}
@@ -32,86 +32,66 @@ public virtual class fflib_DynamicSelectorFactory
extends fflib_DynamicAppFactory extends fflib_DynamicAppFactory
implements fflib_ISelectorFactory implements fflib_ISelectorFactory
{ {
public static final fflib_AppBindingType SELECTOR_BINDING_TYPE = fflib_AppBindingType.Selector; protected fflib_ClassicSelectorFactory selectorFactory;
protected Map<Object, Object> mockSelectorImplByObjectType = new Map<Object, Object>();
public fflib_DynamicSelectorFactory(fflib_IAppBindingResolver resolver) public fflib_DynamicSelectorFactory(fflib_IAppBindingResolver resolver)
{
this(resolver, null);
}
public fflib_DynamicSelectorFactory(fflib_IAppBindingResolver resolver, String packageName)
{ {
this.resolver = resolver; this.resolver = resolver;
this.packageName = packageName;
} }
public fflib_ISObjectSelector newInstance(SObjectType sObjectType) public fflib_ISObjectSelector newInstance(SObjectType sObjectType)
{ {
if (hasMock(sObjectType)) return (fflib_ISObjectSelector) getMock(sObjectType); if (this.selectorFactory == null) loadBindings();
List<fflib_IAppBinding> bindings = resolver return this.selectorFactory.newInstance(sObjectType);
.byType(SELECTOR_BINDING_TYPE)
.byObject(sObjectType)
.bySharingMode(sharingMode)
.resolve();
if (bindings.isEmpty())
throw new DeveloperException(
String.format(
'Unable to find selector binding for {0} with sharing mode {1}',
new List<String>
{
sObjectType.getDescribe().getName(),
sharingMode.name()
}
)
);
return (fflib_ISObjectSelector) bindings.get(0).newImplInstance();
} }
public List<SObject> selectById(Set<Id> recordIds) public List<SObject> selectById(Set<Id> recordIds)
{ {
if (null == recordIds || recordIds.size() == 0) if (this.selectorFactory == null) loadBindings();
throw new fflib_Application.DeveloperException('Invalid record Id\'s set');
return selectById(recordIds, new List<Id>(recordIds)[0].getSObjectType()); return this.selectorFactory.selectById(recordIds);
} }
public List<SObject> selectById(Set<Id> recordIds, SObjectType sObjectType) public List<SObject> selectById(Set<Id> recordIds, SObjectType sObjectType)
{ {
if (null == recordIds || recordIds.size() == 0) if (this.selectorFactory == null) loadBindings();
throw new fflib_Application.DeveloperException('Invalid record Id\'s set');
return newInstance(sObjectType) return this.selectorFactory.selectById(recordIds, sObjectType);
.selectSObjectsById(recordIds);
} }
public List<SObject> selectByRelationship(List<SObject> relatedRecords, SObjectField relationshipField) public List<SObject> selectByRelationship(List<SObject> relatedRecords, SObjectField relationshipField)
{ {
Set<Id> relatedIds = if (this.selectorFactory == null) loadBindings();
new RelatedRecordsDomain(relatedRecords)
.getRelatedIds(relationshipField);
if (relatedIds.isEmpty()) return new List<SObject>(); return this.selectorFactory.selectByRelationship(relatedRecords, relationshipField);
return selectById(relatedIds);
} }
public void replaceWith(SObjectType sObjectType, Type replacementImplType) public void replaceWith(SObjectType sObjectType, Type replacementImplType)
{ {
resolver.byType(fflib_AppBindingType.Selector) if (this.selectorFactory == null) loadBindings();
.byObject(sObjectType)
.bySharingMode(this.sharingMode) this.selectorFactory.replaceWith(sObjectType, replacementImplType);
.resolve()
.get(0)
.setImplementationType(replacementImplType);
} }
public void setMock(fflib_ISObjectSelector selectorInstance) public void setMock(fflib_ISObjectSelector selectorInstance)
{ {
setMock(selectorInstance.sObjectType(), selectorInstance); if (this.selectorFactory == null) loadBindings();
this.selectorFactory.setMock(selectorInstance);
} }
public void setMock(SObjectType sObjectType, fflib_ISObjectSelector selectorInstance) public void setMock(SObjectType sObjectType, fflib_ISObjectSelector selectorInstance)
{ {
super.setMock(sObjectType, selectorInstance); if (this.selectorFactory == null) loadBindings();
this.selectorFactory.setMock(sObjectType, selectorInstance);
} }
public fflib_DynamicSelectorFactory setSharingMode(fflib_SharingMode sharingMode) public fflib_DynamicSelectorFactory setSharingMode(fflib_SharingMode sharingMode)
@@ -120,17 +100,22 @@ public virtual class fflib_DynamicSelectorFactory
return this; return this;
} }
private class RelatedRecordsDomain extends fflib_SObjects2 private void loadBindings()
{ {
public RelatedRecordsDomain(List<SObject> records) fflib_IAppBindings appBindings = new fflib_AppBindings(resolver.resolve());
// If a package name is provide we should filter on package name
if (String.isNotBlank(this.packageName))
{ {
super(records); appBindings = appBindings.selectByPackageName(this.packageName);
} }
public Set<Id> getRelatedIds(Schema.SObjectField relationshipField) Map<SObjectType, Type> implementationBySObjectType =
{ appBindings.selectByType(fflib_AppBindingType.Selector)
return getIdFieldValues(relationshipField); .selectBySharingMode(this.sharingMode)
} .getImplementationBySObjectType();
this.selectorFactory = new fflib_ClassicSelectorFactory(implementationBySObjectType);
} }
public class DeveloperException extends Exception {} public class DeveloperException extends Exception {}
@@ -32,53 +32,38 @@ public virtual class fflib_DynamicServiceFactory
extends fflib_DynamicAppFactory extends fflib_DynamicAppFactory
implements fflib_IServiceFactory implements fflib_IServiceFactory
{ {
public static final fflib_AppBindingType SERVICE_BINDING_TYPE = fflib_AppBindingType.Service; protected fflib_ClassicServiceFactory serviceFactory;
protected Map<System.Type, Object> mockServiceImplByObjectType = new Map<System.Type, Object>();
public fflib_DynamicServiceFactory(fflib_IAppBindingResolver resolver) public fflib_DynamicServiceFactory(fflib_IAppBindingResolver resolver)
{
this(resolver, null);
}
public fflib_DynamicServiceFactory(fflib_IAppBindingResolver resolver, String packageName)
{ {
this.resolver = resolver; this.resolver = resolver;
this.packageName = packageName;
} }
public Object newInstance(Type serviceInterfaceType) public Object newInstance(Type serviceInterfaceType)
{ {
if (hasMock(serviceInterfaceType)) return getMock(serviceInterfaceType); if (serviceFactory == null) loadBindings();
List<fflib_IAppBinding> bindings = resolver return this.serviceFactory.newInstance(serviceInterfaceType);
.byType(SERVICE_BINDING_TYPE)
.byInterfaceType(serviceInterfaceType)
.bySharingMode(sharingMode)
.resolve();
if (bindings.isEmpty())
throw new DeveloperException(
String.format(
'Unable to find service binding for {0} with sharing more {1}',
new List<String>
{
serviceInterfaceType.getName(),
sharingMode.name()
}
)
);
return bindings.get(0).newImplInstance();
} }
public void replaceWith(Type serviceInterfaceType, Type replacementImplType) public void replaceWith(Type serviceInterfaceType, Type replacementImplType)
{ {
resolver.byType(SERVICE_BINDING_TYPE) if (serviceFactory == null) loadBindings();
.byInterfaceType(serviceInterfaceType)
.bySharingMode(sharingMode) this.serviceFactory.replaceWith(serviceInterfaceType, replacementImplType);
.resolve()
.get(0) // TODO - validate that there are any resolved bindings
.setImplementationType(replacementImplType);
} }
public void setMock(Type serviceInterfaceType, Object serviceImpl) public void setMock(Type serviceInterfaceType, Object serviceImpl)
{ {
super.setMock(serviceInterfaceType, serviceImpl); if (serviceFactory == null) loadBindings();
this.serviceFactory.setMock(serviceInterfaceType, serviceImpl);
} }
public fflib_DynamicServiceFactory setSharingMode(fflib_SharingMode sharingMode) public fflib_DynamicServiceFactory setSharingMode(fflib_SharingMode sharingMode)
@@ -87,5 +72,22 @@ public virtual class fflib_DynamicServiceFactory
return this; return this;
} }
private void loadBindings()
{
fflib_IAppBindings appBindings = new fflib_AppBindings(resolver.resolve());
// If a package name is provide we should filter on package name
if (String.isNotBlank(this.packageName))
{
appBindings = appBindings.selectByPackageName(this.packageName);
}
Map<Type, Type> implementationByInterface =
appBindings.selectByType(fflib_AppBindingType.Service)
.getImplementationByInterface();
this.serviceFactory = new fflib_ClassicServiceFactory(implementationByInterface);
}
public class DeveloperException extends Exception {} public class DeveloperException extends Exception {}
} }
@@ -56,6 +56,10 @@ public interface fflib_IAppBinding
* @return Returns the Namespace for the binding * @return Returns the Namespace for the binding
*/ */
String getNamespacePrefix(); String getNamespacePrefix();
/**
* @return Returns the Application Package Name for the binding
*/
String getPackageName();
/** /**
* @return Returns the number of the order sequence in which the binding should be returned * @return Returns the number of the order sequence in which the binding should be returned
@@ -127,6 +131,13 @@ public interface fflib_IAppBinding
*/ */
fflib_IAppBinding setNamespacePrefix(String namespacePrefix); fflib_IAppBinding setNamespacePrefix(String namespacePrefix);
/**
* @param applicationPackageName The name of the Application Second Generation Package
*
* @return An instance of itself to enable method chaining
*/
fflib_IAppBinding setPackageName(String applicationPackageName);
/** /**
* Set the order number so that the resolver can sort the bindings. * Set the order number so that the resolver can sort the bindings.
* The higher the number, the more priority. * The higher the number, the more priority.
@@ -37,5 +37,6 @@ public interface fflib_IAppBindingResolver
fflib_IAppBindingResolver byInterfaceType(System.Type interfaceType); fflib_IAppBindingResolver byInterfaceType(System.Type interfaceType);
fflib_IAppBindingResolver bySharingMode(fflib_SharingMode sharingMode); fflib_IAppBindingResolver bySharingMode(fflib_SharingMode sharingMode);
fflib_IAppBindingResolver byNamespacePrefix(String namespacePrefix); fflib_IAppBindingResolver byNamespacePrefix(String namespacePrefix);
fflib_IAppBindingResolver byPackageName(String packageName);
List<fflib_IAppBinding> resolve(); List<fflib_IAppBinding> resolve();
} }
@@ -34,13 +34,17 @@ public interface fflib_IAppBindings
void addBinding(fflib_IAppBinding binding); void addBinding(fflib_IAppBinding binding);
void addBindings(List<fflib_IAppBinding> bindings); void addBindings(List<fflib_IAppBinding> bindings);
List<fflib_IAppBinding> getBindings();
Map<Object, Type> getImplementationByBindingObject();
Map<Type, Type> getImplementationByInterface();
Map<SObjectType, Type> getImplementationBySObjectType();
List<System.Type> getImplementations();
fflib_IAppBindings selectByObject(Object bindingObject); fflib_IAppBindings selectByObject(Object bindingObject);
fflib_IAppBindings selectBySequence(Decimal sequence); fflib_IAppBindings selectBySequence(Decimal sequence);
fflib_IAppBindings selectByType(fflib_AppBindingType bindingType); fflib_IAppBindings selectByType(fflib_AppBindingType bindingType);
fflib_IAppBindings selectByInterfaceType(System.Type interfaceType); fflib_IAppBindings selectByInterfaceType(System.Type interfaceType);
fflib_IAppBindings selectBySharingMode(fflib_SharingMode sharingMode); fflib_IAppBindings selectBySharingMode(fflib_SharingMode sharingMode);
fflib_IAppBindings selectByNamespacePrefix(String namespacePrefix); fflib_IAppBindings selectByNamespacePrefix(String namespacePrefix);
fflib_IAppBindings selectByPackageName(String packageName);
List<fflib_IAppBinding> getBindings();
List<System.Type> getImplementations();
} }
@@ -18,6 +18,10 @@
<behavior>Required</behavior> <behavior>Required</behavior>
<field>Type__c</field> <field>Type__c</field>
</layoutItems> </layoutItems>
<layoutItems>
<behavior>Edit</behavior>
<field>ApplicationPackage__c</field>
</layoutItems>
</layoutColumns> </layoutColumns>
<layoutColumns> <layoutColumns>
<layoutItems> <layoutItems>
@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<Layout xmlns="http://soap.sforce.com/2006/04/metadata">
<layoutSections>
<customLabel>false</customLabel>
<detailHeading>false</detailHeading>
<editHeading>true</editHeading>
<label>Information</label>
<layoutColumns>
<layoutItems>
<behavior>Required</behavior>
<field>MasterLabel</field>
</layoutItems>
<layoutItems>
<behavior>Required</behavior>
<field>DeveloperName</field>
</layoutItems>
</layoutColumns>
<layoutColumns>
<layoutItems>
<behavior>Edit</behavior>
<field>IsProtected</field>
</layoutItems>
<layoutItems>
<behavior>Required</behavior>
<field>NamespacePrefix</field>
</layoutItems>
</layoutColumns>
<style>TwoColumnsTopToBottom</style>
</layoutSections>
<layoutSections>
<customLabel>false</customLabel>
<detailHeading>false</detailHeading>
<editHeading>true</editHeading>
<label>System Information</label>
<layoutColumns>
<layoutItems>
<behavior>Readonly</behavior>
<field>CreatedById</field>
</layoutItems>
</layoutColumns>
<layoutColumns>
<layoutItems>
<behavior>Readonly</behavior>
<field>LastModifiedById</field>
</layoutItems>
</layoutColumns>
<style>TwoColumnsTopToBottom</style>
</layoutSections>
<layoutSections>
<customLabel>false</customLabel>
<detailHeading>false</detailHeading>
<editHeading>false</editHeading>
<layoutColumns/>
<style>CustomLinks</style>
</layoutSections>
<relatedLists>
<fields>MasterLabel</fields>
<fields>DeveloperName</fields>
<fields>NamespacePrefix</fields>
<relatedList>fflib_AppBinding__mdt.ApplicationPackage__c</relatedList>
</relatedLists>
<showEmailCheckbox>false</showEmailCheckbox>
<showHighlightsPanel>false</showHighlightsPanel>
<showInteractionLogPanel>false</showInteractionLogPanel>
<showRunAssignmentRulesCheckbox>false</showRunAssignmentRulesCheckbox>
<showSubmitAndAttachButton>false</showSubmitAndAttachButton>
</Layout>
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>ApplicationPackage__c</fullName>
<externalId>false</externalId>
<fieldManageability>DeveloperControlled</fieldManageability>
<label>Application Package</label>
<referenceTo>fflib_ApplicationPackage__mdt</referenceTo>
<relationshipLabel>Application Bindings</relationshipLabel>
<relationshipName>ApplicationBindings</relationshipName>
<required>false</required>
<type>MetadataRelationship</type>
<unique>false</unique>
</CustomField>
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomObject xmlns="http://soap.sforce.com/2006/04/metadata">
<description>Define individual packages if your application contains multiple second generation packages (2GP)</description>
<label>Application Package</label>
<pluralLabel>Application Packages</pluralLabel>
<visibility>Public</visibility>
</CustomObject>
@@ -111,6 +111,5 @@ public with sharing class fflib_MockSObjectUtil
Lead record = new Lead(Id = fflib_IDGenerator.generate(Schema.Lead.SObjectType)); Lead record = new Lead(Id = fflib_IDGenerator.generate(Schema.Lead.SObjectType));
SObject result = addFieldValue(record, Lead.ConvertedDate, today); SObject result = addFieldValue(record, Lead.ConvertedDate, today);
System.assertEquals(today, result.get(Lead.ConvertedDate)); System.assertEquals(today, result.get(Lead.ConvertedDate));
System.debug('result: '+result);
} }
} }
@@ -31,15 +31,24 @@
@IsTest(IsParallel=true) @IsTest(IsParallel=true)
public class fflib_DynamicDomainFactoryTest public class fflib_DynamicDomainFactoryTest
{ {
private static final SObjectType ACCOUNT_OBJECT_TYPE = Schema.Account.SObjectType;
private static final String PACKAGE_FORCE_APP = 'force-app';
private static final String PACKAGE_OTHER_APP = 'other-app';
private static final fflib_IAppBindingResolver bindingResolver = private static final fflib_IAppBindingResolver bindingResolver =
new fflib_AppBindingResolver(); new fflib_AppBindingResolver();
private static final fflib_DynamicDomainFactory Domain = private static final fflib_DynamicDomainFactory Domain =
new fflib_DynamicDomainFactory( new fflib_DynamicDomainFactory(
bindingResolver, bindingResolver,
fflib_DynamicSelectorFactoryTest.Selector); fflib_DynamicSelectorFactoryTest.Selector,
private static final SObjectType ACCOUNT_OBJECT_TYPE = Schema.Account.SObjectType; PACKAGE_FORCE_APP);
private static final fflib_DynamicDomainFactory OtherDomain =
new fflib_DynamicDomainFactory(
bindingResolver,
fflib_DynamicSelectorFactoryTest.Selector,
PACKAGE_OTHER_APP);
@IsTest @IsTest
static void itShouldReturnDomain() static void itShouldReturnDomain()
@@ -107,6 +116,19 @@ public class fflib_DynamicDomainFactoryTest
System.Assert.isTrue(instanceB.getRecords().isEmpty()); System.Assert.isTrue(instanceB.getRecords().isEmpty());
} }
@IsTest
static void itShouldReturnTheRightDomainForThePackage()
{
fflib_AppBindingsSelector.SELECTOR_IMPL_TYPE = AppBindingsSelectorMock.class;
final List<Account> records = generateAccountRecords();
System.Test.startTest();
fflib_IDomain otherDomain = OtherDomain.newInstance(records);
System.Test.stopTest();
System.Assert.isTrue(otherDomain instanceof fflib_SObjects2, 'Incorrect returned instance');
}
private static List<Account> generateAccountRecords() private static List<Account> generateAccountRecords()
{ {
@@ -128,18 +150,46 @@ public class fflib_DynamicDomainFactoryTest
} }
} }
public class OtherDomainConstructor implements fflib_IDomainConstructor
{
public fflib_IDomain construct(List<Object> objects)
{
return new fflib_SObjects2((List<SObject>) objects);
}
}
private class AppBindingsSelectorMock extends fflib_AppBindingsSelector private class AppBindingsSelectorMock extends fflib_AppBindingsSelector
{ {
public override List<fflib_AppBinding__mdt> selectAll() public override List<fflib_AppBinding__mdt> selectAll()
{ {
fflib_ApplicationPackage__mdt forceApp =
new fflib_ApplicationPackage__mdt(
Id = fflib_IDGenerator.generate(fflib_ApplicationPackage__mdt.SObjectType),
DeveloperName = PACKAGE_FORCE_APP);
fflib_ApplicationPackage__mdt otherApp =
new fflib_ApplicationPackage__mdt(
Id = fflib_IDGenerator.generate(fflib_ApplicationPackage__mdt.SObjectType),
DeveloperName = PACKAGE_OTHER_APP);
return new List<fflib_AppBinding__mdt> return new List<fflib_AppBinding__mdt>
{ {
new fflib_AppBinding__mdt( new fflib_AppBinding__mdt(
ApplicationPackage__c = forceApp.Id,
ApplicationPackage__r = forceApp,
Type__c = 'Domain', Type__c = 'Domain',
BindingObject__c = '', BindingObject__c = '',
BindingObjectAlternate__c = ACCOUNT_OBJECT_TYPE.getDescribe().getName(), BindingObjectAlternate__c = ACCOUNT_OBJECT_TYPE.getDescribe().getName(),
ImplementationType__c = MyDomainConstructor.class.getName(), ImplementationType__c = MyDomainConstructor.class.getName(),
SharingMode__c = 'None' SharingMode__c = 'None'
),
new fflib_AppBinding__mdt(
ApplicationPackage__c = otherApp.Id,
ApplicationPackage__r = otherApp,
Type__c = 'Domain',
BindingObject__c = '',
BindingObjectAlternate__c = ACCOUNT_OBJECT_TYPE.getDescribe().getName(),
ImplementationType__c = OtherDomainConstructor.class.getName(),
SharingMode__c = 'None'
) )
}; };
} }
@@ -153,7 +153,6 @@ private class fflib_SObjects2Test
} }
); );
System.Test.stopTest(); System.Test.stopTest();
System.debug('result: ' + JSON.serializePretty(result));
// THEN it should return the AccountId By CaseId // THEN it should return the AccountId By CaseId
System.Assert.areEqual(1, result.size(), 'Expected one account'); System.Assert.areEqual(1, result.size(), 'Expected one account');
@@ -194,7 +193,6 @@ private class fflib_SObjects2Test
} }
); );
System.Test.stopTest(); System.Test.stopTest();
System.debug('result: ' + JSON.serializePretty(result));
// THEN it should return the AccountId By CaseId // THEN it should return the AccountId By CaseId
System.Assert.areEqual(1, result.size(), 'Expected one account'); System.Assert.areEqual(1, result.size(), 'Expected one account');
@@ -91,7 +91,6 @@ private class fflib_LogEventAppenderTest
fflib_LogEventAppender eventAppender = new fflib_LogEventAppender(); fflib_LogEventAppender eventAppender = new fflib_LogEventAppender();
fflib_LogLevelFilter__c orgSettings = fflib_LogLevelFilter__c.getOrgDefaults(); fflib_LogLevelFilter__c orgSettings = fflib_LogLevelFilter__c.getOrgDefaults();
System.debug(orgSettings);
orgSettings.LogLevel__c = 'INFO'; orgSettings.LogLevel__c = 'INFO';
insert orgSettings; insert orgSettings;
@@ -96,7 +96,6 @@ private class fflib_LoggerTest
// THEN - It should be appended to the logAppender // THEN - It should be appended to the logAppender
final List<String> result = APPENDER_MOCK final List<String> result = APPENDER_MOCK
.getAppendedMessages(System.LoggingLevel.DEBUG); .getAppendedMessages(System.LoggingLevel.DEBUG);
System.debug(result);
System.Assert.areEqual(1, result.size(), 'Expected only one error'); System.Assert.areEqual(1, result.size(), 'Expected only one error');
System.Assert.isTrue(result.get(0).startsWith(ERROR_MESSAGE_FORMATTED_WITH_EXCEPTION), 'Incorrect returned error message'); System.Assert.isTrue(result.get(0).startsWith(ERROR_MESSAGE_FORMATTED_WITH_EXCEPTION), 'Incorrect returned error message');
} }