Application Factories - wimvelzeboer/fflib-apex-extensions GitHub Wiki
- Interface structure
- Classic, a backwards compatible implementation
- Dynamic a factory based on Dependency Injection, that can be configured instead of using hard reference maps
The existing Application factories of the apex-commons are not based on an interface structure, therefore it is hard to replace their implementations. The fflib-apex-extensions adds an interface structure and includes multiple implementations for you to choose from.
When you are already confortamble with the implementation of [apex-commons] and don't want to change too much, then the classic implementation is your thing. It is fully backward compatible and in the well known familiar structure with predefined static maps relating the interface to its implementation.
This interface structure adds a number of useful methods for you application;
-
fflib_IServiceFactory
-
replaceWith
This replaces an existing link in realtime between an interface-type to its Service class implementation (a binding) for another. This method can also be used to add a new binding on the fly.
-
replaceWith
-
fflib_ISelectorFactory
-
selectById (method overload)
This method overload has one additional argument accepting an SObjectType. This will avoid that time is spent to extract the SObjectType from the givenSet<Id>
and to validate that all members are of the same type. -
replaceWith
This replaces an existing link in realtime between an SObjectType to its Selector class implementation (a binding) for another. This method can also be used to add a new binding on the fly. - setMock (method overload)
-
selectById (method overload)
-
fflib_IDomainFactory
- newInstance (method overload)
-
replaceWith
This replaces an existing link in realtime between an SObjectType to the Domain class implementation (a binding) for another. This method can also be used to add a new binding on the fly. - setMock (method overload)
This will create or replaces an existing binding for another.
void replaceWith(Type serviceInterfaceType, Type replacementImplType)
Let's assume we have two implementation for AccountsService AccountsServiceImpl
and AlternativeAccountsServiceImpl
.
In the Service Factory IAccountsService
is bound to the AccountsServiceImpl
implementation.
IAccountsService accountsService = (IAccountsService) Application.Service.newInstance();
System.assert(accountsService instanceof AccountsServiceImpl);
// Replace the implementation binding
Application.Service.replaceWith(IAccountsService.class, MyAlternateAccountsServiceImpl.class);
IAccountsService accountsService = (IAccountsService) Application.Service.newInstance();
System.assert(accountsService instanceof AlternativeAccountsServiceImpl);
This method overload accepts an extra argument named sObjectType
.
By providing the SObjectType, the method assumes that all the provided Ids are of the given SObjectType.
Thereby executing faster as the original method doesn't need to iterate over all the ids
to validate that they are all from the same SObjectType.
List<SObject> selectById(Set<Id> recordIds, SObjectType sObjectType)
This will create or replaces an existing binding for another.
void replaceWith(SObjectType sObjectType, Type replacementImplType)
This method overload avoids the need to stub the selector mock to return its SObjectType
void setMock(SObjectType sObjectType, fflib_ISObjectSelector selectorInstance)
Instead of doing this:
fflib_ApexMocks mocks = new fflib_ApexMocks();
AccountsSelector serviceMock = (AccountsSelector) mocks.mock(AccountsSelector.class);
mocks.startStubbing();
mocks.when(selectorMock.sObjectType()).thenReturn(Schema.Account.SObjectType);
mocks.stopStubbing();
Application.Selector.setMock(serviceMock);
You can now do the same without the need to stub it:
fflib_ApexMocks mocks = new fflib_ApexMocks();
AccountsSelector serviceMock = (AccountsSelector) mocks.mock(AccountsSelector.class);
Application.Selector.setMock(Schema.Account.SObjectType, serviceMock);
The added argument sObjectType
avoids the selector to iterate over the given record Ids to validate them.
fflib_IDomain newInstance(Set<Id> recordIds, Schema.SObjectType sObjectType)
This will create or replaces an existing binding for another.
void replaceWith(Schema.SObjectType sObjectType, Type replacementImplType)
This method overload avoids the need to stub the selector mock to return its SObjectType
void setMock(Schema.SObjectType sObjectType, fflib_ISObjectDomain mockDomain)
Instead of doing this:
fflib_ApexMocks mocks = new fflib_ApexMocks();
IAccounts domainMock = (IAccounts) mocks.mock(IAccounts.class);
mocks.startStubbing();
mocks.when(domainMock.getType()).thenReturn(Schema.Account.SObjectType);
mocks.stopStubbing();
Application.Domain.setMock(domainMock);
You can now do the same without the need to stub it:
fflib_ApexMocks mocks = new fflib_ApexMocks();
IAccounts domainMock = (IAccounts) mocks.mock(IAccounts.class);
Application.Domain.setMock(Schema.Account.SObjectType, domainMock);
This is the implementation you are looking for when you are used to the Application Factories of the apex-commons and you do not want to change anything about the structure based on maps, but do like to use the added features of fflib-apex-extensions.
It is fully backwards compatible, and you only need to make a few small changes to your existing Application class, to use the added features.
The Application class is slightly different than the stafactories in the fflib_Application are replaced by an interface structure. To use the backwards compatible version of the factories, change your Application class into the example below. Pay special attention to the interface types and implementation classes of the factories.
Example
public class Application
{
public static final fflib_IUnitOfWorkFactory UnitOfWork =
new fflib_ClassicUnitOfWorkFactory(
new List<SObjectType>
{
Schema.Account.SObjectType,
Schema.Contact.SObjectType,
...
}
);
public static final fflib_IServiceFactory Service =
new fflib_ClassicServiceFactory(
new Map<Type, Type>
{
IAccountsService.class => AccountsServiceImpl.class,
IContactsService.class => ContactsServiceImpl.class,
...
}
);
public static final fflib_ISelectorFactory Selector =
new fflib_ClassicSelectorFactory(
new Map<SObjectType, Type>
{
Schema.Account.SObjectType => AccountsSelector.class,
Schema.Contact.SObjectType => ContactsSelector.class,
...
}
);
public static final fflib_IDomainFactory Domain =
new fflib_ClassicDomainFactory(
Application.Selector,
new Map<SObjectType, Type>
{
Schema.Account.SObjectType => Accounts.Constructor.class,
Schema.Contact.SObjectType => Contacts.Constructor.class,
...
}
);
}
The Dynamic Application Factory allows for realtime dependency injection. It is similar to force-di, which is written by Andrew Fawcett, but it has a focus on the Separation of Concern design pattern. It allows to configure the implementation binding to an interface or (S)ObjectType via Custom Metadata. The binding can even be replaced in run-time via Apex.
Architecture
The design structure below shows how the factories are connected to a single resolver which receives the bindings from the modules. The default module, if none is provided, is using a selector to query the data from the Custom Metadata object fflib_AppBinding__mdt.
+------------------+
| Service Factory |---+
+------------------+ |
|
+------------------+ | +----------+ Bindings +-----------+ +----------+
| Selector Factory |---+---| Resolver | <--------------| Module(s) | <-----| Selector |
+------------------+ | +----------+ +-----------+ | +----------+
| +-----------+ |
+------------------+ | |
| Domain Factory |---+ +---------------+
+------------------+ / Custom Metadata \
In the application class we start with configuring the Binding Resolver, which is responsible for loading and resolving the bindings for the Factories. If you want to store the binding in another way than in the default Custom Metadata object fflib_AppBinding__mdt, Than you can provide the resolver with a custom module (AppBindingModule).
The Factory properties only need a minimal amount of configuration, they require a reference to the binding resolver and the Sharing Mode is optional. If no sharing-mode is provided the default will be used.
Example
public class Application
{
private static fflib_IAppBindingResolver bindingResolver = new fflib_AppBindingResolver();
public static final fflib_IUnitOfWorkFactory UnitOfWork =
new fflib_ClassicUnitOfWorkFactory(
new List<SObjectType>
{
Schema.Account.SObjectType,
Schema.Contact.SObjectType,
...
}
);
public static final fflib_IServiceFactory Service =
new fflib_DynamicServiceFactory(bindingResolver)
.setSharingMode(fflib_SharingMode.WithSharing);
public static final fflib_ISelectorFactory Selector =
new fflib_DynamicSelectorFactory(bindingResolver);
public static final fflib_ISelectorFactory WithoutSharingSelector =
new fflib_DynamicSelectorFactory(
bindingResolver.bySharingMode(fflib_SharingMode.WithoutSharing)
);
public static final fflib_ISelectorFactory InheritedSharingSelector =
new fflib_DynamicSelectorFactory(
bindingResolver.bySharingMode(fflib_SharingMode.InheritedSharing)
);
public static final fflib_IDomainFactory Domain =
new fflib_DynamicDomainFactory(bindingResolver, Selector);
}