Apex - padrões de design de gatilho

Os padrões de design são usados ​​para tornar nosso código mais eficiente e evitar atingir os limites do governador. Freqüentemente, os desenvolvedores podem escrever código ineficiente que pode causar instanciação repetida de objetos. Isso pode resultar em código ineficiente e com desempenho insatisfatório e, potencialmente, na violação dos limites do regulador. Isso ocorre mais comumente em gatilhos, pois podem operar em um conjunto de registros.

Veremos algumas estratégias de padrão de projeto importantes neste capítulo.

Bulk Triggers Design Patterns

No caso de negócios reais, será possível que você precise processar milhares de registros de uma vez. Se o seu gatilho não foi projetado para lidar com tais situações, ele pode falhar durante o processamento dos registros. Existem algumas práticas recomendadas que você precisa seguir ao implementar os gatilhos. Todos os gatilhos são em massa por padrão e podem processar vários registros de uma vez. Você deve sempre planejar o processamento de mais de um registro por vez.

Considere um caso de negócios, em que você precisa processar um grande número de registros e escreveu o gatilho conforme fornecido a seguir. Este é o mesmo exemplo que tomamos para inserir o registro da fatura quando o Status do Cliente muda de Inativo para Ativo.

// Bad Trigger Example
trigger Customer_After_Insert on APEX_Customer__c (after update) {
   
   for (APEX_Customer__c objCustomer: Trigger.new) {
      
      if (objCustomer.APEX_Customer_Status__c == 'Active' && 
         trigger.oldMap.get(objCustomer.id).APEX_Customer_Status__c == 'Inactive') {
         
         // condition to check the old value and new value
         APEX_Invoice__c objInvoice = new APEX_Invoice__c();
         objInvoice.APEX_Status__c = 'Pending';
         insert objInvoice;   //DML to insert the Invoice List in SFDC
      }
   }
}

Agora você pode ver que a instrução DML foi escrita para o bloco de loop que funcionará ao processar apenas alguns registros, mas quando você estiver processando algumas centenas de registros, atingirá o limite da instrução DML por transação que é o governor limit. Teremos uma visão detalhada dos limites do governador em um capítulo subsequente.

Para evitar isso, temos que tornar o gatilho eficiente para processar vários registros de uma vez.

O exemplo a seguir ajudará você a entender o mesmo -

// Modified Trigger Code-Bulk Trigger
trigger Customer_After_Insert on APEX_Customer__c (after update) {
   List<apex_invoice__c> InvoiceList = new List<apex_invoice__c>();
   
   for (APEX_Customer__c objCustomer: Trigger.new) {
      
      if (objCustomer.APEX_Customer_Status__c == 'Active' &&
         trigger.oldMap.get(objCustomer.id).APEX_Customer_Status__c == 'Inactive') {
         
         //condition to check the old value and new value
         APEX_Invoice__c objInvoice = new APEX_Invoice__c();
         objInvoice.APEX_Status__c = 'Pending';
         InvoiceList.add(objInvoice);//Adding records to List
      }
   }
   
   insert InvoiceList;
   // DML to insert the Invoice List in SFDC, this list contains the all records 
   // which need to be modified and will fire only one DML
}

Este gatilho disparará apenas 1 instrução DML, pois estará operando sobre uma lista e a lista tem todos os registros que precisam ser modificados.

Dessa forma, você pode evitar os limites do controlador de instrução DML.

Classe Trigger Helper

Escrever todo o código no gatilho também não é uma boa prática. Portanto, você deve chamar a classe Apex e delegar o processamento da classe Trigger para a classe Apex, conforme mostrado abaixo. A classe Trigger Helper é a classe que faz todo o processamento para o gatilho.

Vamos considerar nosso exemplo de criação de registro de fatura novamente.

// Below is the Trigger without Helper class
trigger Customer_After_Insert on APEX_Customer__c (after update) {
   List<apex_invoice__c> InvoiceList = new List<apex_invoice__c>();
   
   for (APEX_Customer__c objCustomer: Trigger.new) {
      
      if (objCustomer.APEX_Customer_Status__c == 'Active' &&
         trigger.oldMap.get(objCustomer.id).APEX_Customer_Status__c == 'Inactive') {
         
         // condition to check the old value and new value
         APEX_Invoice__c objInvoice = new APEX_Invoice__c();
         objInvoice.APEX_Status__c = 'Pending';
         InvoiceList.add(objInvoice);
      }
   }
   
   insert InvoiceList; // DML to insert the Invoice List in SFDC
}

// Below is the trigger with helper class
// Trigger with Helper Class
trigger Customer_After_Insert on APEX_Customer__c (after update) {
   CustomerTriggerHelper.createInvoiceRecords(Trigger.new, trigger.oldMap);
   // Trigger calls the helper class and does not have any code in Trigger
}

Helper Class

public class CustomerTriggerHelper {
   public static void createInvoiceRecords (List<apex_customer__c>
   
   customerList, Map<id, apex_customer__c> oldMapCustomer) {
      List<apex_invoice__c> InvoiceList = new Listvapex_invoice__c>();
      
      for (APEX_Customer__c objCustomer: customerList) {
         
         if (objCustomer.APEX_Customer_Status__c == 'Active' &&
            oldMapCustomer.get(objCustomer.id).APEX_Customer_Status__c == 'Inactive') {
            
            // condition to check the old value and new value
            APEX_Invoice__c objInvoice = new APEX_Invoice__c();
            
            // objInvoice.APEX_Status__c = 'Pending';
            InvoiceList.add(objInvoice);
         }
      }
      
      insert InvoiceList;  // DML to insert the Invoice List in SFDC
   }
}

Nesse caso, todo o processamento foi delegado à classe auxiliar e, quando precisarmos de uma nova funcionalidade, podemos simplesmente adicionar o código à classe auxiliar sem modificar o gatilho.

Gatilho único em cada sObject

Sempre crie um único gatilho em cada objeto. Vários gatilhos no mesmo objeto podem causar o conflito e erros se atingir os limites do governador.

Você pode usar a variável de contexto para chamar os diferentes métodos da classe auxiliar de acordo com o requisito. Considere nosso exemplo anterior. Suponha que nosso método createInvoice deva ser chamado apenas quando o registro é atualizado e em vários eventos. Então, podemos controlar a execução como abaixo -

// Trigger with Context variable for controlling the calling flow
trigger Customer_After_Insert on APEX_Customer__c (after update, after insert) {
   
   if (trigger.isAfter && trigger.isUpdate) {
      // This condition will check for trigger events using isAfter and isUpdate
      // context variable
      CustomerTriggerHelper.createInvoiceRecords(Trigger.new);
      
      // Trigger calls the helper class and does not have any code in Trigger
      // and this will be called only when trigger ids after update
   }
}

// Helper Class
public class CustomerTriggerHelper {
   
   //Method To Create Invoice Records
   public static void createInvoiceRecords (List<apex_customer__c> customerList) {
      
      for (APEX_Customer__c objCustomer: customerList) {
         
         if (objCustomer.APEX_Customer_Status__c == 'Active' &&
            trigger.oldMap.get(objCustomer.id).APEX_Customer_Status__c == 'Inactive') {
            
            // condition to check the old value and new value
            APEX_Invoice__c objInvoice = new APEX_Invoice__c();
            objInvoice.APEX_Status__c = 'Pending';
            InvoiceList.add(objInvoice);
         }
      }
      
      insert InvoiceList; // DML to insert the Invoice List in SFDC
   }
}