In the preceding chapters, we discussed how we can create objects or structure our code to enhance its modularity and reusability. This chapter focuses on design patterns to enable communication between objects and keep them loosely coupled at the same time.
Introducing these design patterns in code increases their flexibility and reusability in order to carry out the communication between objects. It mostly focuses on how an object interacts and how responsibilities are shared amongst them.
We should consider using behavioral patterns in the following scenarios:
In this chapter, we will discuss the following design patterns:
It is a common situation where we need to debug or log some important information in Apex. It can help improve error handling and reporting or analyzing runtime errors while customers are using the application.
Apex provides the following debug levels from the lowest to the highest:
The preceding log levels are cumulative, which means that if the log level is DEBUG, then it will also include ERROR, WARN, and INFO.
There is much more to learn about this topic; however, it would not be possible to cover it entirely in this chapter. Refer to the Salesforce help link to read more about it at https://help.salesforce.com/apex/HTViewHelpDoc?id=code_setting_debug_log_levels.htm&language=en.
Salesforce provides various debug methods in the System
class, as shown in the following code:
//Option 1 System.debug("Your debug message");
Or you can also use the following code:
//Option 2 System.debug(Logginglevel," Your debug Message");
It is recommended that you use the overridden debug
method (option 2) where we can provideLogginglevel
parameter as well. Developers often face problems wherein they do not find the complete log information. This is due to the 2 MB log size limit set by the Force.com platform.
Primarily, developers can configure the debug logging level for Apex, workflow, validation rule, and so on at the user or class level. But, it may not be adequate in some scenarios. For example, when there is a huge code base with lots of debug
statements. In such scenarios, defining Logginglevel
in debug
statements can help you get more granular control of debug logs.
Using debug statements is one of the most basic ways to analyze errors in Apex. Additionally, developers employ other mechanisms such as sending error notifications, saving error information in a custom object, and so on.
Each logging mechanism has its own separate approach to handle error information. It is recommended that we should structure our code to achieve a low cohesion and reusability.
The chain of responsibility pattern focuses on avoiding tight coupling between sender and receiver objects to handle requests. It allows chaining of multiple receiving objects to give a chance to more than one object to handle the same request. It follows the launch and leave design with a single execution path, allowing more than one object to process the request.
The following diagram explains the chain of responsibility pattern at a high level:
Let's try to solve this problem using the chain of responsibility design pattern.
Let's create a custom object named Debug
to store debug logs. It contains two fields: title and message. The title field is created as an external Id to make it an indexed field.
The following screenshot shows the object structure:
We plan to handle debug
statements in the following three different ways:
As per the single responsibility principle, we will need to create three separate classes for each of the preceding functionalities. Each of these classes will share some common logic, which can be implemented in the base class. This is an opportune moment to create an abstract class.
The abstract class includes the following parameters:
setNextLogger
method to define the next handler in the chainlogMessage
method to decide whether the current and next log handler in the chain needs to be invoked on the basis of the requested log levelwrite
abstract method, which needs to be implemented by all handlers (child classes):public abstract class RequestHandler{ /* constant for debug log */ public static final integer LOG_LEVEL_SYSTEMDEBUG = 1; /* constant error and warn */ public static final integer LOG_LEVEL_SAVE = 2; /* constant for Send Email */ public static final integer LOG_LEVEL_EMAIL = 3; /* Requested log level */ protected integer handlerLogLevel ; /* Link to next Request Handler object */ protected RequestHandler nextLogger; /* Set next Logger Request Handler class*/ public void setNextLogger(RequestHandler logger){ this.nextLogger = logger; } // "Chaining" of log request handlers public void logMessage(integer level, String message){ if(handlerLogLevel <= level){ write(message); } /* "Chain" - Pass request to next Request Handler object*/ if(nextLogger != null){ nextLogger.logMessage(level,message); } } //This method needs to be implemented by all child log handlers abstract protected void write(String message); }
Here, we have the first concrete class, which extends the RequestHandler
abstract class to display the message in the standard debug logs:
public class RequestHandler_SystemDebug extends RequestHandler{ public RequestHandler_SystemDebug(integer log_level){ this.handlerLogLevel = log_level ; } /** * This Debug Request Handler class only * log Debug messages in Log Console * */ public override void write(String message){ System.debug(message) ; } }
The second request handler class is used to save messages in a Debug__c
custom object so that it can be used later for reporting purposes.
Saving debug records in the database has many pitfalls, which are as follows:
public class RequestHandler_Save extends RequestHandler{ public RequestHandler_Save(integer log_level){ this.handlerLogLevel = log_level ; } /** * This Debug Request Handler class Saves * Debug log record in custom object for * future error analysis. * */ public override void write(String message){ //Peform Database operation only if we have DML statement limit available if(Limits.getLimitDMLStatements() - Limits.getDMLStatements() > 1 ) { //Perform Database operation only if we have atleast 1 row limit available for DML if(Limits.getLimitDMLRows() - Limits.getDMLRows() > 1){ /** * Save first 250 characters in "title" field * as its indexed , so searching will be fast * */ String title = message.length() > 250 ? message.left(249) : message ; Debug__c debugObj = new Debug__c(Title__c = title, Message__c = message); insert debugObj ; }else{ System.debug(LoggingLevel.ERROR,'DML row limit exhausted'); System.debug(LoggingLevel.ERROR, message); } } else{ System.debug(LoggingLevel.ERROR,'DML statement limit exhausted'); System.debug(LoggingLevel.ERROR, message); } } }
The following request handler provides a functionality to send e-mail notifications:
public class RequestHandler_SendEmail extends RequestHandler{ public RequestHandler_SendEmail(integer log_level){ this.handlerLogLevel = log_level ; } /** * Send Email to Salesforce User. * We would not be sending email to external users to avoid governor limits * */ public override void write(String message){ try { List<Messaging.SingleEmailMessage> lstEmailstoSend = new List<Messaging.SingleEmailMessage>(); //read usernames from Custom label which has //comma seperated users name for(User u : [SELECT ID FROM User WHERE UserName IN : System.Label.Developer_User_Name.split(',')]){ Messaging.SingleEmailMessage emailMessage = new Messaging.SingleEmailMessage(); emailMessage.setSubject('Debug Message from Salesforce'); emailMessage.setTargetObjectId(u.Id); emailMessage.setPlainTextBody(message); emailMessage.setSaveAsActivity(false); lstEmailstoSend.add(emailMessage); } if(!lstEmailstoSend.isEmpty()) Messaging.sendEmail(lstEmailstoSend); }catch(Exception e){ System.debug(LoggingLevel.ERROR,'Some error occurred while sending email for Logging purpose'); System.debug(LoggingLevel.ERROR, e.getMessage()); } } }
Now, we need a utility class that will create a chain of all the preceding logger requests that we created earlier, as shown in the following class:
public class Loggers{ /** * Below method returns Chained Loggers * */ public static RequestHandler getChainOfLoggers(){ RequestHandler debugLogger = new RequestHandler_SystemDebug(RequestHandler.LOG_LEVEL_SYSTEMDEBUG); RequestHandler databaseLogger = new RequestHandler_Save(RequestHandler.LOG_LEVEL_SAVE); RequestHandler emailLogger = new RequestHandler_SendEmail(RequestHandler.LOG_LEVEL_EMAIL); //Create Chain of Loggers //DebugLogger -> Database Save -> Send Email debugLogger.setNextLogger(databaseLogger); databaseLogger.setNextLogger(emailLogger); //return logger which starts chain return debugLogger; } }
The following figure depicts the flow of the chain of responsibility pattern:
The following are the code snippets used to verify a functionality.
Sample code 1:
RequestHandler logger = Loggers.getChainOfLoggers();
logger.logMessage(RequestHandler.LOG_LEVEL_SYSTEMDEBUG, 'I am logger using Chain of responsibility principal');
Behavior:
The log level in the preceding code snippet is set to LOG_LEVEL_SYSTEMDEBUG
(value 1). Therefore, the logMessage
method of the RequestHandler
abstract class will only execute request handlers that have the log level less than or equal to LOG_LEVEL_SYSTEMDEBUG
. In this case, the RequestHandler_SystemDebug
handler will be executed.
Output:
Sample code 2:
RequestHandler logger = Loggers.getChainOfLoggers();
logger.logMessage(RequestHandler. LOG_LEVEL_SAVE, 'I am logger using Chain of responsibility principal');
Behavior:
The log level in the preceding code snippet is set to LOG_LEVEL_SAVE
(value 2). In this case, the RequestHandler_SystemDebug
and RequestHandler_Save
handlers will be executed.
Output:
Debug__c
custom objectSample code 3:
RequestHandler logger = Loggers.getChainOfLoggers();
logger.logMessage(RequestHandler. LOG_LEVEL_EMAIL, 'I am logger using Chain of responsibility principal');
Behavior:
The log level in the preceding code snippet is set to LOG_LEVEL_EMAIL
(value 3). In this case, the RequestHandler_SystemDebug
, RequestHandler_Save
, andRequestHandler_SendEmail
handlers will be executed.
Output:
Debug__c
custom objectThe following diagram is a class diagram for our final code:
As discussed in the previous design pattern, we learnt a new way to decouple a request and a series of receiver objects, and at the same time recursively call all request handlers, which know how to process a request. This is very powerful and one of the most common behavioral design patterns used.