Sunday, 24 February 2019

Trigger Design Pattern in Apex


Trigger Design Patterns Without Framework :
=================================

Frameworks improve reliability and maintainability,and make probelms easier to debug.

ex :
1.Follow up on lead account(Process Builder)
a. Insert lead with 'Open not Contacted' with good email
b. Create a follow up task.

2.taskTriggerSetStatus
a.Task created for lead
b.if the lead has a task, set its status to 'Working-Contacted'
or 'Working - harder '.

3.leadTriggerSetFollowup
a.if a lead has a working status
b.Create a follow-up task (reminder).

4.taskTriggerTrackCount
a.Task is created,updated or deleted
b.Update # of tasks on the lead

5.First Owner Worked (Process Builder)
a.Lead status starts with Working and Owner is a user.
b.Set first owner worked field.

Note :
=========
1.controling the trigger order
2.Combining DML operations
3.controlling reentrancy


Dynamic Trigger Handler :
===========================

public interface ITriggerExtension
{

  void HandlerTrigger(TriggerOperation operationType,
              List<SObject> newList,List<SObject> oldList,
              Map<ID,SObject> newMap,Map<ID,SObject> oldMap);
}


public with sharing class dynamicTaskTriggerHandler implements ITriggerExtension{

public void HandleTrigger(TriggerOperation operationType,List<SObject> newList,List<SObject> oldList,
                         Map<ID,SObject> newMap,Map<ID,SObject> oldMap)
 {
   system.debug('Trigger execution was called');
 }

}


trigger taskTrigger on Task (before insert,after insert,before update,after update,before delete,after delete){

simpleLeadUpdater leadUpdater = new simpleLeadUpdater();

// What happens when you change the order of these two triggers handlers?

  if(trigger.operationType == TriggerOperation.AFTER_INSERT)
    {
      taskSetStatus.handleTrigger(trigger.new,leadUpdater);
    }

  if(trigger.operationType == TriggerOperation.AFTER_INSERT ||
     trigger.operationType == TriggerOperation.AFTER_UPDATE ||
     trigger.operationType == TriggerOperation.AFTER_DELETE )
     {
     
       taskTrackCount.handleTrigger (trigger.operationType,trigger.new,trigger.old,leadUpdater);
    }

 List<ITriggerExtension> dynamicTriggers = TriggerExtensionSupport.getTriggerClasses('Task');
  for(ITriggerExtension trig : dynamic Triggers)
   {
      trig.HandleTrigger(trigger.operationType,trigger.new,trigger.old,trigger.newMap,trigger.oldMap);
 
   }

  leadUpdater.updateLeads();
}

public with sharing class TriggerExtensionSupport{

public static List<ITriggerExtension> getTriggerClasses(String objectType)
 {
    List<Dynamic_Trigger_mdt> triggerSettings = [ Select Class_Name__c from Dynamic_Trigger__mdt where
                                                  Object_Type__c = : objectType OrderBy Priority__c ASC];
    List<ITriggerExtension> results = new List<ITriggerExtension>();

    for(Dynamic_Trigger__mdt setting : triggerSettings)
    {
      System.Type thetype = Type.forName(setting.Class_Name__c);
       if(thetype == null)
        thetype = Type.forName('',setting.Class_Name__c); // Try resolving local class
       if(thetype!=null){
           Object theobject = thetype.newInstance();
           if(theobject instanceof ITriggerExtension) results.add ((ITriggerExtension) theobject);
        }
    }

   return results ;
 }

}

public with sharing class taskTrackCount {

public static void handleTrigger (TriggerOperation operationType, List<Task> newList,List<Task> oldList,simpleLeadUpdater leadUpdater)
  {
     Set<ID> leadIds = new Set<ID>();
   
     if(operationType == TriggerOperation.AFTER_UPDATE || operationType == TriggerOperation.AFTER_INSERT)
       {
          for(Task t : newList)
           {
             if(t.WhoId!=null && t.WhoId.getSObjectType()==Schema.Lead.SObjectType)
              leadIds.add(t.WhoId);
           }
       }
 
     if(operationType == TriggerOperation.AFTER_UPDATE || operationType == TriggerOperation.AFTER_DELETE)
         {
            for(Task t : oldList)
             if(t.whoId !=null && t.whoID.getSObjectType() == Schema.Lead.SObjectType)
               leadIds.add(t.whoId);
         }

     List<Lead> leads = [Select ID,Task_Count__c from Lead where ID in : leadIds];
     List<AggregateResult> tasks = [Select Count(ID) items,WhoId from Task where WhoId in : leadIds group by WhoID];
     Map<ID,Interger> taskCounts = new Map<ID,Interger>();
 
     for(AggregateResult ar : tasks)
     {
       taskCounts.put((ID)ar.get('WhoId'),(Interger) ar.get('items));
     }
   
     for(Lead ld : leads)
      {
        if(ld.Task_Count__c != taskCounts.get(ld.Id))
          {
            Lead toUpdate = leadUpdater.getLead(ld.id);
            toUpdate.Task_Count__c = taskCounts.get(ld.id);
          }
      }
   }
}

public with sharing class taskSetStatus {

private static Set<ID> statusUpdated = new Set<ID>();

public static void handleTrigger (List<Task> newTasks,simpleLeadUpdater leadUpdater){

    Set<ID> leadIds = new Set<ID>();

     for(Task t : newTasks)
      {
        if(t.whoId!=null && t.whoId.getSObjectType() == Schema.Lead.SObjectType && !t.Declarative_Created__c)
            leadIds.add(t.whoId);
      }

    List<Lead> leads = [Slect ID,Status from Lead where ID in : leadids];

   for(Lead ld : leads)
    {
       if(statusUpdated.contains(ld.id)) continue; // Skip those already updated

       switch on ld.status
        {
           when 'Open - Not Contacted'
            {
              Lead toUpdate = leadUpdater.getLead(ld.id);
               toUpdate.status = 'Working - Contacted ';
               statusUpdated.add(ld.id);
            }
           when 'Working - Contacted'
            {
              Lead toUpdate = leadUpdater.getLead(ld.id);
               toUpdate.status = 'Working Harder ';
                statusUpdated.add(ld.id);
            }
        }
    }
 }

}

public with sharing class simpleLeadUpdater {
  Map<ID,Lead> leadsToUpdate = new Map<ID, Lead>();

 public Lead getLead(ID leadID)
  {
    Lead targetLead = leadsToUpdate.get(leadID);
     if(targetLead == null)
      {
        targetLead = new Lead (ID = leadID);
         leadsToUpdate.put(leadID,targetLead);
      }
    return tragetLead;
  }

public void updateLeads()
  {
    if(leadsToUpdate.size()>0)
      update leadsToUpdate.values();
  }

}

Test Class :
===============
@istest
public with sharing class TestLeadActions{

@istest
public static void TestInsertSingleLead(){
    Test.startTest();
    InsertLeads(1,'Open-Not Contacted');
    Test.stopTest();
  }

@isTest
public static void TestInsertBulkLead()
 {
   Long startTime = DateTime.now().getTime();
   Long startCPU = Limits.getCpuTime();
   Test.startTest();
   InsertLeads(200,'Open - Not Contacted');
   Test.stopTest();
   system.debug ('Elapsed : ' + (Datetime.Now().getTime() - startTime));
   system.debug ('Elapsed CPU : ' + (Limits.getCpuTime() - startCPU));

 }

public static void InsertLeads(Interger count,String leadstatus)
{
  List<Lead> leadsToInsert = new List<Lead>();
  for(Interger x=0;x<count;x++)
   {
      String xs = string.valueOf(x);
      leadsToInsert.add(new Lead(FirstName = 'f' +xs,LastName = 'l'+xs,
                         status=leadstatus,
                         Company = 'c' + xs,
                         email = 'e' + 'xs' + '@pluralsight.com',
                         Bypass_Declarative__c = 0 ));

   }
   insert leadsToInsert;
}

 Public static void UpdateLeadStatus()
  {
    List<Lead> leads = [ select Id, status from Lead ];
     for(Lead ld : leads)
       ld.status = 'Working - Contacted ';
     Test.startTest();
      update leads ;
    Test.stopTest();

  }
}

Sunday, 3 February 2019

Asynchronous Apex


Asynchronous Apex :
===================
Asynchronous Apex fires on Salesforce's queue and outside the context of triggers, which allows for massive workloads, expensive computations,and parallel processing.

Note : Asynchronous processes run on their own time in their own run context.

Across run contexts : NO guarantees on order of execution

On the Salesforce platform, we will sometimes need Asynchronous logic to bypass governor limits,manipulate certain objects when certain conditions occur,contact web services,or perform
high-volume workloads.

1.future methods
2.queueable jobs
3.scheduled apex
4.batch apex

Note : Queueable jobs offer monitoring, chaining and non-primitive data types.

Async Benefits :
==================
Asynchronous Processing or Async processing allows running of parallel processes.

Using Future methods for Higher Limits or Mixed DML :
========================================

Future method runs in the background.you can call a future method for executing long-running operations,such as callouts to external web services or any operation you'd like to run in its own thread, on its own time.

Future methods can be used for :
a. Taking advantage of higher governor limits.
b. Long running web service callouts
c. Mixed DML operations.

Note : you might often use future methods for mixed DML operations in unit tests.


you can add on the "@future" annotation to any function that uses primitive data types.

ex :

@future
public static void updateUserManager(Set<Id> userIds,Id managerId)
{
  // update all users in specified IDs to have new specified Manager ID
    List<User> userToUpdate = new List<User>();
   for(Id userId : userIds)
     {
       usersToUpdate.add(new User(Id=userId,Manager = managerId));
     }

  Database.SaveResult[] updateResults= Database.update(usersToUpdate);
}


ex :

public without sharing class AccountUtils
{
  public static void sortOwners(List<Account> oldAccounts,List<Account> newAccounts)
   {
      Map<Id,List<Account>> userIdToAccounts = getUserToAccounts(newAccounts);
       // For each user,calculate Account Revenue and decide if
       // performance tiers should be updated
      Map<Id,Decimal> userIdToRevenue = getUserRevenue(userIdToAccounts);
      // calls the future method to make AnnualRevenue updated on the user
      if(userIdToAccounts.keySet.size()>0)
       UserUtils.updateAnnualRevenue(userIdRevenue);
   }

  public static Map<Id,List<Account>> getUserToAccounts(List<Account> accountsByUser)
   {
      Map<Id,List<Account>> userIdToAccounts=new Map<Id,List<Account>>();
     
       for(Account account : accountsByUsers)
         {
             if(account.AnnualRevenue !=null && account.AnnualRevenue !=0)
               {
                 if(userIdToAccounts.containskey(account.ownerId))
                     userIdToAccounts.get(account.Id).add(account);
                     else
                     userIdToAccounts.put(account.OwnerId,new List<Account>{account});
             
                }

         }
     return userIdToAccounts;
   }
 
 public static Map<Id,Decimal> getUserRevenue(Map<Id,List<Account>> userIdToAccounts)
  {
    Map<Id,Decimal> userIdToRevenue = new Map<Id, Decimal>();
     for(Id userId : userIdToAccounts.keySet())
      {
        // Iterate through Accounts to retrieve total
        Decimal totalRevenue =0;
         for(Account account : userIdToAccounts.get(userId))
          {
            totalRevenue += account.AnnualRevenue;
          }
         userIdToRevenue.put(userId,totalRevenue);

      }
     return userIdToRevenue;
  }

}


public without sharing class UserUtils
{

  public static Boolean testingException = false;

  public static final Map<String,Decimal> performanceTiers
    = new Map<String,Decimal>{'Low' => 0,'Medium' => 50000,'High' => 10000 };

  @future
  Public static void updateAnnualRevenue(Map<Id,Decimal> userIdsToAnnualRevenue)
  {
    // Retrieve users based on key values of the map : userIDsToAnnualRevenue
   List<User> users = getUsers(userIDsToAnnualRevenue.keySet());
   // Iterate through Users to determine if the performance tier
   // should be updated
   for(User user : users)
   {
     // Update Annual Revenue
      user.Annual_Revenue__c = userIDsToAnnualRevenue.get(user.Id);
       if(user.Annual_Revenue__c < performanceTiers.get('Medium'))
           user.Performance_Tier__c ='Low'; 
       if(user.Annual_Revenue__c >= performanceTiers.get('Medium')
          && user.Annual_Revenue__c < performanceTiers.get('High'))
           user.Performance_Tier__c='Medium';
       if(user.Annual_Revenue__c >= performanceTiers.get(High))
          user.performance_Tier__c='High';
    }

    update users;
  }

  public static List<User> getUsers(Set<Id> userIds)
  {
     List<User> users=[select Id,Name,Annual_Revenue__c,performance_Tier__c
                        from User where Id in : userIds];
      return users;

   }

}


Note :

Future methods can be quickly and easily built with benefits.

Drawbacks  of Future methods:
=============================
1.we can use only Primitive data types
2.No chaining
3.No monitoring

Testing future methods using Test.startTest() and Test.stopTest().

@isTest
private class AccountTest
{
  @testsetup
   static void setupData()
   {
     // Create multiple sales person users
      List<User> salesUsersToInsert = new List<User>();
     
      Profile adminProfile=[select Id, Name from Profile where Name ='System Administrator' LIMIT 1];
     // Low ,medium and high tier sales users
      User lowTierUser = new User(
           Performance_Tier__c='Low',
           FirstName ='Low',
           LastName ='User',
           Username='lowtieruser@example.com',
           Email ='lowtieruser@example.com',
           CompanyName ='Globomantics',
           Alias ='alias',
           Title ='Title',
           TimeZoneSidkey ='America/Chicago',
           EmailEncodingKey ='UTF-8',
           LanguageLocalKey='en_US',
           LocaleSidKey ='en_US',
           ProfileId=adminProfile.Id);
           
           User mediumTierUser = new User(
           Performance_Tier__c='Medium',
           FirstName ='Medium',
           LastName ='User',
           Username='mediumtieruser@example.com',
           Email ='mediumtieruser@example.com',
           CompanyName ='Globomantics',
           Alias ='alias',
           Title ='Title',
           TimeZoneSidkey ='America/Chicago',
           EmailEncodingKey ='UTF-8',
           LanguageLocalKey='en_US',
           LocaleSidKey ='en_US',
           ProfileId=adminProfile.Id);

           User highTierUser = new User(
           Performance_Tier__c='High',
           FirstName ='High',
           LastName ='User',
           Username='hightieruser@example.com',
           Email ='hightieruser@example.com',
           CompanyName ='Globomantics',
           Alias ='alias',
           Title ='Title',
           TimeZoneSidkey ='America/Chicago',
           EmailEncodingKey ='UTF-8',
           LanguageLocalKey='en_US',
           LocaleSidKey ='en_US',
           ProfileId=adminProfile.Id);

           // Add user records to list of users to insert
           salesUserToInsert.add(lowTierUser);
           salesUserToInsert.add(mediumTierUser);
           salesUserToInsert.add(highTierUser);
         
           insert salesUserToInsert;
       
           // Create test Accounts

            List<Account> accountsToInsert = new List<Account>();
             Account lowAccount = new Account (
                  Name = 'Low Account',
                  AnnualRevenue = 20000,
                  OwnerId= lowTierUser.Id);

             Account mediumAccount = new Account (
                  Name = 'Medium Account',
                  AnnualRevenue = 75000,
                  OwnerId= mediumTierUser.Id);

             Account highAccount = new Account (
                  Name = 'High Account',
                  AnnualRevenue = 100000,
                  OwnerId= highTierUser.Id);
         
             accountsToInsert.add(lowAccount);
             accountsToInsert.add(mediumAccount);
             accountsToInsert.add(highAccount);
           
             insert accountsToInsert;
 
   }

  @isTest
   static void sortOwnersSuccess()
   {
    // Retrieve users
    List<User> users = [ select Id, Performance_Tier__c from User
                          where LastName ='user'];
    // Retrieve Accounts
    List<Account> accounts = [select Id,Name,OwnerId,
                              AnnualRevenue from Account];
 
    // When
     List<Account> accountsToUpdate = new List<Account>();
     Test.startTest();
      Decimal updatedRevenue = UserUtils.performanceTiers.get('High') + 50000;
       Account accountUpdate = new Account(
            Id = accounts[0].Id,
            AnnualRevenue = updatedRevenue);
           
        accountsToUpdate.add(accountUpdate);
        update accountsToUpdate;
     Test.stopStart();

    // Then
   
    List<User> usersAfterUpdate = [ select Id,Performance_Tier__c
                                  from User where LastName = 'User' ];

   Interger numberofHighTierUsers =0;
 
    for ( User user : usersAfterUpdate)
      {
         if(user.Performance_Tier__c == 'High')
            numberofHighTierUsers + =1;
      }
   
    System.assertEquals (2,numberOfHighTierUsers, ' 2 High Tier Users Found After Update');


   }


}

Note : Do not make the mistake of a trigger that calls a Future Method that calls another trigger that calls a Future method and so on.


Monitoring ,Chaining processes with Queueable jobs :
=====================================

1.Beyond the Future Method

2.Chaining , monitoring , and non-primitive data types.

3. One Queueable job can call another queueable job , or future calls.
4. An Id means being able to retrieve data about other Queueable jobs.
5. Sobjects and declared objects can be passed in as input.

Note :
a. Possibility of chained parallel processes
b. Chanined parallelism means higher workloads.


Queueable Jobs :
=================
Queueable Jobs are derived from its interface, and as such : each Queueable class must have an execute method.

ex :

public class QueueableProcess implements Queueable
{
   // Every Queueable job has an 'execute' method
  public void execute (QueueableContext context)
  {
      doProcessing();
  }

}


To enqueue the job using System.enqueueJob.

System.enqueueJob(new QueueableProcess());

As an argument, we would pass an instance  of the queueable class itself as specifying which job to enqueue.

public without sharing class OpportunityUtils
{
  public static Map<Interger, List<sObject>> processProducts (List<Opportunity> oldOpps,List<Opportunity> newOpps)
   {
     // Collect all Opportunities moved to 'Closed Won'
     Map<Id,Opportunity> accountIdToOpportunity = new Map<Id,Opportunity>();
     Map<Id ,Opportunity> leadIdToOportunity = new Map<Id,Opportunity>();
     Map<Id,Opportunity> opportunityIdToOpportunity=new Map<Id,Opportunity>();
 
    for(Interger i=0; i< newOpps.size(); i++)
     {
       Opportunity opportunity = newOpps[i];
       Opportunity oldOpportunity = oldOpps == null ? null : oldOpps[i];
     
        if(oldOpportunity != null)

           if(opportunity.StageName != oldOpportunity.SatgeName && opportunity.SatgeName == 'Closed won')
            {
              accountIdToOpportunity.put(opportunity.AccountId,opportunity);
              leadIdToOpportunity.put(opportunity.Lead__c,opportunity);
              opportunityIdToOpportunity.put(opportunity.Id,opportunity);

            }
     }
 
   if(opportunityIdToOpportunity.keyset().size()==0)
    return new Map<Interger,List<sObject>>();
    // Retrieve object lists
 
    List<Account> accounts = getAccounts(accountIdToOpportunity.keySet());
 
    List<Case> cases = getCases(opportunityIdToOpportunity.keySet());
 
    List<Lead> leads = getLeads(leadIdToOpportunity.keySet());
   
    // Iterate through each object and make appropriate updates
    // For each Account,update its AnnualRevenue with the Amount
    // of the Opportunity record. For each Case, close it. For each
    // Lead,mark as 'Inactive'.

     for(Account account : accounts)
        account.AnnualRevenue = account.AnnualRevenue +
        accountIdToOpportunity.get(account.Id).Amount;

     for(Case caseRecord : cases)
       caseRecord.Status ='Closed';

     for(Lead lead : leads)
           lead.Status='Inactive';

   // Accounts can be updated synchronously
   
     update accounts;
   Map<Interger,List<sObject>> indexToObjects = new Map<Interger,List<SObject>>();
    indexToObjects.put(0,cases);
    indexToObjects.put(1,leads);
 
    Interger startingIndex =0;
    System.enqueueJob(new Worker(indexToObjects,startingIndex));
   
   return indexToObjects;

   }

  public static List<Case> getCases(Set<Id> opportunityIds)
  {
    return [select Id,OppOrtunity__c,Status
             From Case WHERE Opportunity__c in : opportunityIds];

  }
 
  public static List<Account> getAccounts(Set<Id> accountIds)
  {
    return [select Id,Name AnnualRevenue from Account
            where Account where Id in : accountIds];
  }

 public static List<Lead> getLeads(Set<Id> leads)
 {
   return [select Id, Name,Status from Lead
           where Id in : leadIds];
 }

}

public without sharing class Worker implements Queueable
{
  Map<Interger,List<sObject>> indexToObjectUpdates;
  Interger index;

  public Worker(Map<Interger,List<sObject>> indexToObjectUpdates,Interger index)
   {
     this.indexToObjectUpdates = indexToObjectUpdates;
     this.index = index;
   }

  public void execute (QueueableContext context)
   {
     if(!indexToObjectUpdates.containskey(index))
      return ;

    update indexToObjectUpdates.get(index);
   
    Interger nextIndex = index +1 ;
    if(indexToObjectUpdates.containskey(nextIndex))
      System.enqueueJob(new Worker(indexToObjectUpdates, nextIndex));

   }
}


@isTest
private class OpportunityTest
{
  public static final Decimal amount = 1000.00;

  @testsetup
  static void setupData()
  {
   
   Account account =new Account();
   account.Name ='Test Account';
   account.AnnualRevenue = 0;
   insert account;
 
   Lead  lead = new Lead();
    lead.Company ='A Company';
    lead.FirstName='Test';
    lead.LastName='Lead';
    lead.Status ='Working - Contacted';
  insert lead;
 
  Opportunity opportunity =new Opportunity();
   opportunity.AccountId = account.Id;
   opportunity.Amount = amount;
   opportunity.Lead__c = lead.Id;
   opportunity.CloseDate = Date.today();
   opportunity.Name= 'Test Opportunity';
   opportunity.StageName ='Negotiation/Review';
   insert opportunity;

  Case caseOnOpportunity =new Case();
  caseOnOpportunity.Opportunity__c=opportunity.Id;
  caseOnOpportunity.Status ='New';
  insert caseOnOpportunity;

  }

 @isTest
  static void cloasedOpportunityTest()
   {
    // Retrieve Opportunity
    List<Opportunity> opportunities = [select Id,AccountId,
      Amount,Lead__c,CloseDate,StageName from Opportunity];

    System.assertEquals(1,opportunities.size(),'Test Opportunity retrieved');
 
    List<Account> accounts = [select Id,Name,AnnualRevenue from account
                             where AnnualRevenue=0];

    System.assertEquals(1, accounts.size(),'1 Account Retrieved with 0 AnnualRevenue');
   
    List<Case> cases = [ select Id, Status from Case
                        where Status != 'Closed'];
    System.assertEquals (1, cases.size(),'1 non-closed Case found');

    List<Lead> leads =[select Id, Name,Status from Lead where Status !='Inactive'];
    System.assertEquals(1,leads.size().'1 active lead found');

   Test.startTest();
     List<Opportunity> oldOpportunities = opportunities;
     List<Opportunity> newOpportunities = new List<Opportunity>();
      //new opp

      Opportunity newOpportunity = new Opportunity();
      newOpportunity.AccountId = opportunities[0].AccountId;
      newOpportunity.Amount = opportunities[0].Amount;
      newOpportunity.Lead__c= opportunities[0].Lead__c;
      newOpportunity.CloseDate = opportunities[0].CloseDate;
      newOpportunity.Id = opportunities[0].Id;
      newOpportunity.StageName = 'Closed Won';

     newOpportunities.add(newOpportunity);
   
     Map<Interger,List<sObject>> workerMap = new Map<Interger,List<sObject>>();
     workerMap = OpportunityUtils.processProducts(oldOpportunities, newOpportunities);
     System.assertEquals(2,workerMap.keySet().size(),'2 Objects set for update');
   
     System.enqueueJob(new Worker(workerMap,1)); // o is enqueued in the function call
 
   Test.stopTest();

  // Then
   List<Account> accountsAfterUpdate = [ select Id,AnnualRevenue from Account
                                        where AnnualRevenue =: amount];
   System.assertEquals(1,accountsAfterUpdate.size(),'1 Account found with expected AnnualRevenue');

  List<Case> casesAfterUpdate = [select Id,Status from Case where Status ='closed'];
 
  System.assertEquals (1,casesAfterUpdate.size(),'1 Closed Case Found');

  List<Lead> leadAfterUpdate =[select Id,Name,Status From Lead where Status ='Inactive'];

  System.assertEquals(1,leadAfterUpdate.size(),'1 Inactive Lead Found');

   }

}


Notes :

1.Queueable Jobs allow use of chaining,  monitoring,and can take any object as input.

2.Jobs have to be defined as a class, unlike future methods, and take more effort.

3.testing Queueable jobs using Test.startTest() and Test.stopTest();

Running Apex on a Schedule :
==============================

we can schedule Apex at regular times or even singular times to execute and get certain tasks done.

Schedule Apex at regular intervals for periodic tasks.

Pair with batch processing for large scale nightly jobs.

UI includes options for Scheduling,or use corn expressions for tighter time increments.

Cron Expressions :
==================

// Seconds Minutes Hours Day_of_month  Day_of_week Optional_year is the default

 public static String cronExpression ='0 0 13 1 * ? '

// The above expression is an example of running on the

1st of the month, every month, at 1PM UTC , no matter the year.


Scheduled tasks are great for
1.Sending emails
2.Data snapshots
3.Accounting
4.Expensive calculations
5.Installed or third party packages.

Limitation of Scheduled Apex :
===============================
100 Scheduled Apex jobs at one time per org.

ex :

// Schedule the Error Checker

string cronExpression = ' 0 0 1 * * ? ';
System.schedule('Error Checker ', cronExpression, new ErrorCheckerScheduled() );


global class ErrorCheckerScheduled implements Schedulable
{
 
  global void execute(SchedulableContext sc)
  {
     checkForErrors();
  }

  public void checkForErrors()
   {
     // Find any errors created today
   
     List<Error__c> errors = getErrors();

    // If any errors exist , notify relevant users
 
    if(!errors.isEmpty())
       notify(errors);

   }

  Public List<Error__c> getErrors()
  {
     Datetime now = Datetime.now();

     Datetime onDayAgo = Datetime.newInstance(now.year(),now.month(),now.day()-1,now.hour(),now.minute(),now.seconds());

    return [ select Id,Name ,CreatedDate from Error__c
              where CreatedDate >= : oneDayAgo ];

  }

  public void notify(List<Error__c> errors)
  {
    // Find Contacts to notify
    List<Contact> contactsNotify = [ select Id, Name, Error_Contact__c from Contact where Error_Contact__c = true ];
   
    // Collect Contact ID

    Set<String> contactIDs = new Set<String>()
 
    for(Contact contact : contactsToNotify)
        contactIDs.add(contact.Id);

    // Construct error message
 
    String errorsInMessage = '';
    String newLine = '/n';

     for(Error__c error : errors)
       errorsInMessage += error.Message__c + newLine;

    // Notify users

    sendErrorEmail(errorsInMessage,contactIDs);

  }

  public void sendErrorEmail (String message, Set<String> contactIDs)
  {
    List<String> contactsToSend = new List<String>();
    contactsToSend.addAll(contactIDs);

   // Construct email message
 
   Messaging.SingleEmailMessage errorEmail = new Messaging.SingleEmailMessage();
   errorEmail.setToAddresses(contactsToSend);
   errorEmail.setPlainTextBody (message);
   errorEmail.setSubject('Errors found on :' + String.valueOf(Date.today()));
 
  // Add to list of emails to send
 
   List<Messaging.Email> emailsToSend = new List<Messaging.Email>();
    emailsToSend.add(errorEmail);

  // Send the email
  Messaging.sendEmail(emailsToSend);

  }

}


@isTest
public class ErrorCheckerTest
{

 @isTest
 static void sendEmailOnExceptionTest()
  {
 
     // Set UserUtils to reflect testing
     // throwing an exception
        UserUtils.testingException = true;
 
     // Get current user
     Id currentUserId = UserInfo.getUserId();
     List<User> currentUser = [select Id,Name,Annual_Revenue__c from User
                               where Id =: currentUserId ];

    Map<Id,Decimal> currentUserToNewAmount = new Map<Id,Decimal>();
    currentUserToNewAmount.put(currentUserId, currentUser[0].Annual_Revenue__c + 5000);
 
    // When :
   
     Test.startTest();
   
     UserUtils.updateAnnualRevenue(currentuserToNewAmount);

     Test.stopTest();

   // Then

   List<Error__c> errorsAfterFuture.size()>0,'Errors found After Future call');
  }

}


Massive Workloads in batch Apex :
=================================
Batch Apex is for high volume of records: from hundreds of thousands to millions.

Each batch job is broken of into three major areas :

a Start function,
an Execute function , and
a Finish function

Governor Limits and Flex Queue :
=================================
Managing governor limits is always important in salesforce and with batch jobs, a key limitation is that only five batch jobs are allowed per org simultaneously.

Note : keep in mind , we could use a  batch to spring off queueable jobs to minimize time spent running the batch job.

Note : five batch jobs per org, but with the Apex Flex Queue, we can expand this to up to 100 queued batch jobs.

Queued batch jobs will have a Holding status when they are added to the queue, and then when exiting the queue, the  indication is a status of Queued.

ex :

global class WelcomeEmailBatch implements Database.Batchable<sObject>
{
   String query = ' select Id,Name,Email,Welcome_Email__c ' +
                  'FROM Contact ' +
                  'WHERE Welcome_Email__c = null '+
                  'AND Email != null ';

  global Database.QueryLocator start(Database.BatchableContext BC)
  {
 
    return Database.getQueryLocator(query);
  }

  global void execute (Database.BatchableContext BC , List<Contact> contacts)
   {
     sendWelcomeEmails(contacts);
   }
  global void finish (Database.BatchableContext BC)
   {
      // Add error handling here for if anything
      // failed in job
   }
  public static void sendWelcomeEmails(List<Contact> contacts)
   {
      List<Contact> contactsToUpdate = new List<Contact>();
       List<Id> contactIds = new List<Id>();
       for(Contact contact : contacts)
        {
           contactIds.add(contact.Id);
         
           contact.Welcome_Email__c = Datetime.now();
            contactsToUpdate.add(contact); 
        }

      Boolean allorNone = true;
      Messaging.MassEmailMessage massEmail = new Messaging.MassEmailMessage();
      massEmail.setTargetObjectIds(contactIds);
      massEmail.setTemplateID(getWelcomeTemplateId());
      Messaging.sendEmail(new List<Messaging.MassEmailMessage>{massEmail},allOrNone);

      if(!contactsToUpdate.isEmpty())
         update contactsToUpdate;
   }

   public static Id getWelcomeTemplateId()
    {
      return [select Id,DeveloperName FROM EmailTemplate
                where DeveloperName ='Welcome'
                LIMIT 1].Id;
    }
}

@isTest
private class WelcomeEmailBatchTest
{
  @testsetup
   static void setupData()
    {
      // create a contact
       insert new Contact (FirstName ='Test',
         LastName ='Contact',
         Email = 'test@example.com',
         Welcome_Email__c=null);

    }

 @isTest
  static void welcomeEmailBatchTest()
   {
    // Given
    List<Contact> contacts = [ select Id,Name,Welcome_Email__c
                               from Contact where Welcome_Email__c = null];
    // when
    Test.startTest();
     Database.executeBatch(new WelcomeEmailBatch());
    Test.stopTest();
   
    // Then
 
   List<Contact> contactsAfterBatch = [ select Id,Name,Welcome_Email__c
                                        FROM Contact
                                        WHERE Welcome_Email__c!=null];
 
  System.assertEquals(1,contactsAfterBatch.size(),'1 Contact Found With Welcome_Email__c populated');
 
   }

}

Apex Flex Queue :
=================
With Flex Queues, any jobs that are submitted for execution but are not processed immediately
by the system go in holding status and are placed in a separate queue (Apex Flex queue).

Up to 100 batch jobs can be in the holding status . When system resources become available, the system picks up jobs from the Apex flex queue and moves them to the batch job queue. The status
of these moved jobs changes from Holding to Queued.Queued jobs get executed when the system is ready to process new jobs and they will then go to InProgress status.

This would give you an impression that a lot of jobs are in holding status and are stuck. They actually are placed in the Apex flex queue and waiting to be picked up when system resources are available.


Sunday, 6 January 2019

centralized trigger framework in Apex

AccountTrigger.Trigger :
========================

trigger AccountTrigger on Account (before insert, before update, before delete, after insert, after update, after delete, after undelete)
{

    // This is the only line of code that is required.

   TriggerFactory.createTriggerDispatcher(Account.sObjectType);

}


TriggerFactory.cls :
=====================

/**
 This factory creates the correct dispatcher and dispatches the trigger event(s) to the appropriate
event handler(s). The dispatchers are automatically created using the Type API, hence dispatcher
registration is not required for each dispatchers.
*/
public with sharing class TriggerFactory
{
    /**
Creates the appropriate dispatcher and dispatches the trigger event to the dispatcher's event handler method.
@param Schema.sObjectType Object type to process (SObject.sObjectType)
*/
    public static void createTriggerDispatcher(Schema.sObjectType soType)
    {
        ITriggerDispatcher dispatcher = getTriggerDispatcher(soType);
       if (dispatcher == null)
            throw new TriggerException('No Trigger dispatcher registered for Object Type: ' + soType);
        execute(dispatcher);
    }

    /**
Dispatches to the dispatcher's event handlers.
@param ITriggerDispatcher A Trigger dispatcher that dispatches to the appropriate handlers
*/
    private static void execute(ITriggerDispatcher dispatcher)
    {
    TriggerParameters tp = new TriggerParameters(Trigger.old, Trigger.new, Trigger.oldMap, Trigger.newMap,
Trigger.isBefore, Trigger.isAfter, Trigger.isDelete,
Trigger.isInsert, Trigger.isUpdate, Trigger.isUnDelete, Trigger.isExecuting);
        // Handle before trigger events
        if (Trigger.isBefore) {
            dispatcher.bulkBefore();
            if (Trigger.isDelete)
                dispatcher.beforeDelete(tp);
            else if (Trigger.isInsert)
                dispatcher.beforeInsert(tp);
            else if (Trigger.isUpdate)
                dispatcher.beforeUpdate(tp);       
        }
        else // Handle after trigger events
        {
            dispatcher.bulkAfter();
            if (Trigger.isDelete)
                dispatcher.afterDelete(tp);
            else if (Trigger.isInsert)
                dispatcher.afterInsert(tp);
            else if (Trigger.isUpdate)
                dispatcher.afterUpdate(tp);
        }
        dispatcher.andFinally();
    }

    /**
Gets the appropriate dispatcher based on the SObject. It constructs the instance of the dispatcher
dynamically using the Type API. The name of the dispatcher has to follow this format:
<ObjectName>TriggerDispatcher. For e.g. for the Feedback__c object, the dispatcher has to be named
as FeedbackTriggerDispatcher.
  @param Schema.sObjectType Object type to create the dispatcher
@return ITriggerDispatcher A trigger dispatcher  if one exists or null.
   */
    private static ITriggerDispatcher getTriggerDispatcher(Schema.sObjectType soType)
    {
    String originalTypeName = soType.getDescribe().getName();
    String dispatcherTypeName = null;
    if (originalTypeName.toLowerCase().endsWith('__c')) {
    Integer index = originalTypeName.toLowerCase().indexOf('__c');
    dispatcherTypeName = originalTypeName.substring(0, index) + 'TriggerDispatcher';
    }
    else
    dispatcherTypeName = originalTypeName + 'TriggerDispatcher';
Type obType = Type.forName(dispatcherTypeName);
ITriggerDispatcher dispatcher = (obType == null) ? null : (ITriggerDispatcher)obType.newInstance();
    return dispatcher;
    }
}


AccountTriggerDispatcher.cls :
===============================

/**
 This class extends the TriggerDispatcherBase to provide the dispatching functionality for the trigger actions
 on the Account object. The event handlers support allowing and preventing actions for reentrant scenarios.
 This is controlled by the flag isBeforeXxxxx and isAfterXxxxx member variables. These variables need to be set
 to true before invoking the handlers and set to false after the invocation of the handlers. Resetting is MUST
 as otherwise unit tests MAY fail. The actual actions should be placed in the handlers (in a separate class).
*/
public class AccountTriggerDispatcher extends TriggerDispatcherBase
{
/** Stores if before insert handler is processing */
private static Boolean isBeforeInsertProcessing = false;
/** Stores if before update handler is processing */
private static Boolean isBeforeUpdateProcessing = false;
/** Stores if before delete handler is processing */
private static Boolean isBeforeDeleteProcessing = false;
/** Stores if after insert handler is processing */
private static Boolean isAfterInsertProcessing = false;
/** Stores if after update handler is processing */
private static Boolean isAfterUpdateProcessing = false;
/** Stores if after delete handler is processing */
private static Boolean isAfterDeleteProcessing = false;
/** Stores if after undelete handler is processing */
private static Boolean isAfterUnDeleteProcessing = false;

/**
Called by the trigger framework to carry out the actions before the records are inserted. If there is an
existing call running on the same context, the rentrant call will utilize the handler that was created
in the original call.
TriggerParameters Contains the trigger parameters which includes the records that is getting inserted.
*/
public virtual override void beforeInsert(TriggerParameters tp) {
if(!isBeforeInsertProcessing) {
isBeforeInsertProcessing = true;
execute(new AccountBeforeInsertTriggerHandler(), tp, TriggerParameters.TriggerEvent.beforeInsert);
isBeforeInsertProcessing = false;
}
else execute(null, tp, TriggerParameters.TriggerEvent.beforeInsert);
}

/**
Called by the trigger framework to carry out the actions before the records are updated. If there is an
existing call running on the same context, the rentrant call will utilize the handler that was created
in the original call.
TriggerParameters Contains the trigger parameters which includes the records that is getting updated.
*/
public virtual override void beforeUpdate(TriggerParameters tp) {
if(!isBeforeUpdateProcessing) {
isBeforeUpdateProcessing = true;
execute(new AccountBeforeUpdateTriggerHandler(), tp, TriggerParameters.TriggerEvent.beforeUpdate);
isBeforeUpdateProcessing = false;
}
else execute(null, tp, TriggerParameters.TriggerEvent.beforeUpdate);
}

        public virtual override void beforeDelete(TriggerParameters tp) {
if(!isBeforeDeleteProcessing) {
isBeforeDeleteProcessing = true;
execute(new AccountBeforeDeleteTriggerHandler(), tp, TriggerParameters.TriggerEvent.beforeDelete);
isBeforeDeleteProcessing = false;
}
else execute(null, tp, TriggerParameters.TriggerEvent.beforeDelete);
}

/**
Called by the trigger framework to carry out the actions after the record(s) are created. If there is an
existing call running on the same context, the rentrant call will utilize the handler that was created
in the original call.
TriggerParameters Contains the trigger parameters which includes the record(s) that got created.
*/
public virtual override void afterInsert(TriggerParameters tp) {
if(!isAfterInsertProcessing) {
isAfterInsertProcessing = true;
execute(new AccountAfterInsertTriggerHandler(), tp, TriggerParameters.TriggerEvent.afterInsert);
isAfterInsertProcessing = false;
}
else execute(null, tp, TriggerParameters.TriggerEvent.afterInsert);
}

/**

Called by the trigger framework to carry out the actions after the records are updated. If there is an
existing call running on the same context, the rentrant call will utilize the handler that was created
in the original call.
TriggerParameters Contains the trigger parameters which includes the record(s) that got updated.
*/
public virtual override void afterUpdate(TriggerParameters tp) {
if(!isAfterUpdateProcessing) {
isAfterUpdateProcessing = true;
execute(new AccountAfterUpdateTriggerHandler(), tp, TriggerParameters.TriggerEvent.afterUpdate);
isAfterUpdateProcessing = false;
}
else execute(null, tp, TriggerParameters.TriggerEvent.afterUpdate);
}

       public virtual override void afterDelete(TriggerParameters tp) {
if(!isAfterDeleteProcessing) {
isAfterDeleteProcessing = true;
execute(new AccountAfterDeleteTriggerHandler(), tp, TriggerParameters.TriggerEvent.afterDelete);
isAfterDeleteProcessing = false;
}
else execute(null, tp, TriggerParameters.TriggerEvent.afterDelete);
}


     public virtual override void afterUnDelete(TriggerParameters tp) {
if(!isAfterUnDeleteProcessing) {
isAfterUnDeleteProcessing = true;
execute(new AccountAfterUnDeleteTriggerHandler(), tp, TriggerParameters.TriggerEvent.afterUndelete);
isAfterUnDeleteProcessing = false;
}
else execute(null, tp, TriggerParameters.TriggerEvent.afterUndelete);
}
}


TriggerDispatcherBase.cls :
==============================
/**
 This class implements the ITriggerDispatcher and acts as an adapter to avoid implementing all the
 ITriggerDispatcher methods.
 @return Feedback__c The newly created feedback object.
*/
public virtual class TriggerDispatcherBase implements ITriggerDispatcher {

private static ITriggerHandler beforeInserthandler;
private static ITriggerHandler beforeUpdatehandler;
private static ITriggerHandler beforeDeleteHandler;
private static ITriggerHandler afterInserthandler;
private static ITriggerHandler afterUpdatehandler;
private static ITriggerHandler afterDeleteHandler;
private static ITriggerHandler afterUndeleteHandler;


    /**
This method is called prior to execution of a before trigger event. If you want
to load any lookup data or cache the data, then this is the place that you need
to put your code.
*/
    public virtual void bulkBefore() {}

    /**
This method is called prior to execution of an after trigger event.
*/
    public virtual void bulkAfter() {}

    /**
This method is called for records to be inserted during a BEFORE trigger.
*/
    public virtual void beforeInsert(TriggerParameters tp) {}

    /**
This method is called for records to be updated during a BEFORE trigger.
*/
    public virtual void beforeUpdate(TriggerParameters tp) {}

    /**
This method is called for records to be deleted during a BEFORE trigger.
*/
    public virtual void beforeDelete(TriggerParameters tp) {}

    /**
This method is called for records inserted during an AFTER trigger. Always put field validation
in the 'After' methods in case another trigger has modified any values. The record is 'read only'
at this point.
*/
    public virtual void afterInsert(TriggerParameters tp) {}

    /**
This method is called iteratively for each record updated during an AFTER trigger.
*/
    public virtual void afterUpdate(TriggerParameters tp) {}

    /**
This method is called iteratively for each record deleted during an AFTER trigger.
*/
    public virtual void afterDelete(TriggerParameters tp) {}
   
    /**
This method is called prior to execution of a AFTER UNDELETE trigger.
*/
    public virtual void afterUnDelete(TriggerParameters tp) {}
   
    public virtual void andFinally() {}
   
    /**
Called by the event handlers. If this is the first call in the context, then this method will create a new
instance of the appropriate handler and execute the mainEntry method. If there is an existing call running
on the same context, then this method will use the existing handler instance created by the original call
and execute the inProgressEntry method.
ITriggerHandler The trigger handler instance. The dispatcher need to pass an instance of the trigger handler, such
  as AccountAfterInsertTriggerHandler if this is the first call in a given context. If it is retry,
  then the dispatcher will need to pass null.
TriggerParameters The trigger parameters passed by the framework.
TriggerParameters.TriggerEvent The trigger event.
*/
    protected void execute(ITriggerHandler handlerInstance, TriggerParameters tp, TriggerParameters.TriggerEvent tEvent) {
    if(handlerInstance != null) {
    if(tEvent == TriggerParameters.TriggerEvent.beforeInsert)
    beforeInsertHandler = handlerInstance;
    if(tEvent == TriggerParameters.TriggerEvent.beforeUpdate)
    beforeUpdateHandler = handlerInstance;
    if(tEvent == TriggerParameters.TriggerEvent.beforeDelete)
    beforeDeleteHandler = handlerInstance;
    if(tEvent == TriggerParameters.TriggerEvent.afterInsert)
    afterInsertHandler = handlerInstance;
    if(tEvent == TriggerParameters.TriggerEvent.afterUpdate)
    afterUpdateHandler = handlerInstance;
    if(tEvent == TriggerParameters.TriggerEvent.afterDelete)
    afterDeleteHandler = handlerInstance;
    if(tEvent == TriggerParameters.TriggerEvent.afterUnDelete)
    afterUndeleteHandler = handlerInstance;
    handlerInstance.mainEntry(tp);
    handlerInstance.updateObjects();
    }
    else {
    if(tEvent == TriggerParameters.TriggerEvent.beforeInsert)
    beforeInsertHandler.inProgressEntry(tp);
    if(tEvent == TriggerParameters.TriggerEvent.beforeUpdate)
    beforeUpdateHandler.inProgressEntry(tp);
    if(tEvent == TriggerParameters.TriggerEvent.beforeDelete)
    beforeDeleteHandler.inProgressEntry(tp);
    if(tEvent == TriggerParameters.TriggerEvent.afterInsert)
    afterInsertHandler.inProgressEntry(tp);
    if(tEvent == TriggerParameters.TriggerEvent.afterUpdate)
    afterUpdateHandler.inProgressEntry(tp);
    if(tEvent == TriggerParameters.TriggerEvent.afterDelete)
    afterDeleteHandler.inProgressEntry(tp);
    if(tEvent == TriggerParameters.TriggerEvent.afterUnDelete)
    afterUndeleteHandler.inProgressEntry(tp);
    }
    }
}

ITriggerDispatcher.cls :
==========================

/**
 Defines the interface for the trigger dispatching architecture.
*/
public interface ITriggerDispatcher {
/**
Called by the trigger framework to carry out the actions before the bulk operations.
*/
void bulkBefore();

/**
Called by the trigger framework to carry out the actions after the bulk operations.
*/
void bulkAfter();

/**
Called by the trigger framework to carry out the actions after completing the bulk operations.
*/
void andFinally();

/**
Called by the trigger framework to carry out the actions before the records are inserted.
TriggerParameters Contains the trigger parameters which includes the records that is getting inserted.
*/
void beforeInsert(TriggerParameters tp);

/**
Called by the trigger framework to carry out the actions before the records are updated.
TriggerParameters Contains the trigger parameters which includes the records that is getting updated.
*/
void beforeUpdate(TriggerParameters tp);

/**
Called by the trigger framework to carry out the actions before the records are deleted.
TriggerParameters Contains the trigger parameters which includes the records that is getting deleted.
*/
void beforeDelete(TriggerParameters tp);

/**
Called by the trigger framework to carry out the actions after the records are inserted.
TriggerParameters Contains the trigger parameters which includes the records that got inserted.
*/
void afterInsert(TriggerParameters tp);

/**
Called by the trigger framework to carry out the actions before the records are updated.
TriggerParameters Contains the trigger parameters which includes the records that got updated.
*/
void afterUpdate(TriggerParameters tp);

/**
Called by the trigger framework to carry out the actions after the records got deleted.
TriggerParameters Contains the trigger parameters which includes the records that got deleted.
*/
void afterDelete(TriggerParameters tp);

/**
Called by the trigger framework to carry out the actions after the records are undeleted.
TriggerParameters Contains the trigger parameters which includes the records that got undeleted.
*/
void afterUnDelete(TriggerParameters tp);
}


ITriggerHandler.cls :
========================

/**
 Defines the interface for trigger handlers. Logic for the first time events are placed under the mainEntry
 method and the logic for the subsequent events raised on the same transaction (reentrant) are placed under
 the inProgressEntry method.
*/
public interface ITriggerHandler {
/**
Called for the first time in the execution context.
TriggerParameters The trigger parameters such as the list of records before and after the update.
*/
void mainEntry(TriggerParameters tp);

/**
Called for the subsequent times in the same execution context.
TriggerParameters The trigger parameters such as the list of records before and after the update.
*/
void inProgressEntry(TriggerParameters tp);

/**
Updates the objects, if any.
*/
void updateObjects();
}

TriggerHandlerBase.cls :
============================
/**
  This class implements the ITriggerHandler to provide abstract/virtual methods for the interface methods
  and so that the trigger handlers need to implement only the method that they have to. The only exception
  is the mainEntry, which is mandatory for the trigger handlers to implement.
*/
public abstract class TriggerHandlerBase implements ITriggerHandler {
protected Map<Id, SObject> sObjectsToUpdate = new Map<Id, SObject>();

/**
Called for the first time in the execution context. The trigger handlers need to implement this method.
TriggerParameters The trigger parameters such as the list of records before and after the update.
*/
public abstract void mainEntry(TriggerParameters tp);

/**
Called for the subsequent times in the same execution context. The trigger handlers can chose to ignore if they don't need the reentrant feature.
TriggerParameters The trigger parameters such as the list of records before and after the update.
*/
public virtual void inProgressEntry(TriggerParameters tp) {

}

/**
Updates the objects, if any.
*/
public virtual void updateObjects() {
if(sObjectsToUpdate.size() > 0)
update sObjectsToUpdate.values();
}
}


UtilityClass.cls :
=======================
/**
 This class has helper methods.
*/

public with sharing class UtilityClass {

/**
Gets the type name of the SObject.
@param SObject The SObject for which the name to be obtained.
@return String - The type name.
*/
public static String getSObjectTypeName(SObject so) {
return so.getSObjectType().getDescribe().getName();
}
}

AccountHelper.cls :
=====================

public with sharing class AccountHelper {

   public static map<Id,SObject> updateWebsite(list<Account> listNewAccounts){
map<Id,SObject> resultMap = new map<Id,SObject>();
for(Account acct : listNewAccounts) {
Account newAccount = new Account();
newAccount.Id = acct.Id;
newAccount.Website = 'www.salesforce.com';
resultMap.put(newAccount.Id, newAccount);
}
return resultMap;
}

}


TriggerParameters.cls :
=======================
/**
 This class holds the trigger parameters.
*/
public class TriggerParameters {

/**
A enum that represents the trigger event.
*/
public Enum TriggerEvent { beforeInsert, beforeUpdate, beforeDelete, afterInsert, afterUpdate, afterDelete, afterUndelete }
public TriggerEvent tEvent;

public List<SObject> oldList { get; private set; }
public List<SObject> newList { get; private set; }
public Map<Id, SObject> oldMap { get; private set; }
public Map<Id, SObject> newMap { get; private set; }
public String triggerObject { get; private set; }
public Boolean isExecuting { get; private set; }

/**
Constructs the TriggerParameter object.
@param List<SObject> A list of records with the state of 'before' event.
@param List<SObject> A list of records with the state of 'after' event.
@param Map<Id, SObject> A map of records with the state of 'before' event.
@param Map<Id, SObject> A map of records with the state of 'after' event.
@param Boolean A flag to indicate 'isBefore' event.
@param Boolean A flag to indicate 'isAfter' event.
@param Boolean A flag to indicate 'isDelete' event.
@param Boolean A flag to indicate 'isInsert' event.
@param Boolean A flag to indicate 'isUpdate' event.
@param Boolean A flag to indicate 'isUnDelete' event.
@param Boolean A flag to indicate 'isExecuting'.
*/
public TriggerParameters(List<SObject> olist, List<SObject> nlist, Map<Id, SObject> omap, Map<Id, SObject> nmap,
Boolean ib, Boolean ia, Boolean id, Boolean ii, Boolean iu, Boolean iud, Boolean ie) {
this.oldList = olist;
this.newList = nlist;
this.oldMap = omap;
this.newMap = nmap;
this.triggerObject = UtilityClass.getSObjectTypeName((this.oldList != null && this.oldList.size() > 0) ? this.oldList[0] : this.newList[0]);
if (ib & ii) tEvent = TriggerEvent.beforeInsert;
else if (ib && iu) tEvent = TriggerEvent.beforeUpdate;
else if (ib && id) tEvent = TriggerEvent.beforeDelete;
else if (ia && ii) tEvent = TriggerEvent.afterInsert;
else if (ia && iu) tEvent = TriggerEvent.afterUpdate;
else if (ia && id) tEvent = TriggerEvent.afterDelete;
else if (ia && iud) tEvent = TriggerEvent.afterUndelete;
isExecuting = ie;
}
}

AccountBeforeInsertTriggerHandler.cls :
========================================

public class AccountBeforeInsertTriggerHandler extends TriggerHandlerBase
 {
public override void mainEntry(TriggerParameters tp)
        {

}
}

AccountBeforeUpdateTriggerHandler.cls :
=======================================

public class AccountBeforeUpdateTriggerHandler extends TriggerHandlerBase
{
public override void mainEntry(TriggerParameters tp)
        {

}
}
AccountAfterInsertTriggerHandler.cls :
========================================
public class AccountAfterInsertTriggerHandler extends TriggerHandlerBase
{
public override void mainEntry(TriggerParameters tp)
         {

}
}

AccountAfterUpdateTriggerHandler.cls :
=======================================

public class AccountAfterUpdateTriggerHandler extends TriggerHandlerBase
{

public override void mainEntry(TriggerParameters tp) {
process((List<Account>)tp.newList);
}

private void process(List<Account> listNewAccounts) {
sObjectsToUpdate.putAll(AccountHelper.updateWebsite(listNewAccounts));
}

public override void inProgressEntry(TriggerParameters tp) {
System.debug('This is an example for reentrant code...');
}

public override void updateObjects() {
// for demonstration purposes, don't do anything here...
}
}

TriggerException.cls :
======================

public class TriggerException extends Exception {

}

Wrapper class in Apex


A Wrapper is simply an instance of a class that exposes some or all of the properties of a contained object,an object-oriented nomenclature.

ex :

public class MyWrapper
{

public Contact con {get;private set;}
public Opportunity opp {get;private set;}
public MyWrapper(Contact c,Opportunity o)
  {
     this.con=c;
     this.opp=o;
  }
}

ex 2 :


public class SObjectWrapper
{
  public SObject sObj {get;private set;}
  public Object name {get;private set;}
  Public Boolean deleteThis {get;private set;}
 //constructor
  Public SObjectWrapper(SObject so)
   {
     this.sObj=so;
     try{
          this.name=this.sObj.get('Name');
        }
        catch(Exception e){}
  }
}

public string selectString {get;set;}
public List<SObjectWrapper> sObjectWrpper {get;private set;}

public PageReference executeSelect()
{
   try{
      loadData(Database.query(this.selectString));
    }
   catch(Exception e)
   {
     logError(e.getMessage());
   }

}

private void loadData(List<SObject> sObjs)
{
  this.sObjectWrapperList = new List<SObjectWrapper>();
   for(SObject s : sObjs)
    {
    this.sObjectWrapperList.add(new SObjectWrapper(s));
   
   }
}

public PageReference deleteSelected()
{
  try
  {
    List<SObject> sObjs=new List<SObject>();
     for (SObjectWrapper so : this.sObjectWrapperList)
       {
         if(so.deleteThis==true)
            sObjs.add(so.sObj);
       }
     delete sObjs;
     executeSelect();
  }
  catch(Exception e)
   {
   logError(e.getMessage());
   }
  return null;
}

private void logError(String errorString)
{

 ApexPages.addMessage(new ApexPages.Message(ApexPages.severity.ERROR,errorString);
}

bulk processing patterns in Apex


bulk processing patterns in Apex :
=================================
All of your Apex code should be designed to handle bulk operations.All of it-no exceptions.

1.Apex triggers are always bulk.
2.Avoid governor limits.

common trigger design :
=======================
the order of trigger execution is guaranteed to be unpredictable.

managing recursion :
=====================

public with sharing class IsProcessing
{
  public static Boolean isProcessing=false;
}

public with sharing class TestIsProcessing
{
  public void processLogic()
  {
    public void processLogic()
     {
       if(!IsProcessing.isProcessing)
        {
          IsProcessing.isProcessing=true;
           //--- do processing
        }
     }

  static testMethod void testProcessing()
  {
   system.assert(!IsProcessing.isprocessing);
   TestIsProcessing tip =new TestIsProcessing();
    tip.processLogic();
   system.assert(IsProcessing.isProcessing);
  }
  }

}

The most common Apex pattern for dealing with recursion is by leveraging a public
static Boolean somewhere in a class.Then,either in trigger code or in a class method called by the trigger,the static Boolean is evaluated immediately when the trigger code first executes.If the value
is false,then it is immediately set to true when processing continues.

Note : static variables in apex are only global to the specific execution context that reference them.

Interface based centralized trigger dispatching architecture :
==========================================

Apex supports Interfaces,abstract classes and virtual classes.

ex : MainContactEntry.trigger

trigger MainContactEntry on Contact (before insert,before update,before delete,
after insert,after update,after delete)
{
  TriggerDispatcher.entry(new TriggerDispatcher.TriggerParameters(Trigger.isBefore,
     Trigger.isAfter,Trigger.isDelete,trigger.isInsert,Trigger.isUpdate,
     Trigger.isExecuting,Trigger.old,Trigger.new,Trigger.oldMap,Trigger.newMap));

}

TriggerDispatchr.cls

public with sharing class TriggerDispatcher
{

// contacts
  private static final String SACCOUNT='Account';
  private static final String SCONTACT='Contact';
  private static final String SLEAD = 'Lead';
  private static final String SOPPORTUNITY ='Opportunity';
  private static final String SUSER ='User';

  public static ITriggerEntry activeInstance = null;
  public static Map<Id,Sobject> sObjectsToUpdate =new Map<Id,Sobject>();

  public interface ITriggerEntry
   {
     void mainEntry(TriggerParameters tp);
     void inProgressEntry(TriggerParameters tp);
   }

  public class TriggerParameters
  {
    public String triggerObject {get;private set;}
    public Boolean isBefore {get;private set;}
    Public Boolean isAfter  {get;private set;}
    Public Boolean isDelete {get;private set;}
    public Boolean isInsert {get;private set;}
    public Boolean isUpdate {get;private set;}
    public Boolean isExecuting {get;private set;}
    public List<Sobject> oldList {get;private set;}
    public List<Sobject> newList {get;set;}
    public Map<Id,Sobject> oldMap {get;private set;}
    public Map<Id,Sobject> newMap {get;set;}
 
  public TriggerParameters(Boolean ib,Boolean ia,Boolean id,Boolean ii,Boolean iu,Boolean ie,
                   List<Sobject> ol,List<Sobject> nl,Map<Id,Sobject> om,map<Id,Sobject> nm)
    {
      this.isBefore = ib;
      this.isAfter  = ia;
      this.isDelete = id;
      this.isInsert = ii;
      this.isUpdate = iu;
      this.isExecuting = ie;
      this.oldList = ol;
      this.newList = nl;
      this.oldMap = om;
      this.newMap = nm;
      this.triggerObject = getSobjType( ( this.oldList !=null && this.oldList.size() >0 )?
           this.oldList[0] : this.newList[0] );
    }
 

 private String getSobjectType (Sobject so)
  {
    String retVal;
    if(so instanceof Account)  retVal = SACCOUNT;
    else if (so instanceof Contact) retVal = SCONTACT;
    else if (so instanceof Lead) retVal = SLEAD;
    else if (so instanceof Opportunity) retVal= SOPPORTUNITY;
    else if (so instanceof User) retVal = SUSER;
    return retVal;
  }
}

// Central dispatch entry

 public static void entry(TriggerParameters tp)
  {
     if(activeInstance == null)
          processWork(tp);
     else
       activeInstance.inProgressEntry(tp);
  }

 // Order of execution is controlled
  private static void processWork(TriggerParameters tp)
   {
      if(tp.triggerObject == SContact && tp.isAfter && tp.isUpdate)
       {
         execute(new SyncContactToUser(), tp);
       }
       else if(tp.triggerObject == SUSER && tp.isAfter && tp.isUpdate)
         execute(new SyncUserToContact(), tp);
     
       //update records
       if(sObjectsToUpdate.size() >0)
         update sObjectsToUpdate.values(); 
   }

  private static void execute (ITriggerEntry ite, TriggerParameters tp)
   {
     activeInstace = ite;
     activeInstance.mainEntry(tp);
   }

}


Note :
The instanceof keyword allows you to check the if an object is an instance of a class.

SyncContactToUser.cls :
========================

public with sharing class SyncContactToUser implements TriggerDispatcher.ITriggerEntry
{
  public void inProgressEntry(TriggerDispatcher.TriggerParameters tp)
  {
    // confirm caller origin
    system.assert(tp.triggerObject=='User');
  }
  public void mainEntry(TriggerDispatcher.TriggerParameters tp)
   {

   Map<Id,Contact> newMap =(Map<Id,Contact>) tp.newMap;
   List<Id> userIds = new List<Id>();
   for(Contact c : newMap.values())
      {
        userIds.add(c.User__c);
      }
    Map<Id,User> userMap = new Map<Id,User>
   ([SELECT Id,Phone,MobilePhone,Fax FROM User WHERE Id IN : userIds]);
   
    for(Contact c : newMap.values())
    {
      User u=userMap.get(c.User__c);
      if(u.Phone !=c.Phone || u.MobilePhone !=c.MobilePhone || u.Fax !=c.Fax)
        {
           u.Email=c.Email;
           u.Phone=c.Phone;
           u.MobilePhone =c.MobilePhone;
           u.Fax = c.Fax;
           TriggerDispatcher.sObjectsToUpdate.put(u.Id,u);
         }
    }
  }
}

SyncUserToContact.cls :
=========================

public with sharing class SyncUserToContact implements TriggerDispatcher.ITriggerEntry
{

 public void inProgressEntry(TriggerDispatcher.TriggerParameters tp)
  {
    system.assert(tp.triggerObject == 'Contact');
  }

 public void mainEntry(TriggerDispatcher.TriggerParameters tp)
  {
    Map<Id,User> newMap=(Map<Id,User>) tp.newMap;
    List<Contact> contactList = [SELECT User__c,Phone,MobilePhone,Fax FROM Contact WHERE User__c IN : newMap.keyset()];
     
      for(Contact c : contactList)
        {
           User u = newmap.get(c.User__c);
            if(c.Phone!=u.Phone || c.MobilePhone !=u.MobilePhone || c.Fax!=u.Fax)
              {
                c.Phone=u.Phone;
                c.MobilePhone=u.MobilePhone;
                c.Fax=u.Fax;
                TriggerDispatcher.sObjectsToUpdate.put(c.Id,c);
              }
        }
  }

}

Asynchronous Execution :
==========================
The three available tools that you can leverage for Asychronous Execution on force.com.

1.Scheduled Apex
2.Batch Apex
3.@Future calls

Through the standard interface(declarative tool) ,  you can activate schedulable Apex classes
and configure the execution recurrence.

The available recurrence options are very flexible,permitting schedules such as execute every Monday,Wednesday and Friday at 5 p.m. or execute  at 11 a.m on third Thrusday of every month.

Limitation to use the standard interface to schedule Apex classes.You're limited to one execution per day per configured schedule.

Note : 25 is the maximum number of  Apex classes that can be schedule at any one time,this poses a real problem.

if you schedule your Apex classes to run from within Apex that can execute on the hour if needed.

Scheduled Apex is only targeted to execute on a set schedule,but depending on available resources,they execute after the scheduled time.And only 25 concurrent Scheduled Apex processes
maybe active at any one time regardless of their recurrence frequency.

Future calls :
==============
Within a single execution context, we can only call 10 future methods and within each future method we're limited to 10 callouts.

1.Runs in its own execution context
2.Certain limits are increased
3.Limited to 10 calls per context
4.Limited to 200 calls per salesforce
user license,per 24 hours
5.Can't be called from Batch Apex or
future method.

Batch Apex :
==============

1.Runs in its own execution context.
2.Certain limits are increased.
3.can process up to 50 million records.
4.Batch sizes of 1-2000 records
5.Up to 250,000 daily Batch Apex Executions permitted.
6.Maximum 5 concurrent batch Apex processes.
7.Transactions cannot span batch processes in Apex.

In other words, after each batch of 1 to 2,000 objects has been committed, this cannot be undone in a subsequent batch.

8.Can't be called from Batch Apex or future methods.

There are a few ways to work around this using other techniques such as email services and chaining by way of using Scheduled Apex to bridge subsequent Batch Apex calls.