Our goals on Salesforce often conflict – we want:

  • Simplicity – so business requirements can evolve
  • Fault tolerance – to focus our effort on solutions rather than problems
  • Performance – to squeeze more DML and callouts into each transaction

Consider a university that offers online enrolments. The enrolment logic interacts with a number of systems including CRM. Here is an example implementation in Apex:

global class Enrol implements Callable {

    global Object call(String m, Map<String,Object> event) {

        // [1] student account
        Account account = new Account();
        account.Name = event.get('name') + '';
        
        // [2] related opportunity
        Opportunity opportunity = new Opportunity();
        opportunity.Name = event.get('course') + '';
        opportunity.CloseDate = Date.today();
        opportunity.StageName = 'New';
        opportunity.Account = account;
        
        // [3] staff email
        Messaging.SingleEmailMessage email
        = new Messaging.SingleEmailMessage();
        email.subject = 'Enrolment';
        email.saveAsActivity = false;
        email.plainTextBody = 'Details: ' + event;
        email.targetObjectId = UserInfo.getUserId();
        
        // [4] callout (eg to attendance system)
        HttpRequest request = new HttpRequest();
        request.setEndpoint(Url.getOrgDomainUrl().toExternalForm());
        request.setMethod('POST');
        new Http().send(request);
        
        // [5] return intended DML side effects
        return new List<Object>{email, opportunity, account};

    }
}

No DML is performed inside the service.

All the intended side effects, including record inserts and an email message, are returned in a list of intents. It’s like the Unit Of Work pattern. Let’s run 3 stateless services asynchronously:

Streams.Events.enqueue(new List<Object>{
    '{"_to": "Enrol", "name": "Bob", "course": "apex"}',
    '{"_to": "Enrol", "name": "Mark", "course": "java"}',
    '{"_to": "Enrol", "name": "Alice", "course": "ltng"}'
});

Or call everything synchronously, using the same static method with immediate = true:

Streams.Events.enqueue(new List<Object>{
    '{"_immediate": true, "_to": "Enrol", "name": "Bob", "course": "apex"}',
    '{"_immediate": true, "_to": "Enrol", "name": "Mark", "course": "java"}',
    '{"_immediate": true, "_to": "Enrol", "name": "Alice", "course": "ltng"}'
});

Whether we handle 3 enrolments or 3,000 enrolments, the business logic is free from boilerplate code. Transaction control, DML bulkification, and error recovery is automatic.

Experiment with stateless intents in Streams:
Docs – https://github.com/bigassforce/streams/wiki
Install – /packaging/installPackage.apexp?p0=04t7F000003iruR

Related Posts

Last modified: 20th July 2020

Comments

Write a Reply or Comment

Your email address will not be published.