Dependencies: Before you can use fflib-force-di you must install these dependencies
This plugin is designed to link force-di to the Apex Enterprise Patterns. It will replace parts of the existing fflib_Application Factories. Instead of hardcoded dependencies in Apex, this plugin allows you to use the force-di for dependencies instead.
It will also allow you to use the Apex Enterprise Patterns in a DX project that consists of multiple depended sub-packages. The core-package will then contain just an "empty" Application.cls that can be referenced by all sub-packages. If they need to add a dependency they no longer need to update the application class but just add another custom meta-data record in their own folder defining the dependency.
Example:
public with sharing class Application
{
public static final String APP_NAME = 'MyForceApp';
// Configure and create the UnitOfWorkFactory for this Application
public static final fflib_Application.UnitOfWorkFactory UnitOfWork =
new fflib_Application.UnitOfWorkFactory(
new List<SObjectType>
{
Account.SObjectType,
Contact.SObjectType
});
// Configure and create the ServiceFactory for this Application
public static final fflib_ServiceFactory Service = new fflib_ServiceFactoryImp();
// Configure and create the SelectorFactory for this Application
public static final fflib_SelectorFactory Selector = new fflib_SelectorFactoryImp(APP_NAME);
// Configure and create the DomainFactory for this Application
public static final fflib_DomainFactory Domain =
new fflib_DomainFactoryImp(APP_NAME, Application.Selector);
public static final fflib_TriggerHandlerFactory TriggerHandler =
new fflib_TriggerHandlerFactoryImp(APP_NAME, Domain);
}
The plugin required an APP_NAME
, which is either the Namespace
or another unique application identifier with a similar format as the Namespace.
No changes were made to the UnitOfWork factory, so will stay the same.
Instead of defining a list with all the dependencies we just create three new factory instances, one for Service, Selector and Domain. Only the Selector and Domain need to know the Namespace or App_Name. These factories will connect with Force-Di to retrieve the dependencies.
The methods on the application factories are the same as the current fflib implementation, which makes implementing this plugin very easy.
All the bindings are stored not in the standard force-di binding object but in
the custom meta-data object 'Enterprise Pattern Binding' (fflib_Binding__mdt
).
This object is very similar to the one in force-di with the exception of the specific Type
(Selector, Domain, Service).
This object only supports Apex files. Any other dependency injection for e.g. Lightning Components
still need to be registered in the force-di binding object.
- di_Bindings,
domain class for Di Bindings - di_Configurator, creates Di Bindings at runtime
- fflib_Bindings, Domain class for fflib_Binding_mdt
- fflib_BindingsSelector, Selector for the Custom Metadata object fflib_Binding__mdt
- fflib_CustomMetaDataModule, Dependency Injection module to register the bindings for the SoC layers
- fflib_DeveloperException, Generic exception class
- fflib_DomainFactory, Interface for the Domain factory to be able to dynamically instantiate domains
- fflib_SelectorFactory,
public fflib_ISObjectDomain newInstance(Set<Id> recordIds);
Queries the records and constructs a new domain instance for the query result
public with sharing class MyAccountService
{
public void myMethod(Set<Id> idSet)
{
Accounts domain = (Accounts) Application.Domain.newInstance(idSet);
...
}
}
public fflib_ISObjectDomain newInstance(List<SObject> records);
Gets the SObjectType from the list of records and constructs a new instance of the domain with the records
public with sharing class MyAccountService
{
public void myMethod(List<Accounts> records)
{
Accounts domain = (Accounts) Application.Domain.newInstance(records);
...
}
}
public fflib_ISObjectDomain newInstance(List<SObject> records, SObjectType domainSObjectType);
Gets the instance for the domain constructor from force-di and constructs a new domain
public with sharing class MyAccountService
{
public void myMethod(List<SObject> records)
{
Accounts domain = (Accounts) Application.Domain.newInstance(records, Account.SObjectType);
...
}
}
public void replaceWith(SObjectType sObjectType, Object domainImpl);
Dynamically replace a domain implementation at runtime. As this is a domain class and domain classes are constructed via the sub-class Constructor we need to replace the binding with the Constructor implementation and not the actual domain implementation.
public with sharing class MyAccountService
{
public void myMethod(List<Account> records)
{
if (isUserInTestGroup())
{
Application.Domain.replaceWith(Schema.Account.SObjectType, new TestGroep_AccountsImp.Constructor());
}
// This domain will be different for the users in the TestGroup
Accounts domain = (Accounts) Application.Domain.newInstance(records);
}
}
@IsTest
static void itShouldRunMyTest()
{
// GIVEN
fflib_ApexMocks mocks = new fflib_ApexMocks();
AccountsConstructor domainConstructorMock = (Accounts) mocks.mock(AccountsImp.class);
Application.Service.replaceWith(Schema.Account.SObjectType, domainMock)
}
void setMock(Schema.SObjectType sObjectType, Object domainImp);
public fflib_ISObjectSelector newInstance(Schema.SObjectType sObjectType);
public with sharing class MyAccountService
{
public void myMethod(Set<Id> idSet)
{
List<Account> records =
((AccountsSelector) Application.Selector.newInstance(Account.SObjectType))
.selectById(idSet);
...
}
}
public void replaceWith(Schema.SObjectType sObjectType, Object selectorImpl);
Used to replace the implementation for another, e.g. a mock
List<SObject> selectById(Set<Id> recordIds);
Method to query the given SObject records and internally creates an instance of the registered Selector and calls its selectSObjectById method.
public with sharing class MyAccountService
{
public void myMethod(Set<Id> idSet)
{
List<Account> records = (List<Account>) Application.Selector.selectById(idSet);
...
}
}
public
List<SObject> selectByRelationship(List<SObject> relatedRecords, Schema.SObjectField relationshipField);
Method to query related records to those provided, for example if passed a list of Opportunity records and the AccountId field will construct internally a list of Account Ids and call the registered Account selector to query the related Account records, e.g.
public with sharing class Opportunities
{
public List<Account> getRelatedAccountRecords()
{
return (List<Account>) Application.Selector.selectByRelationship(Records, Opportunity.AccountId);
}
}
public Object newInstance(Type serviceInterfaceType);
Creates a new instance of a service class by referencing its binding type
public with sharing class MyController
{
public void myMethod()
{
((MyService) Application.Service.newInstance(MyService.class))
.myServiceMethod();
}
}
public interface MyService
{
myServiceMethod();
}
public with sharing MyServiceImp implements MyService
{
public void myServiceMethod()
{
...
}
}
public void replaceWith(Type serviceInterfaceType, Object serviceImpl);
Used to replace the implementation for another, e.g. a mock
Folder | Description |
---|---|
sfdx-source/force-app/main/default | Core of the application |
sfdx-source/force-app/examples | Examples |
Have fflib use the force-di package to enable dependency injection