1

I'm developing a project using fflib's Enterprise Architecture patterns, based on their example app (https://github.com/apex-enterprise-patterns/fflib-apex-common-samplecode). I am a bit confused about bulkification in Domain layer classes. In the example, Domain classes are bulkified (they are constructed with a List of SObjects), but every example operation seem to assume that every record in the list will receive exactly the same input. For example, there is this method in sfdx-source/apex-common-samplecode/main/classes/domains/Opportunities.cls:

public void applyDiscount(Decimal discountPercentage, fflib_ISObjectUnitOfWork uow)

it applies the discountPercentage to every Opportunity inside the instance of Domain class. But what if I need to apply different percentages to each Opportunity? Let's say I have Map<Id, Decimal> oppIdToDiscountPercentage in my Service class (received from REST API, for example). Should I create a method in Opportunities Domain class that accepts such a map, that is:

// Opportunities Domain class
public void applyDiscount(Map<Id, Decimal> oppIdToDiscountPercentage, fflib_ISObjectUnitOfWork uow)

or should I instead instantiate an instance of Opportunities domain for each Opportunity record in Service class and use the existing Domain method with a singular argument?

// Opportunities Service class
Map<Id, Opportunities> oppIdToDomain = ... // create Domain for each Opp

for (Id oppId : oppIdToDiscountPercentage.keySet()) {
    Decimal discountPercentage = oppIdToDiscountPercentage.get(oppId);
    oppIdToDomain.get(oppId).applyDiscount(discountPercentage, uow);
}

Which is considered a proper (or better) practice?

1

1 Answer 1

2

The example in fflib-apex-common-samplecode assumes that all opportunities will get a common discount%

Your conundrum is a common one

Option 1

If in fact you want the domain class to apply the discount calculation but the % varies across multiple opportunities in the service layer

You could do (in the service class):

for (Opportunity o: opportunities) {
   new Opportunities(new List<Opportunity>{o}).applyDiscount(somePct);
}

That is, instantiate the domain class with one record and call the domain method.

Option 2

Change the signature of the applyDiscounts domain method to take a list of objects where the object has various properties needed to apply the discount, such as the rate

class DiscountDetails {
  Decimal rate;
}

and then the service class does

new Opportunities(opportunities).applyDiscount(discountDetails);

where discountDetails is an array of DiscountDetail that agrees 1:1 with the corresponding Opportunity (i.e. Opportunity[0] uses discountDetails[0].

If you don't like that kind of implicit binding, then change DiscountDetail to include the opportunityId and let the domain class internally build an association map

Option 3

Move the discount calculation out of the domain class into the service layer

Summary

A lot of this is a judgement call and I've used all three approaches; sometimes refactoring over time from one approach to the other. I'd urge you to read the chapters on the Domain and Service layers within the latest edition of the Andy Fawcett Salesforce Platform Enterprise Architecture for insight.

The domain class job is to preserve and protect the integrity of the SObject and be a central location for field DML change detection. For example to ensure multiple fields are internally in agreement; handle defaulting; respond to changes in values and act as a redirector to service layers for follow-on actions.

Every domain class public method should be written to assume it operates on the domains list of sobjects. I also find the domain class to be an ideal place to cache recordTypeIds and other constants so the code base has a single source for commonly referenced values

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .