Application Factories - wimvelzeboer/fflib-apex-extensions GitHub Wiki

fflib-apex-extensions

Application Factory Interface Structure

  1. Interface structure
  2. Classic, a backwards compatible implementation
  3. Dynamic a factory based on Dependency Injection, that can be configured instead of using hard reference maps

Interface structure

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.
  • 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 given Set<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)
  • 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)
fflib_IServiceFactory - replaceWith

This will create or replaces an existing binding for another.

Syntax

void replaceWith(Type serviceInterfaceType, Type replacementImplType)

Example

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);
fflib_ISelectorFactory - selectById

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.

Syntax

List<SObject> selectById(Set<Id> recordIds, SObjectType sObjectType)

fflib_ISelectorFactory - replaceWith

This will create or replaces an existing binding for another.

Syntax

void replaceWith(SObjectType sObjectType, Type replacementImplType)

fflib_ISelectorFactory - setMock

This method overload avoids the need to stub the selector mock to return its SObjectType

Syntax

void setMock(SObjectType sObjectType, fflib_ISObjectSelector selectorInstance)

Example

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);
fflib_IDomainFactory - newInstance

The added argument sObjectType avoids the selector to iterate over the given record Ids to validate them.

Syntax

fflib_IDomain newInstance(Set<Id> recordIds, Schema.SObjectType sObjectType)

fflib_IDomainFactory - replaceWith

This will create or replaces an existing binding for another.

Syntax

void replaceWith(Schema.SObjectType sObjectType, Type replacementImplType)

fflib_IDomainFactory - setMock

This method overload avoids the need to stub the selector mock to return its SObjectType

Syntax

void setMock(Schema.SObjectType sObjectType, fflib_ISObjectDomain mockDomain)

Example

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);

Classic

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.

Application Class

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,
                            ...
                    }
            );
}

Dynamic

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 \

The Application Class

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);
}
⚠️ **GitHub.com Fallback** ⚠️