El framework de Appleman que los inició a todos

Las ideas propuestas por Dan Appleman en su libro Advanced Apex para la gestión eficiente de triggers, son de las más usadas y se usan como base para otros frameworks aparecidos posteriormente.

De hecho, el título que el autor dio al capítulo de su libro, “One trigger to rule them all” es conocido y mencionado en muchos artículos y sitios web.

Mi objetivo en esta entrada, es explicar de manera llana, los conceptos e ideas de este framework, esencial para cualquier proyecto mediano-grande. Al finalizar el artíuclo, podrás usar el framework directamente mejorar tu actual gestión de triggers.

Continue reading “El framework de Appleman que los inició a todos”

Un patrón de triggers simple y potente

Uno de los primeros patrones/frameworks que aparecieron fue en un Force.com Cookbook, y después mejorado en su propio blog, por Tony Scott.

Los principios de este patrón de gestión de triggers son:

  • El trigger no contine código, solo invoca a una clase Handler que implementa la lógica en cada evento
  • Una clase Factory, construye los Handlers de forma dinámica y enruta las acciones en función del evento del trigger (before, insert…)
  • Este funcionamiento requiere de la creación de una Interface que todos los triggers deberán implementar
  • Se incorporan funciones de BULK para eficientar el código

Algunas de las decisiones de este esquema planteado, se repite en varios de los frameworks que iremos viendo, por lo que es muy útil entenderlas.

Trigger sin código

El trigger solo realiza una acción, invocar a la Factory proporcionando el Type de la clase del Handler que tiene la lógica del Trigger, implementando la interface (vaya frase más horrenda por dios!).

Veamos el código del trigger, donde observamos su simplicidad:


/*
 * El código del trigger es mínimo, con una única invocación
 * a la Factory, que veremos se encará de la ejecución
 */

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

    // Se invoca a la clase factoria, que construirá la clase 
    // handler de forma dinámica 

    TriggerFactory.createAndExecuteHandler(AccountHandler.class);
}

Diseño de la Interface

Antes de ver el código de la Factory, veamos el código de la interface que cada trigger debe implementar, y que contiene todas las funciones que la parte del enrutador de la Factory utiliza.

Aquí es donde empieza a brillar el patrón reglando como debe implementarse el código del trigger, proporcionado la separación entre conceptos.

Creo que con los comentarios que he incluido en el código, podemos seguir lo que realiza el código:

/*
 * Para cada uno de los eventos del Trigger la interface 
 * implementa un método. 
 * Además implementa 2 métodos adicionales para caché de
 * los registros, y un método de finalización, donde por 
 * ejemplo, concentrar las operaciones DML, y así evitar 
 * ineficiencias y superar límites en el Execution Context
 *
 */

public interface ITrigger
{
 
    // La característica de todos estos métodos, es que son 
    // invocados para cada registro involucrado

    void beforeInsert(SObject so);
    void beforeUpdate(SObject oldSo, SObject so);
    void beforeDelete(SObject so);
    void afterInsert(SObject so);
    void afterUpdate(SObject oldSo, SObject so);
    void afterDelete(SObject so);

    // Los 2 métodos Bulk, son invocados para cachear los 
    // registros en variables tipo Map, y así eficientar
    // su consulta

    void bulkBefore();
    void bulkAfter();

    // Este método será invocado al finalizar toda la lógica del 
    // trigger. Puede servir para realizar operaciones DML, 
    // de limpieza, audit/log, etc.

    void andFinally();
}

Código de la Factory

Las funciones de esta clase:

  • crear la instancia del Handler del objeto que estemos gestionando (y de ahí su nombre)
  • enrutar el código hacia la función adecuada del Handler dependiendo de la naturaleza del trigger

public with sharing class TriggerFactory{

    // Función principal donde se realiza la 
    // creación dinámica del Handler y el enrutamiento

    public static void createAndExecuteHandler(Type t){
        ITrigger handler = getHandler(t);
        
        if (Handler == null) 
           throw new TriggerException('No Trigger Handler: ' + 
                                           t.getName());
         
        // Ejecutar el enrutado hacia la función que 
        // implementa el evento generado

        execute(handler);
    }
 
    /*
     * Ejecución del enrutado
     */
    private static void execute(ITrigger handler){

        if (Trigger.isBefore){

            // Función de caché
            handler.bulkBefore();
 
            if (Trigger.isDelete){
                for (SObject so : Trigger.old){
                    handler.beforeDelete(so);
                }
            }

            else if (Trigger.isInsert){
                for (SObject so : Trigger.new){
                    handler.beforeInsert(so);
                }
            }

            else if (Trigger.isUpdate){
                for (SObject so : Trigger.old){
                    handler.beforeUpdate(so, 
                               Trigger.newMap.get(so.Id));
                }
            }
        }
        
        //Repetimos el esquema hacia las funciones after*

        else{
            
            handler.bulkAfter();
 
            if (Trigger.isDelete){
                for (SObject so : Trigger.old){
                    handler.afterDelete(so);
                }
            }

            else if (Trigger.isInsert){
                for (SObject so : Trigger.new){
                    handler.afterInsert(so);
                }
            }

            else if (Trigger.isUpdate){
                for (SObject so : Trigger.old){
                    handler.afterUpdate(so, 
                              Trigger.newMap.get(so.Id));
                }
            }
        }
 
        // Post-proceso y operaciones DML 
        handler.andFinally();
    }
 
    //Función para la creación del handler

    private static ITrigger getHandler(Type t){
        Object o = t.newInstance();
 
        // Comprobación de seguridad
        if (o instanceOf ITrigger)return (ITrigger)o;
        else return null;
    }
 
    /*
     * El código de la Excepcion, puede ser el que deseemos
     */
    public class TriggerException extends Exception {}
}

Implementación de una clase de ejemplo

Por tanto, hemos visto el código de la interface, hemos visto el de la Factory creando y enrutando la lógica, veamos pues un ejemplo de implementación de trigger sobre el objeto Account, que he simplificado levemente el código para evitar un código extenso:


/*
 * Ejemplo de trigger trivial para el objeto Account
 *
 */
public without sharing class AccountHandler
    implements ITrigger
{
    // Variable miembro que almacena los identificadores 
    // de Account con los q trabaja nuestra lógica de negocio
    private Set<Id> m_inUseIds = new Set<Id>();
 
    public void bulkBefore() {

        // Función auxiliar que obtiene los Id account
        // con los que realiza cierto tratamiento
       if (Trigger.isDelete){
           Set<Id> accIds = Trigger.oldMap.keySet();

           //En el código de Bitbucket he utilizado Contacts, 
           //pero en esta versión. dejo la que utiliza Tony Scott
           //para que no te pierdas si consultas su artículo
           for (Account[] accounts : [
                           SELECT p.Id, 
                            (SELECT Id FROM Opportunities LIMIT 1)
                           FROM Account p
                           WHERE p.Id in : accIds]){

              for (Account acc : accounts){
                 if (acc.Opportunities.size() > 0) 
                        m_inUseIds.add(acc.id);
              }
           }
       }
    }

    // Se realizan ciertas comprobaciones de negocio sobre
    // los IDs que hemos obtenido previamente

    public void beforeDelete(SObject so) {

        Account myAccount = (Account) so;

        if (m_inUseIds.contains(myAccount.Id))
                so.addError('Esta Account no se puede eliminar');
    }
     
    public void bulkAfter(){}
    public void beforeInsert(SObject so){} 
    public void beforeUpdate(SObject oldSo, SObject so){}
    public void afterInsert(SObject so){}
    public void afterUpdate(SObject oldSo, SObject so){}
    public void afterDelete(SObject so){}
  
    public void andFinally(){
       System.Debug ('Trigger ejecutado');
    }
}

Facilidad de uso

¿Como es el uso de este patrón de uso de triggers en el día a día? Pues para es una de sus grandes virtudes.

Añadir nueva funcionalidad a un trigger existente implementado con este patrón, supone exclusivamente añadir o modificar el código en la función del Handler adecuada.

Si el trigger no existiera, creamos el trigger con 1 sola línea de código e implementamos su Handler (solo aquellas funciones que necesitemos).

Es decir, el patrón no requiere mantenimiento y posee una ciclotomía muy baja, lo que ayuda a su mantenibilidad y reducción de errores.

¿Consigue los objetivos?

Veamos si esta propuesta consigue los objetivos que nos habíamos propuesto:

  1. Crear un único trigger por objeto y evitar duplicidades: el patrón mediante el uso del Handler utiliza una única clase para la gestión del trigger
  2. Crear el trigger sin código, externalizando todo su código en clases externas: el trigger realizar una única llamada a la Factory, para registrarse y que se inicie el tratamiento
  3. Controlar el orden de ejecución: el orden de la ejecución lo marca el código del Handler, con lo que estamos controlando en todo momento el flujo de ejecución
  4. Detección de la recursividad / Control de re-entradas: no propone nada al respecto. Como veremos más adelante en el framework de Dan Appleman, mediante el uso de variables estáticas, esto sería fácilmente controlable
  5. Activar/Desactivar un trigger en caliente en cualquier entorno: no propone nada al respecto
  6. Crear una estructura de código que facilite un entorno multi-programador: la separación del handler, de la factory y del trigger, un equipo de programadores obtendrá pocas colisiones en sus modificaciones del código

Conclusiones

Para mi este es un patrón de gestión de triggers, que presenta las ideas principales de las buenas prácticas, exceptuando un único punto, la gestión de la recursividad, aunque es fácil incorporarla.

Además creo que, para un equipo que no esté utilizando ningún patrón ó framework, este puede ser el ideal para iniciarse, realizara varios tests, y adop-adap-tarlo rápidamente.

En la siguiente entrada veremos el framework de Dan Appleman, el que ha sido el framework de referencia durante mucho tiempo, que gestiona la recursividad y la re-entrada.

Espero que te haya sido de ayuda.

Enlaces Interesantes

Trigger Frameworks – Triggers súper vitaminados

Los triggers, son una funcionalidad ampliamente utilizada, pero que a medida que avanza el proyecto y se afianza, su uso desordenado provoca situaciones inadvertidas o efectos laterales indeseados, con frustración por parte del desarrollador y del usuario.

En este artículo quiero explicar, cuales son los problemas habituales, cuando surgen, y como se han desarrollado best practices y frameworks completos, que aportan soluciones, que con poco  esfuerzo permiten evitar estos situaciones complejas de diagnosticar.

Veamos pues, como crear Triggers vitaminados y super-mineralizados.

Continue reading “Trigger Frameworks – Triggers súper vitaminados”

Dynamic Planning for Async Processing

As explained in the last 2 entries, asynchronous processing and execution planning capabilities on Salesforce are very useful and powerful.

But nonetheless, in some complex scenarios, you will find some limitations that can be overcome easily.

Let me explain how, with some Apex Developing and using standard capabilities, you can get a quite powerful Dynamic Planner to sort out those limitations.

My goal on this entry is to describe the current limitations on the platform, explain how they can be solved, and hope they are useful and make some contribution to improve your knowledge in this key area.

Continue reading “Dynamic Planning for Async Processing”

Planificador Dinámico para Procesos Asíncronos

Como hemos visto en las 2 entradas anteriores, las capacidades asíncronas de Salesforce y su planificación son de gran ayuda.

Aún así, vimos que podemos encontrar una carencia: si nuestros procesos asíncronos tiene ncondiciones funcionales o técnicas de las que depende lanzar su ejecución.

Mi objetivo es explicar la idea, ofreciendo una prueba de concepto funcional, para que puedas darle la vuelta, mejorarlo y ampliarlo para tus necesidades.

Continue reading “Planificador Dinámico para Procesos Asíncronos”

Casos de uso para Apex asíncrono

Partiendo del artículo anterior donde vimos como Salesforce gestiona la ejecución de los procesos asíncronos  veamos como seleccionar el/los método/s  más adecuado/s en casos de uso reales.

Recordemos las posibilidades que tenemos:

  1. Utilización de la anotación @Future
  2. Implementación de la interfaz Schedulable
  3. Implementación de la interfaz Queueable
  4. Implementación de la interfaz Batchable

Continue reading “Casos de uso para Apex asíncrono”