Exception Handling using Platform Events

In our last session we talk about how to handle exception in flow using platform events. In this session we will talk about how to do Exception Handling using Platform Events in Apex.

What is Platform Event?

Platform Event is based on Event-Driven Architecture which enable apps to communicate inside and outside of Salesforce. Platform events are based on the publish/subscribe model and work directly with a message bus which handles the queue of incoming events and processes listening for them. Check this session to learn about how to Overcome Salesforce Governor Limits Using Platform Events.

Exception Handling & Logging

Lets understand what is Exception and different between Exception logging and Handling.

Exception : Unexpected event occurred during your transaction execution
Exception Logging : Store exception details for later analysis and trouble shooting
Exception Handling : Deal with unexpected Exception

Sample Apex Exception Types

Here are some exception in Apex.

Exception Type Description
DmlException Failure of DML Operation Example – Missing required field value during insertion
LimitException Transaction reached Salesforce Governor Limit: Example – SOQL 101 Exception
ListException Any problem with List operation Example – List Index out of bound
NullPointerException Reference null value Example – Accessing uninitialized variable
QueryException Issue with query execution Example – Try to assign more records to single instance

Apex Scenario

On new Opportunity creation, if parent Account has team member with “Sales Manager” role, it should be automatically populated to Opportunity as Opportunity Team Member.

Solution : After Update Opportunity Trigger

So far your code look good and working fine. What about if any exception will come in code? For Demo purpose we just removed the required field from code. Your code will fail and will give you exception like below.

Now if you want to handle & log the Exception then you can create one Exception Object to log the exception. like below

And add try-catch in your code to handle the exception then Insert details to Exception Object Inside catch. You can add your code in util method for Exception Logging like below code :

Handle & Log Exception – Transaction Behavior

Now what will happened if any exception will come after adding the try catch and logging the error in custom object?

  • Opportunity Creation is success
  • OpportunityTeam is not populated
  • Exception Record Got Created

How to Fail complete Transaction but still log error?

Create your custom Exception class to throw it from catch block.

Now your try catch code will look like below

Throw Exception – Transaction Behavior

Lets Execute our code and see how its behavior.

  • Complete Transaction failed
  • No Exception Logging happened

Platform Events provide a means to send notifications from your code without fear of rollback, making them an ideal means to communicate diagnostics about your code. Apply Platform Events with worked examples to enhance your logging skills while making it easier to diagnose issues without debug logs!

Event driven Transaction

Create one platform event like below screen.

Once your platform event is ready then create one Trigger to log the Exception in Exception Object like below code.

Why we need PE & Exception Object?

PE does not provide:

  • Reporting
  • Querying
  • Formula fields for additional processing

And update your catch block like below code

catch(Exception e){   
			//get exception details
            String exdetails = e.getCause()+' ; '+e.getLineNumber() + ' ; '+e.getMessage()+' ; '+e.getStackTraceString()
                 +' ; '+e.getTypeName() ;
            //ExceptionUtil.logException('Opportunity','Opp Split Addition',oppList[0].id,exdetails);
            ExceptionUtil.publishException('OpportunitySplit','Opp Split Addition',oppList[0].id,exdetails);
			//rollback complete transaction
            Database.rollback( savep );
            
        }

Check below recording for detail explanation for Apex Exception handing with Platform Event.

Recording

YouTube video
Date     : Sat, OCT 03, 2020 10:00 AM EST (7:30 PM IST)
Speaker  : Meera Nair

Here is final code

public class ExceptionUtil {
	public class MyException extends Exception {}
	 @InvocableMethod(label='Invoke ExceptionUtil')
    public static void throwcustomException(List<String> excmessage){
       
            throw new MyException('An internal exception happened during current operation.'+ 
                                  'Please contact system administrator with this exception details:'+excmessage[0]);
        
    }
    //Log Single Exception
    public static void logException(String objname, String processname, String recordID, String exceptiondetails){
        Exception__c exc = new Exception__c();
        exc.Object__c = objname;
        exc.Operation__c = processname;
        exc.Exception_Details__c = exceptiondetails;
        exc.Record_ID__c = recordID;
        insert exc;
    }
    
    //Log Exceptions
    public static void logException(String objname, String processname, List<Database.SaveResult> listSaveResult){
        System.debug(listSaveResult);
        List<Exception__c> exclist = new List<Exception__c>();
        Exception__c exc;
        for(Database.SaveResult res : listSaveResult){
            if(!res.isSuccess()){
                exc = new Exception__c();
                exc.Object__c = objname;
        		exc.Operation__c = processname;
                String excdetail;
                 for(Database.Error err : res.getErrors()) {
                      excdetail+= err.getStatusCode()+':'+err.getMessage(); 
                     System.debug('Error returned: ' + err.getStatusCode() +' - ' + err.getMessage());
                    }
                String detail = new dmlException().getStackTraceString();
                exc.Exception_Details__c =excdetail+detail ;
                exc.Record_ID__c = res.getId();
                exclist.add(exc);
            }
        }
        if(exclist.size()>0){
            insert exclist;
        }
    }
    //publish event
    public static void publishException(String objname, String processname, String recordID, String exceptiondetails){
        Exception_Log__e pe = new Exception_Log__e();
        pe.Object__c = objname;
        pe.Operation__c = processname;
        pe.Exception_Details__c = exceptiondetails;
        pe.Record_ID__c = recordID;
        eventBus.publish(pe);
    }
    //publish Events
    public static void publishException(String objname, String processname, List<Database.SaveResult> listSaveResult){
       List<Exception_Log__e> pelist = new List<Exception_Log__e>();
        Exception_Log__e pe;
        for(Database.SaveResult res : listSaveResult){
            if(!res.isSuccess()){
                pe = new Exception_Log__e();
                pe.Object__c = objname;
        		pe.Operation__c = processname;
                String excdetail;
                 for(Database.Error err : res.getErrors()) {
                      excdetail+= err.getStatusCode()+':'+err.getMessage(); 
                     System.debug('Error returned: ' + err.getStatusCode() +' - ' + err.getMessage());
                    }
                String detail = new dmlException().getStackTraceString();
                pe.Exception_Details__c = excdetail+detail;
                System.debug(excdetail);
                //pe.Record_ID__c = res.getId();
                pelist.add(pe);
            }
        }
        if(pelist.size()>0){
            eventBus.publish(pelist);
        }
    }
}

Handler Class

public class OpportunityTriggerUtil {
    
    public static void populateOpportunityTeam(List<Opportunity> oppList){
        Set<id> accids = new Set<id>();
   	    Map<id,List<id>> accidtoOppIdsMap = new Map<id,List<id>>();        
		set<ID> teammemberids = new set<ID>();
        List<OpportunityTeamMember> otmToInsert = new List<OpportunityTeamMember>();
        //Get AccountIDS
        Savepoint savep = Database.setSavepoint();
		try{
			
			for(Opportunity opp : oppList){
				if(opp.AccountId != null){
				   //accids.add(opp.accid);
				   if(accidtoOppIdsMap.get(opp.AccountId)!=null){
						accidtoOppIdsMap.get(opp.AccountId).add(opp.id);
					}
					else{
						accidtoOppIdsMap.put(opp.AccountId,new List<id>{opp.id});
					} 
				}
				
			}
			
			System.debug(accidtoOppIdsMap);
			Map<id,id> accidTouserIDmap = new Map<id,id>();
			//Query and retrieve Account teammember
			if(accidtoOppIdsMap.size()>0){
				for(AccountTeamMember atm : [SELECT AccountId,TeamMemberRole,UserId FROM AccountTeamMember
											  WHERE AccountId IN :accidtoOppIdsMap.keySet() AND TeamMemberRole='Sales Manager']){
					accidTouserIDmap.put(atm.AccountId,atm.UserId);                              
				}
				
				//Create Opp Team Member
				
				if(accidTouserIDmap.size()>0){
					Integer reccnt = 0;
					for(ID accid : accidTouserIDmap.keySet()) {
						for(ID oppID :accidtoOppIdsMap.get(accid) ){
                            OpportunityTeamMember otm;
							if(reccnt==1){
								otm = new OpportunityTeamMember( UserId =accidTouserIDmap.get(accid),TeamMemberRole = 'Sales Manager');//,OpportunityId=oppID
							}
							else{
								otm = new OpportunityTeamMember( UserId =accidTouserIDmap.get(accid),OpportunityId=oppID, TeamMemberRole = 'Sales Manager');//
							}
							
							otmToInsert.add(otm);
							reccnt++;
						}
					} 
				 }
				if(otmToInsert.size()>0 &&  Limits.getDMLStatements() < Limits.getLimitDMLStatements()){					
						  
						if(otmToInsert.size()>1){
							//otmToInsert[0].UserId = null;
						    Database.SaveResult[] resultlist = Database.Insert(otmToInsert,false);
							ExceptionUtil.logException('Opportunity','Opp Team Addition',resultlist);
                            ExceptionUtil.publishException('Opportunity','Opp Team Addition',resultlist);
						}
						else{
							insert otmToInsert;
						}
								  
				}
                else{
                    ExceptionUtil.logException('Opportunity','Opp Team Addition',null,'Reached DML governor Limit');	
                    //ExceptionUtil.publishException('Opportunity','Opp Team Addition',oppList[0].id,exdetails);
                }
			}
		}catch(Exception e){
					String exdetails =  e.getCause()+' ; '+e.getLineNumber() + ' ; '+e.getMessage()+' ; '+e.getStackTraceString()
                                                  +' ; '+e.getTypeName() ;
					//ExceptionUtil.logException('Opportunity','Opp Team Addition',oppList[0].id,exdetails);	
                    ExceptionUtil.publishException('Opportunity','Opp Team Addition',oppList[0].id,exdetails);
                    //throw new ExceptionUtil.MyException('Exception happend during Opp Team Addition'+e.getMessage());
                }
        //Create split record
        List<OpportunitySplit> oppsplit = new List<OpportunitySplit>();
        try{
			
            
			//query and get teammember details
			
				List<OpportunityTeamMember> otmsuccessList = [SELECT id,UserID,OpportunityID from OpportunityTeamMember WHERE OpportunityID IN :oppList and TeamMemberRole ='Sales Manager'];
				for(OpportunityTeamMember otmnew : otmsuccessList){
					OpportunitySplit sp = new OpportunitySplit();
					//sp.OpportunityId = otmnew.OpportunityID;
					sp.SplitPercentage = 30;          
					sp.SplitOwnerID = otmnew.UserId;
					oppsplit.add(sp);
				}
			
			
            if(oppsplit.size()>0){
                insert oppsplit;
            }
        }catch(Exception e){   
			//get exception details
            String exdetails = e.getCause()+' ; '+e.getLineNumber() + ' ; '+e.getMessage()+' ; '+e.getStackTraceString()
                 +' ; '+e.getTypeName() ;
            //ExceptionUtil.logException('Opportunity','Opp Split Addition',oppList[0].id,exdetails);
            ExceptionUtil.publishException('OpportunitySplit','Opp Split Addition',oppList[0].id,exdetails);
			//rollback complete transaction
            Database.rollback( savep );
            
        }
        
        
    }

}

Further Learning

Amit Chaudhary
Amit Chaudhary

Amit Chaudhary is Salesforce Application & System Architect and working on Salesforce Platform since 2010. He is Salesforce MVP since 2017 and have 17 Salesforce Certificates.

He is a active blogger and founder of Apex Hours.

Articles: 461

One comment

Leave a Reply

Your email address will not be published. Required fields are marked *