Comments (6)
Hi @parsam97,
The thought behind splitting the domain from the triggerhandler is to limit the scope of the domain class. The domain is now just a wrapper around a List of SObjects, providing methods to get & set fields, and to filter the SObjects. It shouldn't do something more.
If you would to combine a UnitOfWork with a domain method, that will immediately render that method useless if you want to use the same domain method in a before Insert/update context.
I would leave the UnitOfWork to the abstraction level that is calling the domain methods, e.g. a service method.
Alternatively you could add method overloads in the domain to handle that (see my example at the bottom).
Looking at your example, One thing I notice is that the domain goes beyond its scope, and invokes a selector. In those cases you would want to introduce a service method.
I also see that the trigger runs on Interaction__c
in a before insert context, which means that you do not require a unitOfWork. You can just set the fields with a domain setter method.
I would do something like:
InteractionsTriggerHandler.cls
public override void onBeforeInsert()
{
InteractionsService.assignOwnership(getRecords());
}
InteractionsService.cls
public static void assignOwnership(List<Interaction__c> records) {
Service.assignOwnership(records);
}
private static IInteractionsService Service() {
(IInteractionsService) Application.Service.newInstance(IInteractionsService.class);
}
InteractionsServiceImpl.cls
public void assignOwnership(List<Interaction__c> records) {
assignOwnership(Interactions.newInstance(records));
}
public void assignOwnership(Interactions domain){
// Collect channels
IChannels channels = Channels.newInstance(domain.getChannelIds());
// Create a map of key = Channel__c.Id to Channel.OwnerId
Map<Id, Id> ownerIdByChannelId = channels.getOwnerIdById();
domain.setOwnerIdByInteractionChannelId(ownerIdByChannelId);
}
Interaction.cls
public static IInteractions newInstance(List<Interaction__c> records)
{
return (IInteractions) Application.Domain.newInstance(records, Schema.Interaction__c.SObjectType);
}
public IInteractions setOwnerIdByInteractionChannelId(Map<Id, Id> ownerIdById) {
for (Interaction__c record : (List<Interaction__c>) getRecords())
{
if (ownerIdById.containsKey(record.Interaction_Channel__c) == false) {
continue;
}
record.ownerId = ownerIdById.get(record.Interaction_Channel__c);
}
return this;
}
And you could add a method overload to the domain method for the unitOfWork:
public IInteractions setOwnerIdByInteractionChannelId(fflib_ISObjectUnitOfWork uow, Map<Id, Id> ownerIdById) {
setOwnerIdByInteractionChannelId(ownerIdById);
uow.registerDirty(getRecords(), new List<SObjectField>{ Interaction__c.OwnerId }); // only register the modified field as dirty
}
If you would also use the fflib-apex-extensions package and extend your domain from fflib_SObject2
, then you could even change the setOwnerIdByInteractionChannelId
into this:
public IInteractions setOwnerIdByInteractionChannelId(Map<Id, Id> ownerIdById) {
setFieldValue(
Schema.Interaction__c.Interaction_Channel__c,
Schema.Interaction__c.OwnerId,
ownerIdById);
return this;
}
from fflib-apex-common.
Hi @wimvelzeboer,
Really appreciate you taking the time to reply and help me out 🙏🙏
Since my posting this question, I got Andrew Fawcett's 4 edition enterprise patterns book, where I learned more about CDCL and SDCL approaches. I've since decided to use the CDCL approach to simplify things for myself as I am just starting out.
The code you kindly provided still leaves me with the following question:
What if I there's a requirement to assign ownership (or something similar of the sort) onAfterInsert?
If I understand your suggestions correctly, this would mean the domain class should call the InteractionService to assign ownership onAfterInsert? It seems wrong to me because the InteractionService is just going to call the domain again, which is where we start from. Although yes, the service method would collect the necessary Channel__c data. But are we doing this just so we don't have our domain layer calling the selector layer or is there another reason?
Thanks again.
from fflib-apex-common.
@parsam97
In the scenario of an on after insert, I would do the following:
InteractionsTriggerHandler.cls
public override void onAfterInsert()
{
fflib_ISObjectUnitOfWork uow = Application.UnitOfWork.newInstance();
InteractionsService.assignOwnership(uow, getRecords());
uow.commitWork();
}
InteractionsService.cls
public static void assignOwnership(fflib_ISObjectUnitOfWork uow, List<Interaction__c> records) {
Service.assignOwnership(uow, records);
}
private static IInteractionsService Service() {
(IInteractionsService) Application.Service.newInstance(IInteractionsService.class);
}
InteractionsServiceImpl.cls
public void assignOwnership(fflib_ISObjectUnitOfWork uow, List<Interaction__c> records) {
assignOwnership(uow, Interactions.newInstance(records));
}
public void assignOwnership(fflib_ISObjectUnitOfWork uow, Interactions domain){
// Collect channels
IChannels channels = Channels.newInstance(domain.getChannelIds());
// Create a map of key = Channel__c.Id to Channel.OwnerId
Map<Id, Id> ownerIdByChannelId = channels.getOwnerIdById();
domain.setOwnerIdByInteractionChannelId(uow, ownerIdByChannelId);
}
Interaction.cls
public static IInteractions newInstance(List<Interaction__c> records)
{
return (IInteractions) Application.Domain.newInstance(records, Schema.Interaction__c.SObjectType);
}
public IInteractions setOwnerIdByInteractionChannelId(Map<Id, Id> ownerIdById) {
for (Interaction__c record : (List<Interaction__c>) getRecords())
{
if (ownerIdById.containsKey(record.Interaction_Channel__c) == false) {
continue;
}
record.ownerId = ownerIdById.get(record.Interaction_Channel__c);
}
return this;
}
public IInteractions setOwnerIdByInteractionChannelId(fflib_ISObjectUnitOfWork uow, Map<Id, Id> ownerIdById) {
setOwnerIdByInteractionChannelId(ownerIdById);
uow.registerDirty(getRecords(), new List<SObjectField>{ Interaction__c.OwnerId }); // only register the modified field as dirty
}
You would still use the same method to update the field, but you reach that method via slightly different method overloads with the unit-of-work as extra parameter.
So, the domain would indeed not call a selector class.
from fflib-apex-common.
Got it, thanks for the help again!
from fflib-apex-common.
public void assignOwnership(fflib_ISObjectUnitOfWork uow, Interactions domain){
@wimvelzeboer You mentioned this, including the domain as an argument to the Impl class. Did you do this so we could later call service method from domain like this: InteractionService.assignOwnership(this)
?
from fflib-apex-common.
Closing this as it's not a bug or feature request and has been largely addressed by William (thank you!)
from fflib-apex-common.
Related Issues (20)
- Unable to Deploy - Unhandled Exception : java.lang.RuntimeException HOT 1
- Unit test failure in multi-currency org HOT 4
- Deploy button doesn't deploy because there are test failures HOT 1
- Expected a QueryException due to read only user not having access to Opportunity HOT 7
- Switch to Inherited Sharing on SObjectDescribe and SObjectSelector HOT 2
- Selected tests in fflib_SObjectSelectorTest fail in an org with encryption enabled on Account.Name HOT 2
- Selector Mocks to Include SOQL Query Retrieve Check HOT 1
- fflib_SObjectUnitOfWork doCommitWork executes all dml for all registered types even if there are no changes
- Update README as session recordings are not available HOT 2
- fflib_SObjectDescribe.cls fails to resolve cross-object field paths for Person Accounts
- Aggregate SOQL support. HOT 2
- Disable Savepoint in tests HOT 4
- Inconsistent Code Coverage and Test Failures in fflib-apex-common HOT 1
- fflib_SObjectSelector and fflib_SObjectUnitOfWork have insufficient test code coverage. HOT 2
- Add queryWithBinds to Selector layer HOT 2
- Unit test failing fflib_SecurityUtilsTest & sysadmin_objectAndField_access HOT 8
- Coverage of UnitOfWork class HOT 1
- Versioning HOT 1
- fflib_SObjectSelectorTest failure with Lookup relationship and Person Type Accounts HOT 3
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from fflib-apex-common.