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
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 );
}
}
}
For sure a very nice way of enhancing a functionality that you would expect salesforce should have OOTB