Saltar al contenido

Bulkificación de gatillo general: mejores prácticas

Solución:

Buena pregunta, pero hay MUCHAS respuestas posibles, así que solo arrojaré mis 2 centavos.

La primera y más sencilla forma de ‘BULKIFY‘es aprovechar las colecciones para ahorrarse llamadas SOQL y declaraciones DML.

Aquí hay un recurso más antiguo, pero aún excelente, de Jeff Douglass sobre el uso de colecciones en Salesforce.

http://blog.jeffdouglas.com/2011/01/06/fun-with-salesforce-collections/

En mi opinión, diría que aprovechar las colecciones es el primer y mejor lugar para comenzar a tratar de optimizar y aumentar sus factores desencadenantes. Ahora intentaré mostrar algunos ejemplos de cómo el aprovechamiento de las cobranzas puede evitarle muchos dolores de cabeza por los límites del gobernador.

Este código usa una declaración DML para cada cuenta en trigger.new

Trigger myTrigger on Account(after insert) 
    for(Account a : trigger.new)
        My_Custom_Object__c obj = new My_Custom_Object__c(Account__c = a.Id);
        insert obj;
    

El ejemplo anterior realiza una llamada DML para cada cuenta en trigger.new. Si se trata de una inserción masiva, se encontrará con problemas de límite del regulador.

Este código ahora usa una declaración DML total, independientemente del tamaño del disparador.

Trigger myTrigger on Account(after insert) 
    list objList = new list();
    for(Account a : trigger.new)
        objList.add(new My_Custom_Object__c(Account__c = a.Id));
    
    insert objList;

Este ejemplo mueve el DML fuera del ciclo. En su lugar, agrega un nuevo objeto personalizado a la lista dentro del bucle. Una vez que haya revisado la lista completa de trigger.new, inserte la lista de objetos personalizados.

Este código usa una consulta SOQL para cada cuenta en trigger.new

Trigger myTrigger on Contact(before insert) 
    for(Contact c : trigger.new)
        if(c.AccountId != null) 
            Account a = [Select Id, Name, ShippingCity From Account Where Id =: c.AccountId];
            c.ShippingCity = a.ShippingCity;
        
    

El ejemplo anterior realiza una consulta SOQL para cada contacto en trigger.new. Si se trata de una inserción masiva, se encontrará con problemas de límite del regulador.

Este código ahora usa una consulta SOQL total, independientemente del tamaño del disparador.

Trigger myTrigger on Contact(before insert) 
    map accountMap = new map();
    for(Contact c : trigger.new)
        accountMap.put(c.AccountId, null);
    
    accountMap.remove(null);
    accountMap.putAll([Select Id, Name, ShippingCity From Account Where Id In : accountMap.keyset()]);
    for(Contact c : trigger.new)
        if(accountMap.containsKey(c.AccountId))
            c.ShippingCity = accountMap.get(c.AccountId).ShippingCity;
        
    

Este ejemplo anterior utiliza un mapa para almacenar todas las cuentas relacionadas con los contactos en trigger.new. La ventaja aquí es que una sola consulta SOQL reúne todas las cuentas. Luego, puede obtener la cuenta fácilmente dentro del ciclo sin tener que consultar la base de datos. Ahora tiene el mismo disparador con una sola consulta SOQL independientemente del tamaño del disparador.

Creo que esta es una de las mejores prácticas para optimizar sus activadores para operaciones masivas.

Para dar un paso más, hay algunas cosas más que podemos hacer para optimizar nuestros disparadores. Una de las mejores prácticas es usar solo un disparador por objeto.

Supongamos que tiene dos piezas específicas de lógica empresarial que debe aplicar después de crear una cuenta. La forma más sencilla de lograr esto sería crear 2 activadores en el objeto de la cuenta.

Trigger myTrigger1 on Contact(after insert) 
    //YOUR LOGIC FOR TRIGGER 1


Trigger myTrigger2 on Contact(after insert) 
    //YOUR LOGIC FOR TRIGGER 2

Esto podría funcionar bien dependiendo de su situación. ¿Qué sucede si tiene lógica en trigger2 que depende de los resultados de trigger1? No hay garantía del orden en el que se ejecutarán los desencadenadores, por lo que en algunos casos el desencadenante1 se ejecutará primero y en otros, el desencadenante2 se ejecutará primero.

Un enfoque simple para resolver esto es combinar la lógica en un solo disparador

Trigger myTrigger1 on Contact(after insert) 
    //YOUR FIRST PIECE OF LOGIC

    //YOUR SECOND PIECE OF LOGIC

Esto funciona técnicamente, ya que ahora puede controlar el orden de las operaciones, y es una buena práctica tener solo 1 disparador por objeto, pero aún se puede mejorar un poco. Digamos que, por el bien de los argumentos, este es un disparador bastante grande, con algunas piezas diferentes de lógica compleja.

Trigger myTrigger1 on Contact(after insert) 
    //YOUR FIRST PIECE OF LOGIC
    //LOTS OF CODE

    //YOUR SECOND PIECE OF LOGIC
    //LOTS OF CODE

    //YOUR THIRD PIECE OF LOGIC
    //LOTS OF CODE

    //YOUR N+1 PIECE OF LOGIC
    //LOTS OF CODE

Hay algunas cosas que saltan a la vista que podrían ser un problema.

  1. Toda esta lógica está enterrada en un disparador y no es reutilizable.
  2. Es muy difícil probar una pieza lógica específica en el disparador. Básicamente, debe llamar a una declaración DML para activar todo el disparador.

Entonces, ¿cómo lo arreglamos?

Querríamos mover la lógica del propio disparador a una clase de utilidad o manejador.

Trigger ContactTrigger on Contact(before insert, after insert, before update, after update) 
    if(trigger.isBefore)
        if(trigger.isInsert)
            ContactTriggerHandler.ContactBeforeInsert(trigger.new, trigger.newMap);
        
        if(trigger.isUpdate)
            ContactTriggerHandler.ContactBeforeUpdate(trigger.new, trigger.old, trigger.newMap, trigger.oldMap);
        
    

    if(trigger.isAfter)
        if(trigger.isInsert)
            ContactTriggerHandler.ContactAfterInsert(trigger.new, trigger.newMap);
        
        if(trigger.isUpdate)
            ContactTriggerHandler.ContactAfterUpdate(trigger.new, trigger.old, trigger.newMap, trigger.oldMap);
        
    

Manipulador

public class ContactTriggerHandler 

    public static void ContactBeforeInsert(list newContacts, map newMap) 
        myMethod1(newContacts, newMap);
        myMethod2(newContacts, newMap);
    

    public static void ContactBeforeUpdate(list newContacts, list oldContacts, map newMap, map oldMap) 
        myMethod3(newContacts, oldContacts, newMap, oldMap);
    

    public static void ContactAfterInsert(list newContacts, map newMap) 
        myMethod2(newContacts, newMap);
        myMethod4(newContacts, newMap);
    

    public static void ContactAfterUpdate(list newContacts, list oldContacts, map newMap, map oldMap) 
        myMethod5(newContacts, oldContacts, newMap, oldMap);
    

    public static void myMethod1(list newContacts, map newMap)
        //YOUR LOGIC
    
    public static void myMethod2(list newContacts, map newMap)
        //YOUR LOGIC
    
    public static void myMethod3(list newContacts, list oldContacts, map newMap, map oldMap)
        //YOUR LOGIC
    
    public static void myMethod4(list newContacts, map newMap)
        //YOUR LOGIC
    
    public static void myMethod5(list newContacts, list oldContacts, map newMap, map oldMap)
        //YOUR LOGIC
    

Ha resuelto los dos problemas mencionados anteriormente aquí. Ahora puede reutilizar su código. Puedes llamar a estos públicos static métodos de otros lugares para reutilizar el código. Ahora también puede segmentar sus pruebas y probar métodos individuales más pequeños al probar su disparador, ya que ya no tiene que hacer una llamada DML y ejecutar todo el disparador, solo puede probar métodos individuales.

Con suerte, esto maneja algunas de sus preguntas sobre la masificación / mejores prácticas. En realidad, hay un poco más de lo que puede avanzar con la optimización, pero luego nos adentramos en los marcos e interfaces de desencadenadores, pero creo que este es un comienzo decente para algunas de las mejores prácticas al escribir sus desencadenadores.

PD En una nota al margen, esta podría ser la patada que necesitaba para comenzar un blog, ya que resultó ser mucho más largo de lo que planeé originalmente.

Primero, aquí hay un enlace a la Lista actual de mejores prácticas para la lógica de Developer Force, que tiene enlaces a varios artículos que se aplican a la distribución masiva de su código.

Aquí hay un enlace al Patrón de desencadenadores para desencadenadores ordenados, optimizados y masivos, que es un buen lugar para comenzar si desea implementar sus desencadenadores como clases y tener todos y cada uno de los códigos para un solo objeto en un desencadenador; algo que creo que es muy importante hacer siempre que sea posible.

Con todo lo dicho, estas son las pautas generales en las que enfoco mis esfuerzos al escribir un desencadenante más complejo:

Inicialmente, suelo recopilar datos en Sets y Maps de Trigger.new, Trigger.old o Trigger.newmap, etc., iterando a través de FOR bucles. Normalmente uso conjuntos en lugar de listas para evitar la recopilación de duplicados. Puede haber ocasiones en las que realmente quiera recopilar todas las instancias de algo. Cuando ese sea el caso, usaré Listas. Una vez que tenga los ID de mi registro de interés, luego puedo recuperar cualquier dato relacionado que sea de interés de Trigger.oldmap o Trigger.newmap, por lo que no necesariamente necesito capturarlo todo por adelantado a menos que sea necesario para ayudarme a determinar qué registro Las identificaciones me interesan.

NO PONGO una CONSULTA dentro de un FOR ¡círculo!

Al haber realizado estas recopilaciones, si necesito consultar información que no está almacenada en trigger.new o trigger.old, Ahora puedo ejecutar un consulta única fuera de un FOR círculo. Minimizo la cantidad de consultas que hago en un disparador determinando las relaciones entre padres, hijos, nietos y otros objetos relacionados, y luego hago una sola consulta siempre que sea posible.

A veces, eso hace que sea necesario devolver los resultados de mi consulta como un Map en lugar de un List. Cuando eso sucede, uso un FOR bucle para recuperar Listas de los diferentes objetos dentro del Mapa para ponerlos en un formulario que pueda usar. Código de ejemplo a continuación.

Map AcctOppCustomObjectMap = new Map([SELECT Id,(SELECT Id, xxx FROM xxx__r), (SELECT Id, yyy FROM yyy__r) FROM Account WHERE Id IN :idSet]);

List yyyThings = new List();
List xxxxThings = new List();

for (Id accountId : AcctOppCustomObjectMap.keyset()) 
    Account acct = AcctOppCustomObjectMap.get(accountId);

    // the acct reference will have child or related lists for your subqueries
    // they will not be null, but they could be empty lists
    List xxxList = acct.xxx__r;
    List yyyList = acct.yyy__r;

    // iteration of the child or related records is possible now using these two lists
    for (xxx__c xThing : xxxList) 

       // if there's filtering I need to do or other work on the list, 
       // I can do it in a loop like below
       if(xThing.isEmpty() == false) 

          if(xThing.xxx == yThing.yyy) xThing.zzz = acct.Id

          xxxThings.add(xThing);
       
    

    for (yyy__c yThing : yyyList) 

       // the same thing can be done for the other objects or both

       if(yThing.yyy == xThing.xxx) yyyThings.add(yThing);
    


// I now have list xxxThings and list yyyThings pulled from the Map and sorted as needed

Realizo todas mis operaciones para crear, actualizar o eliminar registros antes de realizar mi DML en cada objeto como una sola operación. Si hay más de un objeto que requiere DML, he encontrado que a menudo es mejor llamar a una clase separada para hacer eso.

Intento evitar hacer una actualización en el mismo objeto desde el que se llamó a mi disparador. Hacer eso hará que mi gatillo se dispare de nuevo. En esa situación que a veces es necesaria, como restablecer una casilla de verificación que llamó al disparador para empezar, lo primero que hago en el disparador es probar para ver si la condición de ese campo me permitirá salir del disparador (suponiendo que sea el único criterios).

Además, también evito escribir un disparador que requiera que se realice una consulta antes de que pueda determinar si necesita hacer algo (sí, lamentablemente ese tipo de disparadores existen).

Eres capaz de añadir valor a nuestra información participando con tu experiencia en los comentarios.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *