2

Apologies in advance if this question is too opinion based.

I am trying to lead us to a more defined separation of concerns in our Salesforce development. Currently, it is not much of a consideration in the development process.

I wanted to ask about how to keep the domain layer classes from getting bloated. In this case, I am talking about an FFLIB-style class that implements trigger handler logic. I understand and agree with the flow of dependencies Clients -> Service -> Domain.

This does seem to lead to all the business logic landing in a domain class at some point if our domains don't call services.

But if Domains contain object-specific behavior.. validations etc. And the service layer is core business logic it seems challenging never to have a Domain call a service.

If I have an AccountDomain and on-insert, if the account name contains "foo", I want notify our external Foo Account Partner. What is the best place for that logic to live?

I would normally think that onInsert calls FooVenderCalloutService()...

public override void onBeforeInsert() {      
        //if name.contains('foo){
        //PartnerCallout callout = new PartnerCalloutService(foo);
         }
    }
4
  • 2
    My interpretation of the pattern has always been to try and minimise what we put into the Domain layer by strictly keeping it to DML operations and minimal validations that might be required. In my opinion you've landed on the correct place for that notification logic to sit, as it's definitely a business logic element rather than something directly related to database operation. Commented Apr 26, 2023 at 19:46
  • I feel like I am violating some bed rock of Separation of Concerns in the Domain is not at the bottom of the stack. Commented Apr 26, 2023 at 20:30
  • The main violation you have is that callouts should go in after context.
    – Adrian Larson
    Commented Apr 26, 2023 at 20:50
  • LOL fair, I did not think of that when I threw together the sample. Commented Apr 26, 2023 at 21:04

1 Answer 1

2

First of all, the latest fflib separates trigger handling and domain into separate classes. This helps with bloat issues. Triggers deal with

  • onApplyDefaults
  • onValidate
  • onBeforeInsert ...

This code might exploit domain methods and reference domain constants but more typically call service methods (especially the onAfterInsert, onAfterUpdate, and onAfterDelete entry points)

Second of all, the useful references to read are the Lightning Enterprise Architecture book 4th edition by Andy Fawcett which in turn is based on Martin Fowler's various books/postings.

UPDATE 2023-06-05

The 4th edition discusses two models (see p. 197-199):

  • Combined Domain Class Logic - the domain class (e.g. Accounts.cls) includes trigger handlers as well as other domain methods (this was the pattern used since the 1st edition)
  • Split Domain Class Logic - where trigger handlers get their own class AccountsHandler.cls) and other domain methods use the domain class (e.g. Accounts.cls)

Opinion

This is what I put in my domain classes

  • domain (i.e. sobject) specific constants
  • methods that provide a facade to service classes around domain field values (e.g. perhaps it is not so simple to determine if an Opportunity is overdue and a domain method Boolean[] isOverdue() can be used. Or you might have a log() method that captures the current relevant fields of each sobject in the domain for logging.
  • methods that implement an interface that many domains must support. For example, in my org, I have an interface called IPii with a single method called scrub(). When a request comes in to scrub a Contact's PII, the service class that handles it delegates to all domain classes that implement IPii by calling scrub().

In general, business transaction logic (e.g. the classic Bank transfer money from account A to account B) are handled by service classes. The domain's job is work around the SObject but not know how to deal with cross sObject DML (what the service layer does)

Having business logic in service classes means they can be called by invocable Apex (hence callable by Flows) or, of course Apex REST, VF controller, or Apex Web Services. This promotes reusablity.

6
  • Thanks for the feedback. I am reading Fawcetts book right now 3rd edition and his sample app domains are implementing fflib_SObject Domain with the trigger handler methods. I did not see anything new in Github except the latest security updates. I think I saw another blog that talked about domains never calling services and this made me think I was doing it wrong (if your trigger handler logic is in your domain). Fowler talks about domain logic and application logic in the layers and that makes total sense. I might have worried about nothing as far as domain calling service. Commented Apr 26, 2023 at 21:41
  • I think maybe the source of my concern is Trigger Handler vs. Domain because the behavior is combined. Commented Apr 26, 2023 at 21:43
  • 1
    3rd edition was written before trigger handler and domain were separated by one of the Open Source contributors. I just ordered 4th edition and I don't know if that enhancement is reflected. See PR and PR for lengthy discussion and examples
    – cropredy
    Commented Apr 26, 2023 at 23:19
  • 1
    @BrooksJohnson - see my June 5 update above
    – cropredy
    Commented Jun 5, 2023 at 15:53
  • 1
    @BrooksJohnson - given you got those august folks to agree with you, you're in good company as an fflib pro!
    – cropredy
    Commented Jun 7, 2023 at 17:17

You must log in to answer this question.

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