Monday, March 4, 2024

Salesforce Apex: Template Method Pattern

The Template Method design pattern is used to define the skeleton of an algorithm in an operation, deferring some steps to subclasses. It lets one redefine certain steps of an algorithm without changing the algorithm's structure. In Salesforce Apex, particularly in the context of CPQ, this pattern can be useful for setting up a standard process for quote calculation with certain steps that vary depending on the context or product.

A common functionality in CPQ where the Template Method pattern can be applied is in the customization of quote or quote line calculations. CPQ has a standard calculation process, but businesses often need to apply additional, context-specific logic, such as industry-specific pricing adjustments, geographical tax calculations, or promotional discounts.

Let's design a Template Method pattern to implement a customized pricing algorithm that allows for different types of adjustments to be made to the quote lines.

Step 1: Define the abstract base class with a template method

public abstract class QuoteCalculatorTemplate {
    // The template method defines the skeleton of the algorithm.
    public final void calculate(List<SBQQ__QuoteLine__c> quoteLines) {
        applyStandardPricing(quoteLines);
        applyCustomAdjustments(quoteLines);
        applyTaxes(quoteLines);
        finalizeCalculations(quoteLines);
    }
    
    // These methods are common and have a standard implementation.
    private void applyStandardPricing(List<SBQQ__QuoteLine__c> quoteLines) {
        // Apply the standard CPQ pricing logic.
    }
    
    private void applyTaxes(List<SBQQ__QuoteLine__c> quoteLines) {
        // Apply standard tax logic.
    }
    
    private void finalizeCalculations(List<SBQQ__QuoteLine__c> quoteLines) {
        // Finalize the calculations, e.g., rounding off the prices.
    }
    
    // These methods are placeholders for custom logic and should be overridden.
    protected abstract void applyCustomAdjustments(List<SBQQ__QuoteLine__c> quoteLines);
}

Step 2: Create concrete subclasses to implement the varying steps

public class IndustrySpecificQuoteCalculator extends QuoteCalculatorTemplate {
    // Override the necessary methods to provide custom behavior.
    protected override void applyCustomAdjustments(List<SBQQ__QuoteLine__c> quoteLines) {
        for (SBQQ__QuoteLine__c line : quoteLines) {
            // Apply industry-specific pricing adjustments.
            // For example, apply a discount based on the product's industry segment.
        }
    }
}

public class PromotionalQuoteCalculator extends QuoteCalculatorTemplate {
    protected override void applyCustomAdjustments(List<SBQQ__QuoteLine__c> quoteLines) {
        for (SBQQ__QuoteLine__c line : quoteLines) {
            // Apply promotional discounts based on certain criteria.
            // For example, check if the product is part of a current promotion.
        }
    }
}

Step 3: Use the concrete classes in the CPQ process

public with sharing class QuoteLinePriceManager {
    public static void calculatePrices(List<SBQQ__QuoteLine__c> quoteLines) {
        // Determine the context and select the appropriate calculator.
        QuoteCalculatorTemplate calculator;
        
        if (isIndustrySpecificContext()) {
            calculator = new IndustrySpecificQuoteCalculator();
        } else if (isPromotionalPeriod()) {
            calculator = new PromotionalQuoteCalculator();
        } else {
            // Fallback to a default calculator if needed
            calculator = new DefaultQuoteCalculator();
        }
        
        // Execute the template method to perform the calculations.
        calculator.calculate(quoteLines);
        
        // Bulk update the quote lines with the calculated prices.
        update quoteLines;
    }

    private static Boolean isIndustrySpecificContext() {
        // Implement your logic to determine if the context is industry-specific.
        return false; // Simplified for example purposes.
    }

    private static Boolean isPromotionalPeriod() {
        // Implement your logic to determine if it's a promotional period.
        return true; // Simplified for example purposes.
    }
}

In this example, QuoteCalculatorTemplate provides the structure for the quote calculation process. It defines the template method calculate, which outlines the steps for calculating quotes. The steps that are common across all calculations, such as applying standard pricing and taxes, are implemented directly within this class. applyCustomAdjustments is an abstract method that subclasses must implement to provide custom pricing logic.

IndustrySpecificQuoteCalculator and PromotionalQuoteCalculator are concrete implementations that provide specific algorithms for their respective scenarios. When calculatePrices is called, it determines the context and chooses the appropriate subclass of QuoteCalculatorTemplate to use. The chosen calculator's calculate method is then called to perform the pricing calculation, and the quote lines are updated in bulk.

Share This:    Facebook Twitter

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

Salesforce Apex: Observer Pattern

The Observer Pattern is a behavioral design pattern where an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods. This pattern defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically. This is particularly useful for creating a publish-subscribe mechanism to enable loose coupling between the system components.

In the context of CPQ, the Observer Pattern can be useful in scenarios where changes to certain objects (like a Quote or a Product) need to trigger updates in related objects or execute certain business logic (such as recalculating prices or applying discounts).

Here is an example illustrating the Observer Pattern in Salesforce Apex:

Step 1: Create an observer interface.

public interface IQuoteObserver {
    void update(Quote quote);
}

Step 2: Create the subject class that observers can subscribe to.

public class Quote {
    private List<IQuoteObserver> observers = new List<IQuoteObserver>();
    private Decimal totalPrice;

    // Constructor
    public Quote(Decimal totalPrice) {
        this.totalPrice = totalPrice;
    }

    // Method to subscribe observers
    public void subscribe(IQuoteObserver observer) {
        observers.add(observer);
    }

    // Method to unsubscribe observers
    public void unsubscribe(IQuoteObserver observer) {
        observers.remove(observer);
    }

    // Method to notify observers of changes
    public void notifyObservers() {
        for (IQuoteObserver observer : observers) {
            observer.update(this);
        }
    }

    // Method to change the price and notify observers
    public void setTotalPrice(Decimal newPrice) {
        this.totalPrice = newPrice;
        notifyObservers();
    }

    // Getter for totalPrice
    public Decimal getTotalPrice() {
        return totalPrice;
    }
}

Step 3: Create concrete observers that react to changes.

public class DiscountApplier implements IQuoteObserver {
    private Decimal discountRate;

    public DiscountApplier(Decimal discountRate) {
        this.discountRate = discountRate;
    }

    public void update(Quote quote) {
        Decimal newPrice = quote.getTotalPrice() * (1 - discountRate);
        System.debug('Applying discount: New Price is ' + newPrice);
        // Discount logic can be applied here, and the quote can be updated accordingly
    }
}

public class TaxCalculator implements IQuoteObserver {
    private Decimal taxRate;

    public TaxCalculator(Decimal taxRate) {
        this.taxRate = taxRate;
    }

    public void update(Quote quote) {
        Decimal taxAmount = quote.getTotalPrice() * taxRate;
        System.debug('Calculating tax: Total Tax is ' + taxAmount);
        // Tax calculation logic can be applied here, and the quote can be updated accordingly
    }
}

Step 4: Use the Quote class and attach observers.

Quote quote = new Quote(1000.00);

// Create observers
DiscountApplier discountApplier = new DiscountApplier(0.1); // 10% discount
TaxCalculator taxCalculator = new TaxCalculator(0.15); // 15% tax

// Subscribe observers to the quote
quote.subscribe(discountApplier);
quote.subscribe(taxCalculator);

// Change the quote's price and notify observers
quote.setTotalPrice(1200.00); // This will automatically notify the observers

In the example above, we have a Quote class, which is the subject that maintains a list of observers. The DiscountApplier and TaxCalculator are observers that implement the IQuoteObserver interface. When the setTotalPrice method on the Quote is called, it triggers the notifyObservers method, which in turn calls the update method on each observer, allowing them to react to the price change.

The Observer Pattern is widely used in Salesforce CPQ for keeping various parts of the application in sync. It helps in situations where you have multiple pieces of logic that need to respond to changes in the data model, facilitating a clean separation of concerns.

2nd Example

In Salesforce CPQ, a common use case that can benefit from the Observer pattern is when multiple aspects of a Quote need to be recalculated or updated based on changes to Quote Line Items. For example, you might need to update the Quote's total price, apply discounts, and recalculate taxes when a Quote Line Item is modified.

Let's create an example with multiple observers that handle different updates: one for recalculating the total price, another for applying discounts, and a third one for recalculating taxes.

Firstly, define the Observer interface:

public interface QuoteObserver {
    void calculateUpdates(List<Id> quoteIds);
}

Create a Subject class that Quote Line Items can notify:

public class QuoteLineItemSubject {
    private static List<QuoteObserver> observers = new List<QuoteObserver>();

    public static void attach(QuoteObserver observer) {
        observers.add(observer);
    }

    public static void notifyObservers(List<Id> quoteIds) {
        for (QuoteObserver observer : observers) {
            observer.calculateUpdates(quoteIds);
        }
    }
}

Now, implement different Observers for each aspect of the Quote that needs to be updated:

public class QuoteTotalPriceCalculator implements QuoteObserver {
    public void calculateUpdates(List<Id> quoteIds) {
        // Calculate total price for each Quote and bulkify the update
        // Logic to calculate and update total price
    }
}

public class QuoteDiscountHandler implements QuoteObserver {
    public void calculateUpdates(List<Id> quoteIds) {
        // Apply discounts to each Quote and bulkify the update
        // Logic to calculate and update discounts
    }
}

public class QuoteTaxCalculator implements QuoteObserver {
    public void calculateUpdates(List<Id> quoteIds) {
        // Recalculate taxes for each Quote and bulkify the update
        // Logic to calculate and update taxes
    }
}

In a trigger on the Quote Line Item object, attach the observers and trigger the notifications:

trigger QuoteLineItemTrigger on QuoteLineItem (after insert, after update, after delete, after undelete) {
    // Collect the affected Quote IDs
    Set<Id> quoteIds = new Set<Id>();
    for (QuoteLineItem item : Trigger.isDelete ? Trigger.old : Trigger.new) {
        quoteIds.add(item.QuoteId);
    }

    // Attach the observers
    QuoteLineItemSubject.attach(new QuoteTotalPriceCalculator());
    QuoteLineItemSubject.attach(new QuoteDiscountHandler());
    QuoteLineItemSubject.attach(new QuoteTaxCalculator());
    
    // Notify observers with the updated quote IDs
    QuoteLineItemSubject.notifyObservers(new List<Id>(quoteIds));
}

Each observer's calculateUpdates method should handle bulk operations and should be implemented to query the necessary Quote and Quote Line Item records, perform the calculations, and update the Quotes in a bulkified manner.

Here is an example for the QuoteTotalPriceCalculator:

public class QuoteTotalPriceCalculator implements QuoteObserver {
    public void update(List<Id> quoteIds) {
        // Aggregate the total prices from Quote Line Items
        Map<Id, Decimal> quoteTotals = new Map<Id, Decimal>();
        for (AggregateResult ar : [
            SELECT QuoteId, SUM(TotalPrice) total
            FROM QuoteLineItem
            WHERE QuoteId IN :quoteIds
            GROUP BY QuoteId
        ]) {
            Id quoteId = (Id)ar.get('QuoteId');
            Decimal total = (Decimal)ar.get('total');
            quoteTotals.put(quoteId, total);
        }

        // Update the Quotes with the new totals
        List<Quote> quotesToUpdate = new List<Quote>();
        for (Id quoteId : quoteTotals.keySet()) {
            Quote quote = new Quote(Id = quoteId, TotalPrice = quoteTotals.get(quoteId));
            quotesToUpdate.add(quote);
        }

        // Perform a bulk update
        update quotesToUpdate;
    }
}

This approach ensures that whenever a Quote Line Item triggers an update, all related aspects of the Quote are automatically and efficiently updated through their respective observers. The pattern allows for easy extension, as you can add more observers to handle additional functionalities without modifying existing code. This makes the system more maintainable and scalable.

Aggregating the changes from all observers in a centralized manner

To ensure DML operations are only performed in one place, you can aggregate the changes from all observers in a centralized manner and perform the update once all observers have contributed their modifications. This can be accomplished by collecting the results of each observer's calculations and then applying those changes in bulk after all observers have processed.

Here's how you can modify the code:

First, modify the QuoteObserver interface to return a Map<Id, Quote> which contains the Quote Id and the modified Quote record.

public interface QuoteObserver {
    Map<Id, Quote> calculateUpdates(List<Id> quoteIds);
}

Each observer will now implement the calculateUpdates method to return the map of changes without performing any DML operations:

public class QuoteTotalPriceCalculator implements QuoteObserver {
    public Map<Id, Quote> calculateUpdates(List<Id> quoteIds) {
        // Logic to calculate total prices
        // Return a map of Quote records with updated total prices
    }
}

// Implement similar logic for QuoteDiscountHandler and QuoteTaxCalculator

In the Subject class, modify the notifyObservers method to collect all changes:

public class QuoteLineItemSubject {
    private static List<QuoteObserver> observers = new List<QuoteObserver>();

    // ...

    public static Map<Id, Quote> notifyObservers(List<Id> quoteIds) {
        Map<Id, Quote> quotesToUpdate = new Map<Id, Quote>();

        for (QuoteObserver observer : observers) {
            Map<Id, Quote> observerUpdates = observer.calculateUpdates(quoteIds);
            for (Id quoteId : observerUpdates.keySet()) {
                Quote updatedQuote = observerUpdates.get(quoteId);
                if (quotesToUpdate.containsKey(quoteId)) {
                    // Merge changes with existing updates, ensuring no overwrites
                    // This could include summing totals, recalculating taxes, etc.
                    // You'll define the merge logic based on your business requirements
                } else {
                    quotesToUpdate.put(quoteId, updatedQuote);
                }
            }
        }

        return quotesToUpdate;
    }
}

Now, the trigger will call notifyObservers and then perform the DML operation with the collected changes:

trigger QuoteLineItemTrigger on QuoteLineItem (after insert, after update, after delete, after undelete) {
    // Collect the affected Quote IDs and attach observers
    Set<Id> quoteIds = new Set<Id>();
    for (QuoteLineItem item : Trigger.isDelete ? Trigger.old : Trigger.new) {
        quoteIds.add(item.QuoteId);
    }

    QuoteLineItemSubject.attach(new QuoteTotalPriceCalculator());
    QuoteLineItemSubject.attach(new QuoteDiscountHandler());
    QuoteLineItemSubject.attach(new QuoteTaxCalculator());

    // Notify observers and collect updates
    Map<Id, Quote> quotesToUpdate = QuoteLineItemSubject.notifyObservers(new List<Id>(quoteIds));

    // Perform a bulk update with the collected changes
    if (!quotesToUpdate.isEmpty()) {
        update quotesToUpdate.values();
    }
}

With this approach, each observer calculates the necessary updates and returns them. The Subject class aggregates these updates, ensuring that there are no conflicts or overwrites. Finally, the trigger performs a single DML operation with all the aggregated changes. This pattern keeps DML operations consolidated in one place, making the code cleaner, easier to maintain, and more efficient.

Share This:    Facebook Twitter

Salesforce Apex: Composite Pattern

The Composite Pattern is a structural pattern that allows you to compose objects into tree structures to represent part-whole hierarchies. It lets clients treat individual objects and compositions of objects uniformly.

In the context of CPQ, the Composite Pattern can be used to model product bundles, where a bundle can contain individual products or other bundles. This pattern is useful when creating a product hierarchy that includes complex combinations of products and services.

Here is an example illustrating the Composite Pattern in Salesforce Apex:

Step 1: Define a common component interface.

public interface IProductComponent {
    Decimal getPrice();
    String getDescription();
}

Step 2: Create leaf objects that represent individual products.

public class Product implements IProductComponent {
    private Decimal price;
    private String description;

    public Product(Decimal price, String description) {
        this.price = price;
        this.description = description;
    }

    public Decimal getPrice() {
        return this.price;
    }

    public String getDescription() {
        return this.description;
    }
}

Step 3: Create a composite object that can contain other products or product bundles.

public class ProductBundle implements IProductComponent {
    private List<IProductComponent> children = new List<IProductComponent>();
    private String description;

    public ProductBundle(String description) {
        this.description = description;
    }

    public void add(IProductComponent component) {
        children.add(component);
    }

    public void remove(IProductComponent component) {
        children.remove(component);
    }

    public List<IProductComponent> getChildren() {
        return children;
    }

    public Decimal getPrice() {
        Decimal total = 0;
        for (IProductComponent child : children) {
            total += child.getPrice();
        }
        return total;
    }

    public String getDescription() {
        return this.description;
    }
}

Step 4: Use the composite in a CPQ scenario.

ProductBundle mainBundle = new ProductBundle('Main Product Bundle');

// Individual products
Product product1 = new Product(100.00, 'Product 1');
Product product2 = new Product(200.00, 'Product 2');
mainBundle.add(product1);
mainBundle.add(product2);

// Sub-bundle
ProductBundle subBundle = new ProductBundle('Sub Product Bundle');
Product subProduct1 = new Product(50.00, 'Sub Product 1');
Product subProduct2 = new Product(75.00, 'Sub Product 2');
subBundle.add(subProduct1);
subBundle.add(subProduct2);

// Add sub-bundle to the main bundle
mainBundle.add(subBundle);

// Calculating the total price of the main bundle
System.debug(mainBundle.getDescription() + ' Total Price: ' + mainBundle.getPrice());

In the example above, Product represents a leaf node, and ProductBundle represents a composite node that can have child nodes. Both implement the IProductComponent interface, allowing clients to treat individual products and bundles of products uniformly. When calculating the total price of a bundle, the composite iterates over its children and sums the prices, whether they are individual products or nested bundles.

Share This:    Facebook Twitter

Salesforce Apex: Decorator Pattern

The Decorator design pattern is a structural design pattern used to dynamically add behavior to an object without altering its structure. In Salesforce Apex, this can be particularly useful when you want to enhance the functionality of an object without changing its existing code, which is a common scenario when dealing with managed packages like Salesforce CPQ (formerly Steelbrick CPQ), where you cannot modify the source code.

A common functionality in Salesforce CPQ is the pricing calculation of QuoteLineItems, where you might need to apply different pricing strategies based on certain criteria. The Decorator pattern allows you to dynamically adjust the pricing calculations without modifying the original QuoteLineItem code.

Here's a simplified example demonstrating how we could use the Decorator pattern in Salesforce Apex to apply different pricing strategies to CPQ QuoteLineItems:

First, let's create an interface that will define the common functionality for our Quote line items:

public interface IQuoteLineItem {
    void calculatePrice();
    Decimal getPrice();
}

Next, we have a basic implementation of our Quote line item:

public class BasicQuoteLineItem implements IQuoteLineItem {
    private Decimal basePrice;
    
    public BasicQuoteLineItem(Decimal price) {
        this.basePrice = price;
    }
    
    public void calculatePrice() {
        // Basic price calculation logic
    }
    
    public Decimal getPrice() {
        return basePrice;
    }
}

Now, we can create our decorators to extend the functionality. Each decorator implements the same interface and takes in an instance of IQuoteLineItem.

public abstract class QuoteLineItemDecorator implements IQuoteLineItem {
    protected IQuoteLineItem decoratedItem;
    
    public QuoteLineItemDecorator(IQuoteLineItem item) {
        this.decoratedItem = item;
    }
    
    public void calculatePrice() {
        decoratedItem.calculatePrice();
    }
    
    public Decimal getPrice() {
        return decoratedItem.getPrice();
    }
}

public class DiscountDecorator extends QuoteLineItemDecorator {
    private Decimal discountRate;
    
    public DiscountDecorator(IQuoteLineItem item, Decimal discountRate) {
        super(item);
        this.discountRate = discountRate;
    }
    
    public void calculatePrice() {
        super.calculatePrice();
        applyDiscount();
    }
    
    private void applyDiscount() {
        Decimal price = decoratedItem.getPrice();
        decoratedItem.getPrice() * (1 - discountRate);
    }
}

public class TaxDecorator extends QuoteLineItemDecorator {
    private Decimal taxRate;
    
    public TaxDecorator(IQuoteLineItem item, Decimal taxRate) {
        super(item);
        this.taxRate = taxRate;
    }
    
    public void calculatePrice() {
        super.calculatePrice();
        addTax();
    }
    
    private void addTax() {
        Decimal price = decoratedItem.getPrice();
        decoratedItem.getPrice() * (1 + taxRate);
    }
}

Finally, we can use these decorators to dynamically add behavior to our QuoteLineItem objects:

List<IQuoteLineItem> quoteLineItems = new List<IQuoteLineItem>();

for (QuoteLineItem qli : [SELECT Id, UnitPrice FROM QuoteLineItem WHERE QuoteId = :someQuoteId]) {
    IQuoteLineItem item = new BasicQuoteLineItem(qli.UnitPrice);
    
    // Apply discounts only if the item qualifies
    if (meetsDiscountCriteria(qli)) {
        item = new DiscountDecorator(item, 0.10); // 10% discount
    }
    
    // Apply tax to all items
    item = new TaxDecorator(item, 0.08); // 8% tax
    
    item.calculatePrice(); // Calculate final price
    quoteLineItems.add(item);
}

In the example above, we query all the QuoteLineItem records outside of the loop and then process them in bulk. However, you would need to add additional logic to handle updates to these records back to the database, ensuring that you collect all the updated items and perform a single DML update outside of the loop.

2nd Example

One common requirement might be to apply additional discounts or fees based on complex business rules that are not covered by standard CPQ functionality. For instance, you might need to apply volume discounts, promotional discounts, or special fees that depend on the combination of products and customer attributes.

Here's an example of how you could use the Decorator pattern to add custom discounting logic to CPQ quote lines in a bulkified way:

Step 1: Define the interface

public interface IQuoteLineCalculator {
    void calculate(List<SBQQ__QuoteLine__c> quoteLines);
}

Step 2: Implement the base calculator

public class BaseQuoteLineCalculator implements IQuoteLineCalculator {
    public void calculate(List<SBQQ__QuoteLine__c> quoteLines) {
        // Here you would apply the base CPQ pricing logic
        // For simplicity, let's assume this is a no-op, since CPQ calculates base prices
    }
}

Step 3: Implement the decorators

public abstract class QuoteLineCalculatorDecorator implements IQuoteLineCalculator {
    protected IQuoteLineCalculator innerCalculator;
    
    public QuoteLineCalculatorDecorator(IQuoteLineCalculator inner) {
        this.innerCalculator = inner;
    }
    
    public void calculate(List<SBQQ__QuoteLine__c> quoteLines) {
        // Delegate to the wrapped calculator
        innerCalculator.calculate(quoteLines);
    }
}

public class VolumeDiscountDecorator extends QuoteLineCalculatorDecorator {
    public VolumeDiscountDecorator(IQuoteLineCalculator inner) {
        super(inner);
    }
    
    public override void calculate(List<SBQQ__QuoteLine__c> quoteLines) {
        // First, let the inner calculator do its work
        super.calculate(quoteLines);
        
        // Then apply volume discounts
        for (SBQQ__QuoteLine__c line : quoteLines) {
            Integer quantity = line.SBQQ__Quantity__c;
            // Example volume discount logic
            if (quantity > 100) {
                line.SBQQ__AdditionalDiscount__c = 0.1; // 10% discount
            }
        }
    }
}

public class PromotionalDiscountDecorator extends QuoteLineCalculatorDecorator {
    public PromotionalDiscountDecorator(IQuoteLineCalculator inner) {
        super(inner);
    }
    
    public override void calculate(List<SBQQ__QuoteLine__c> quoteLines) {
        // Apply the inner calculator's logic
        super.calculate(quoteLines);
        
        // Then apply promotional discounts
        for (SBQQ__QuoteLine__c line : quoteLines) {
            // Example promotional discount logic
            if (isPromotionalProduct(line.SBQQ__Product__c)) {
                line.SBQQ__AdditionalDiscount__c += 0.05; // Additional 5% discount
            }
        }
    }
    
    private Boolean isPromotionalProduct(Id productId) {
        // Implement logic to determine if the product is on promotion
        return true; // Simplified for example purposes
    }
}

Step 4: Use the decorators in your CPQ plugin or custom logic

public with sharing class CustomQuoteCalculator {

    public void calculateQuoteLineAdjustments(List<SBQQ__QuoteLine__c> quoteLines) {
        IQuoteLineCalculator calculator = new BaseQuoteLineCalculator();
        
        // Wrap the calculator with decorators as needed
        calculator = new VolumeDiscountDecorator(calculator);
        calculator = new PromotionalDiscountDecorator(calculator);
        
        // Perform calculations
        calculator.calculate(quoteLines);
        
        // Update the quote lines in bulk
        update quoteLines;
    }

}

This example demonstrates how the Decorator pattern can be applied in Salesforce CPQ to compose different pricing behaviors dynamically. Each decorator adds its layer of logic on top of the existing calculations. The BaseQuoteLineCalculator represents the standard CPQ pricing logic, and each decorator adds additional adjustments like volume discounts or promotional discounts.

The CustomQuoteCalculator class demonstrates how these decorators can be used in a CPQ plugin or custom logic, ensuring that all calculations are done in bulk to avoid governor limits. The list of SBQQ__QuoteLine__c records is passed through each decorator, with each decorator applying its specific adjustments.

This pattern offers a clean and maintainable structure for extending CPQ logic, enabling you to add or remove decorators as business requirements evolve without modifying the underlying classes.

Share This:    Facebook Twitter

Salesforce Apex: Factory and Strategy Patterns

Factory Pattern

The Factory Pattern is a creational design pattern that provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objects that will be created. In Salesforce Apex, you can use the Factory Pattern to encapsulate the object creation process and to promote loose coupling, thereby making your code more modular, flexible, and maintainable.

To utilize the Factory Pattern in Salesforce Apex, you can define an interface or an abstract class with a method declaration that subclasses or implementing classes will use to create instances of objects. Here's an example to illustrate the Factory Pattern in Salesforce Apex:

Suppose you have different types of notifications that you want to send from Salesforce, such as EmailNotification, SMSNotification, and PushNotification. You can create a factory to generate these notification instances based on the type required.

Step 1: Define an interface with a method to send notifications.

public interface INotification {
    void send(String message);
}

Step 2: Implement the interface with different notification types.

public class EmailNotification implements INotification {
    public void send(String message) {
        // Logic to send email notification
        System.debug('Email notification sent: ' + message);
    }
}

public class SMSNotification implements INotification {
    public void send(String message) {
        // Logic to send SMS notification
        System.debug('SMS notification sent: ' + message);
    }
}

public class PushNotification implements INotification {
    public void send(String message) {
        // Logic to send push notification
        System.debug('Push notification sent: ' + message);
    }
}

Step 3: Create a Factory class to generate instances of the notifications.

public class NotificationFactory {
    public enum NotificationType {
        EMAIL, SMS, PUSH
    }

    public static INotification getNotificationInstance(NotificationType type) {
        switch on type {
            when EMAIL {
                return new EmailNotification();
            }
            when SMS {
                return new SMSNotification();
            }
            when PUSH {
                return new PushNotification();
            }
            when else {
                throw new IllegalArgumentException('Invalid notification type');
            }
        }
    }
}

Step 4: Use the Factory to get instances and send notifications.

public class NotificationService {

    public void sendNotification(NotificationFactory.NotificationType type, String message) {
        INotification notification = NotificationFactory.getNotificationInstance(type);
        notification.send(message);
    }
}

To test this pattern, you can write a test method that uses the NotificationService to send different types of notifications:

@IsTest
private class NotificationServiceTest {
    @IsTest static void testSendNotifications() {
        NotificationService service = new NotificationService();
        
        // Test sending email notification
        service.sendNotification(NotificationFactory.NotificationType.EMAIL, 'Test email message');
        
        // Test sending SMS notification
        service.sendNotification(NotificationFactory.NotificationType.SMS, 'Test SMS message');
        
        // Test sending push notification
        service.sendNotification(NotificationFactory.NotificationType.PUSH, 'Test push message');
    }
}

With this setup, adding a new notification type requires you to create a new class that implements INotification and update the NotificationFactory to handle the new type. This design adheres to the open/closed principle, one of the SOLID principles, making it easy to extend the functionality without modifying existing code.

Strategy Pattern

The Strategy Pattern is a behavioral design pattern that enables selecting an algorithm's behavior at runtime. Instead of implementing a single algorithm directly, code receives run-time instructions as to which in a family of algorithms to use.

In the context of your Salesforce Apex example with notifications, you can use the Strategy Pattern to define a set of interchangeable algorithms for sending notifications. The client code can then choose the appropriate algorithm based on the context.

Here’s an example to illustrate the Strategy Pattern in Salesforce Apex:

Step 1: Define an interface with a method to send notifications, just like in the Factory Pattern example.

public interface INotificationStrategy {
    void send(String message);
}

Step 2: Implement the interface with different strategies for sending notifications.

public class EmailNotificationStrategy implements INotificationStrategy {
    public void send(String message) {
        // Logic to send email notification
        System.debug('Email notification sent: ' + message);
    }
}

public class SMSNotificationStrategy implements INotificationStrategy {
    public void send(String message) {
        // Logic to send SMS notification
        System.debug('SMS notification sent: ' + message);
    }
}

public class PushNotificationStrategy implements INotificationStrategy {
    public void send(String message) {
        // Logic to send push notification
        System.debug('Push notification sent: ' + message);
    }
}

Step 3: Create a context class that uses a notification strategy.

public class NotificationContext {
    private INotificationStrategy strategy;

    // Constructor to set the strategy
    public NotificationContext(INotificationStrategy strategy) {
        this.strategy = strategy;
    }

    // Method to send notification using the strategy
    public void sendNotification(String message) {
        strategy.send(message);
    }

    // Method to change the strategy at runtime
    public void setStrategy(INotificationStrategy strategy) {
        this.strategy = strategy;
    }
}

Step 4: Use the context class to send notifications.

public class NotificationSender {

    public void sendNotification(String type, String message) {
        INotificationStrategy strategy;

        if (type == 'EMAIL') {
            strategy = new EmailNotificationStrategy();
        } else if (type == 'SMS') {
            strategy = new SMSNotificationStrategy();
        } else if (type == 'PUSH') {
            strategy = new PushNotificationStrategy();
        } else {
            throw new IllegalArgumentException('Invalid notification type');
        }

        NotificationContext context = new NotificationContext(strategy);
        context.sendNotification(message);
    }
}

In this example, NotificationSender is responsible for selecting the appropriate strategy based on the notification type and then using a NotificationContext to send the message.

To test this pattern, you can write a test method that sends different types of notifications:

@IsTest
private class NotificationSenderTest {
    @IsTest static void testSendNotifications() {
        NotificationSender sender = new NotificationSender();
        
        // Test sending email notification
        sender.sendNotification('EMAIL', 'Test email message');
        
        // Test sending SMS notification
        sender.sendNotification('SMS', 'Test SMS message');
        
        // Test sending push notification
        sender.sendNotification('PUSH', 'Test push message');
    }
}

Difference between Factory and Strategy Patterns:

  • Factory Pattern is a creational pattern used to create objects. It hides the instantiation logic of the classes and refers to the newly created object through a common interface. The client doesn't know about which concrete class is being instantiated.
  • Strategy Pattern is a behavioral pattern used to select an algorithm's behavior at runtime. It defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

In the given examples, the Factory Pattern would be used if you wanted a single point (the factory class) to handle the instantiation of notification objects, while the Strategy Pattern is used when the algorithm for sending the notification can be chosen at runtime by the client code. With Strategy, you define a context in which different strategies can be applied, and you can switch between them as needed.

(This blog post is generated by ChatGPT)

Share This:    Facebook Twitter

Dependency Injection in Salesforce Apex

In Salesforce Apex, Dependency Injection (DI) is a design pattern that allows a class to receive dependencies from an external source rather than creating them itself. This makes the class more flexible, testable, and modular.

Problem Statement

In a Salesforce implementation for a Quote-to-Cash process, you may have a scenario where you need to process payments using different payment gateways (e.g., PayPal, Stripe, or a custom gateway). Implementing the code to handle different payment gateways directly within your classes can lead to tightly coupled code, which is hard to maintain and not flexible for future extensions.

How Dependency Injection Can Solve the Issue:

Dependency Injection (DI) can be used to create more maintainable and testable code by decoupling the classes that implement business logic from the classes that implement specific functionalities, like payment processing. DI allows you to inject the specific payment gateway implementation at runtime, making the code more modular and easier to extend with new payment gateways without modifying existing code.

Here's an example of how you can implement DI in Apex to solve this problem:

Step 1: Define an Interface

First, define an interface that declares the methods all payment processors should implement.

public interface IPaymentProcessor {
    Boolean processPayment(Decimal amount, String currencyCode, Map<String, Object> paymentDetails);
}

Step 2: Implement the Interface for Each Payment Gateway

Create classes that implement this interface for different payment gateways.

public class PayPalPaymentProcessor implements IPaymentProcessor {
    public Boolean processPayment(Decimal amount, String currencyCode, Map<String, Object> paymentDetails) {
        // PayPal-specific implementation
        // ...
        return true;
    }
}

public class StripePaymentProcessor implements IPaymentProcessor {
    public Boolean processPayment(Decimal amount, String currencyCode, Map<String, Object> paymentDetails) {
        // Stripe-specific implementation
        // ...
        return true;
    }
}

Step 3: Inject the Payment Processor

Create a PaymentService class that will use the payment processor. The processor is injected through the constructor.

public class PaymentService {
    private IPaymentProcessor paymentProcessor;

    // Constructor for dependency injection
    public PaymentService(IPaymentProcessor processor) {
        this.paymentProcessor = processor;
    }

    public Boolean handlePayment(Decimal amount, String currencyCode, Map<String, Object> paymentDetails) {
        return paymentProcessor.processPayment(amount, currencyCode, paymentDetails);
    }
}

Step 4: Usage

Now, you can instantiate the PaymentService with the desired payment processor dynamically.

// Example of injecting PayPalPaymentProcessor
IPaymentProcessor payPalProcessor = new PayPalPaymentProcessor();
PaymentService paymentService = new PaymentService(payPalProcessor);
Boolean result = paymentService.handlePayment(100.00, 'USD', new Map<String, Object>{'orderId' => '12345'});

// Example of injecting StripePaymentProcessor
IPaymentProcessor stripeProcessor = new StripePaymentProcessor();
paymentService = new PaymentService(stripeProcessor);
result = paymentService.handlePayment(200.00, 'USD', new Map<String, Object>{'invoiceId' => '67890'});

Benefits of Using Dependency Injection

  1. Testability: It's easier to write unit tests by mocking the IPaymentProcessor interface.
  2. Extensibility: If a new payment gateway needs to be added, you only need to create a new class that implements the IPaymentProcessor interface without changing the existing code.
  3. Maintainability: Changing the payment logic for a specific gateway does not impact other parts of the system.
  4. Loose Coupling: The PaymentService class doesn't depend on concrete payment processor implementations, making the system more flexible and robust.

Integrate Custom Metadata Types with Dependency Injection in your Apex code

Using Custom Metadata Types in Salesforce can make the code even more dynamic by allowing administrators to configure which payment processor to use without changing the code. This approach can provide greater flexibility and control from the Salesforce setup interface.

Step 1: Create a Custom Metadata Type

Create a Custom Metadata Type called PaymentGatewaySetting with the following fields:

  1. GatewayName (Text): The name of the payment gateway (e.g., "PayPal", "Stripe").
  2. ClassName (Text): The Apex class name that implements the IPaymentProcessor interface for the corresponding gateway.

Step 2: Insert Records for Each Payment Gateway

Create records for each payment gateway within the Custom Metadata Type. For example:

  • GatewayName: "PayPal", ClassName: "PayPalPaymentProcessor"
  • GatewayName: "Stripe", ClassName: "StripePaymentProcessor"

Step 3: Fetch the Configuration and Instantiate the Processor

Modify your service class to fetch the payment processor class name from the Custom Metadata and use the Type.forName method to dynamically instantiate the processor.

public class PaymentService {
    private IPaymentProcessor paymentProcessor;

    // Constructor for dependency injection is removed

    // Method to set the payment processor dynamically based on Custom Metadata
    public void setPaymentProcessor(String gatewayName) {
        PaymentGatewaySetting__mdt setting = [
            SELECT ClassName__c
            FROM PaymentGatewaySetting__mdt
            WHERE GatewayName__c = :gatewayName
            LIMIT 1
        ];

        if (setting != null) {
            Type processorType = Type.forName(setting.ClassName__c);
            if (processorType != null) {
                this.paymentProcessor = (IPaymentProcessor)processorType.newInstance();
            }
        }
    }

    public Boolean handlePayment(Decimal amount, String currencyCode, Map<String, Object> paymentDetails) {
        if (paymentProcessor == null) {
            // Handle the error - payment processor not set
            return false;
        }
        return paymentProcessor.processPayment(amount, currencyCode, paymentDetails);
    }
}

Step 4: Usage

Now, you can set the payment processor based on the configured gateway name:

PaymentService paymentService = new PaymentService();
paymentService.setPaymentProcessor('PayPal');
Boolean result = paymentService.handlePayment(100.00, 'USD', new Map<String, Object>{'orderId' => '12345'});

In the above example, the setPaymentProcessor method dynamically selects the appropriate payment processor based on the Custom Metadata settings. This allows administrators to switch payment gateways or add new ones without deploying new Apex code.

Benefits of Combining DI with Custom Metadata:

  1. Flexibility: Payment gateways can be changed or added through Salesforce setup without modifying Apex code.
  2. Manageability: All gateway configurations are managed in one place, making it easy to view and edit settings.
  3. Scalability: As new gateways are needed, you only need to add new Custom Metadata records and implement the corresponding classes.

Combining Dependency Injection with Custom Metadata Types in this way facilitates a highly configurable and scalable solution for managing payment processors in Salesforce.

Testing PaymentService class

You can test the PaymentService class by mocking the IPaymentProcessor interface using the Stub API. The Stub API allows you to substitute method implementations with mock behavior, which is ideal for unit testing because it helps isolate the class under test from its dependencies. Here's how you can create a mock class for the IPaymentProcessor interface and use it to test the PaymentService:

Step 1: Create a Mock Class

Create a mock class that implements the StubProvider interface provided by Salesforce. This class will define the behavior of the mocked methods.

@isTest
private class MockPaymentProcessor implements System.StubProvider {
    private Boolean processPaymentReturnValue;

    public MockPaymentProcessor(Boolean returnValue) {
        this.processPaymentReturnValue = returnValue;
    }

    public Object handleMethodCall(Object stubbedObject, String stubbedMethodName, Type returnType, List<Type> parameterTypes, List<String> parameterNames, List<Object> args) {
        if (stubbedMethodName == 'processPayment' && returnType == Boolean.class) {
            return processPaymentReturnValue;
        }
        return null;
    }
}

Step 2: Write a Test Class

Now, write a test class for PaymentService. Use the Test.createStub method to create an instance of the IPaymentProcessor interface with the mock behavior.

@isTest
private class PaymentServiceTest {

    @isTest
    static void testHandlePayment() {
        // Create an instance of the mock payment processor with the desired return value (true for successful payment)
        IPaymentProcessor mockProcessor = (IPaymentProcessor)Test.createStub(IPaymentProcessor.class, new MockPaymentProcessor(true));

        // Inject the mock payment processor into the payment service
        PaymentService paymentService = new PaymentService(mockProcessor);

        // Call the method to test with some test data
        Boolean result = paymentService.handlePayment(100.00, 'USD', new Map<String, Object>{'orderId' => '12345'});

        // Assert that the payment was successful
        System.assertEquals(true, result, 'The payment should have been processed successfully.');
    }
}

In this test, we're asserting that handlePayment returns true, which is the behavior we've defined in our mock class for a successful payment processing scenario. You can also test for different scenarios by changing the return value in the MockPaymentProcessor constructor or adding more logic to the handleMethodCall method.

By mocking the IPaymentProcessor interface, we can focus on testing the behavior of the PaymentService class without needing to rely on actual implementations of the payment processor, which might have external dependencies and side effects. This allows for faster and more reliable unit tests.

Best Practices and Common Challenges implementing Dependency Injection

Best Practices

  • Use Interfaces: We defined IPaymentProcessor as an interface, which allows us to implement different payment processors without changing the dependent PaymentService class code.
  • Constructor Injection: Originally, we used constructor injection to pass the specific payment processor to PaymentService. This is a clear and direct way to handle dependencies.
  • Single Responsibility Principle: Each payment processor class, such as PayPalPaymentProcessor and StripePaymentProcessor, has a single responsibility: to process payments for its respective gateway.
  • Testability: With DI, we can easily test PaymentService by mocking the IPaymentProcessor interface, ensuring that unit tests do not rely on external systems.
  • Custom Metadata Types: By using Custom Metadata Types, we allowed for dynamic configuration of payment processors, which is a best practice for managing external configurations.
  • Documentation: Documenting how PaymentService and payment processors work together, including how to configure Custom Metadata, is crucial for maintainability.
  • Managing Dependencies: We only inject the necessary dependencies into PaymentService, avoiding unnecessary complexity.

Common Challenges

  • Limited Reflection: Apex's reflection capabilities are limited, but we used Type.forName to instantiate classes by name, which is a workaround for dynamic instantiation based on Custom Metadata.
  • Complex Configuration: As the number of payment gateways grows, managing Custom Metadata records can become complex. It's important to have a clear strategy for managing these configurations.
  • Learning Curve: Developers new to DI might need time to understand the pattern. In the PaymentService example, clear documentation and code comments can help mitigate this.
  • Over-Engineering: Adding DI where it's not necessary can overcomplicate the solution. In our case, we only introduced DI for actual needs, like varying payment gateways.
  • Testing: With DI, we must write tests for each payment processor and their interaction with PaymentService. This means more tests but also better coverage.
  • Debugging: Debugging can be more complex because the implementation details are abstracted. To mitigate this, ensure logging and error handling are in place, as they can provide insights when something goes wrong.
  • Performance Considerations: Creating new instances of payment processors could have performance impacts. In the PaymentService example, we should consider reusing processor instances if appropriate.
Share This:    Facebook Twitter

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