Monday, March 4, 2024

Salesforce Apex: Command Pattern

The Command design pattern encapsulates a request as an object, thereby allowing for parameterization of clients with queues, requests, and operations. It also allows for the support of undoable operations. In Salesforce Apex, you can use the Command pattern to encapsulate all the details of an operation in a single object.

A common functionality in Salesforce CPQ where the Command pattern might be useful is when performing different actions on a Quote object, such as applying discounts, recalculating prices, or generating documents. These operations can be encapsulated as command objects, making them easy to manage and extend.

Here's an example of how you might implement the Command pattern in Salesforce CPQ:

// The Command interface declares a method for executing a command.
public interface ICommand {
    void execute();
}

// Concrete Commands implement various kinds of requests.
public class ApplyDiscountCommand implements ICommand {
    private Quote quote;
    private Decimal discountRate;
    
    public ApplyDiscountCommand(Quote quote, Decimal discountRate) {
        this.quote = quote;
        this.discountRate = discountRate;
    }
    
    public void execute() {
        // Logic to apply discount to the quote
        quote.TotalPrice *= (1 - discountRate);
        update quote;
    }
}

public class RecalculatePricesCommand implements ICommand {
    private Quote quote;
    
    public RecalculatePricesCommand(Quote quote) {
        this.quote = quote;
    }
    
    public void execute() {
        // Logic to recalculate prices for the quote
        // This might involve complex logic and multiple steps
    }
}

public class GenerateDocumentCommand implements ICommand {
    private Quote quote;
    
    public GenerateDocumentCommand(Quote quote) {
        this.quote = quote;
    }
    
    public void execute() {
        // Logic to generate a document for the quote
    }
}

// The Invoker class is associated with one or several commands. It sends a request to the command.
public class CommandInvoker {
    private ICommand command;
    
    public void setCommand(ICommand command) {
        this.command = command;
    }
    
    public void executeCommand() {
        command.execute();
    }
}

// Usage
CommandInvoker invoker = new CommandInvoker();
Quote currentQuote = [SELECT Id, TotalPrice FROM Quote WHERE Id = :someQuoteId];

// Applying a discount
ICommand applyDiscount = new ApplyDiscountCommand(currentQuote, 0.1); // 10% discount
invoker.setCommand(applyDiscount);
invoker.executeCommand();

// Recalculating prices
ICommand recalculatePrices = new RecalculatePricesCommand(currentQuote);
invoker.setCommand(recalculatePrices);
invoker.executeCommand();

// Generating a document
ICommand generateDocument = new GenerateDocumentCommand(currentQuote);
invoker.setCommand(generateDocument);
invoker.executeCommand();

In this example, we have an ICommand interface with an execute method, which must be implemented by all concrete command classes. We implement three concrete commands (ApplyDiscountCommand, RecalculatePricesCommand, and GenerateDocumentCommand) that encapsulate the request details and the operations to be performed on the Quote object.

The CommandInvoker class is used to execute these commands. It allows you to set the command to be executed and then calls the execute method on the command object.

By applying the Command pattern, you can add new commands easily without changing the existing code, which adheres to the Open/Closed Principle. It also allows you to queue, log, or rollback operations if necessary.

2nd Example

Let's say we have different post-price calculation operations that need to be applied to quote lines after the main pricing logic of CPQ has been run. We can define a command interface and create multiple command classes, each encapsulating the logic for a specific operation.

Step 1: Define the Command interface

public interface IQuoteLineCommand {
    void execute(List<SBQQ__QuoteLine__c> quoteLines);
}

Step 2: Implement specific Commands

public class ApplyBulkDiscountCommand implements IQuoteLineCommand {
    private Decimal discountRate;
    
    public ApplyBulkDiscountCommand(Decimal discountRate) {
        this.discountRate = discountRate;
    }
    
    public void execute(List<SBQQ__QuoteLine__c> quoteLines) {
        for (SBQQ__QuoteLine__c line : quoteLines) {
            line.SBQQ__NetPrice__c *= (1 - discountRate);
        }
    }
}

public class ApplyTaxCommand implements IQuoteLineCommand {
    private Decimal taxRate;
    
    public ApplyTaxCommand(Decimal taxRate) {
        this.taxRate = taxRate;
    }
    
    public void execute(List<SBQQ__QuoteLine__c> quoteLines) {
        for (SBQQ__QuoteLine__c line : quoteLines) {
            line.SBQQ__NetPrice__c *= (1 + taxRate);
        }
    }
}

Step 3: Use the Commands in your CPQ logic

public class QuoteLinePriceProcessor {
    private List<IQuoteLineCommand> commands = new List<IQuoteLineCommand>();

    public void addCommand(IQuoteLineCommand command) {
        commands.add(command);
    }

    public void processQuoteLines(List<SBQQ__QuoteLine__c> quoteLines) {
        for (IQuoteLineCommand command : commands) {
            command.execute(quoteLines);
        }
    }
}

public with sharing class QuoteLineCalculator {

    public void calculate(List<SBQQ__QuoteLine__c> quoteLines) {
        QuoteLinePriceProcessor processor = new QuoteLinePriceProcessor();
        
        // Define the commands to be executed
        processor.addCommand(new ApplyBulkDiscountCommand(0.05)); // 5% bulk discount
        processor.addCommand(new ApplyTaxCommand(0.07)); // 7% tax
        
        // Process all quote lines with the defined commands
        processor.processQuoteLines(quoteLines);
        
        // Bulk update the quote lines with changes
        update quoteLines;
    }
}

The Command pattern is used here to encapsulate post-price calculation operations into objects (ApplyBulkDiscountCommand and ApplyTaxCommand). Each command has an execute method that applies a specific operation to a list of SBQQ__QuoteLine__c records.

The QuoteLinePriceProcessor class acts as an invoker that keeps a list of commands and can execute them in order. This allows for adding, removing, or reordering commands without changing the core logic in the QuoteLineCalculator class.

The QuoteLineCalculator class demonstrates how the commands can be utilized in a bulkified manner to process quote lines. It adds the necessary commands to the processor and then executes them, performing all operations in bulk to prevent hitting governor limits.

The Command pattern provides flexibility and extensibility to the pricing logic in CPQ. You can easily add new commands for additional operations, or modify existing ones without affecting other parts of the system.

This pattern is helpful for organizing complex sets of operations, and when you need an undo feature, you can extend the command interface to include an undo method, allowing you to reverse the operation if necessary.

Share This:    Facebook Twitter

0 comments:

Post a Comment

Total Pageviews

My Social Profiles

View Sonal's profile on LinkedIn

Tags

__proto__ $Browser Access Grants Accessor properties Admin Ajax AllowsCallouts Apex Apex Map Apex Sharing AssignmentRuleHeader AsyncApexJob Asynchronous Auth Provider AWS Callbacks Connected app constructor Cookie CPU Time CSP Trusted Sites CSS Custom settings CustomLabels Data properties Database.Batchable Database.BatchableContext Database.query Describe Result Destructuring Dynamic Apex Dynamic SOQL Einstein Analytics enqueueJob Enterprise Territory Management Enumeration escapeSingleQuotes featured Flows geolocation getGlobalDescribe getOrgDefaults() getPicklistValues getRecordTypeId() getRecordTypeInfosByName() getURLParameters Google Maps Governor Limits hasOwnProperty() Heap Heap Size IIFE Immediately Invoked Function Expression Interview questions isCustom() Javascript Javascript Array jsForce Lightning Lightning Components Lightning Events lightning-record-edit-form lightning:combobox lightning:icon lightning:input lightning:select LockerService Lookup LWC Manual Sharing Map Modal Module Pattern Named Credentials NodeJS OAuth Object.freeze() Object.keys() Object.preventExtensions() Object.seal() Organization Wide Defaults Override PDF Reader Performance performance.now() Permission Sets Picklist Platform events Popup Postman Primitive Types Profiles Promise propertyIsEnumerable() prototype Query Selectivity Queueable Record types Reference Types Regex Regular Expressions Relationships Rest API Rest Operator Revealing Module Pattern Role Hierarchy Salesforce Salesforce Security Schema.DescribeFieldResult Schema.DescribeSObjectResult Schema.PicklistEntry Schema.SObjectField Schema.SObjectType Security Service Components Shadow DOM Sharing Sharing Rules Singleton Slots SOAP API SOAP Web Services SOQL SOQL injection Spread Operator Star Rating stripInaccessible svg svgIcon Synchronous this Token Triggers uiObjectInfoApi Upload Files VSCode Web Services XHR
Scroll To Top