Framework fácil para mantener los triggers de APEX limpios y libres de lógica

Este artículo también está disponible en: English (Inglés)

«Mantén tus triggers (disparadores) libres de lógica». Probablemente has leído eso un montón de veces en artículos de internet sobre APEX cuando estabas escribiendo tus primeros triggers. Para conseguir esto, necesitas aplicar un «framework». Que no es más que una forma de escribir y ordenar tu código de modo que sea sencillo. Tras leerme todos esos artículos y tutoriales, he dado por fin con mi framework favorito y fácil de entender.

¿Qué significa «trigger libre de lógica»?

Básicamente me refiero a que tu trigger debería contener únicamente instrucciones para llamar a métodos desarrollados en una clase controladora y no contener los métodos dentro. Debe estar escrito de una manera en la que sepas fácilmente qué va a pasar en cada caso que tu trigger es disparado. Por ejemplo:

trigger ContactTrigger on Contact (before update, after update, before insert, after insert) {

    if(Trigger.isBefore && (Trigger.isInsert || Trigger.isUpdate)){
        if (checkRecursive.runOnce()){
            ContactHandler.isBeforeInsertAndUpdate(Trigger.new);
        }
   
    }
}

Como se puede ver aquí, en lugar de escribir toda la lógica aquí (esto es: todas las cosas que queremos que ejecute el trigger), con todas sus líneas, nos valemos de una clase ayudante llamada «contact handler» para llamar a los métodos que corresponden a cada caso de disparo.

Con este esquema, puedes controlar fácilmente cuando pasan la cosas. Y en la práctica, no necesitas tocar tu trigger cuando quieras modificar un comportamiento. En su lugar, tendrás que dirigirte a la/s clases ayudantes.

La estructura del framework

La estructura del framework que normalmente utilizo sigue el siguiente patrón:

  • Trigger (archivo .trigger)
    • Clase controladora (o handler)
      • Clase ayudante (o Helper)

El trigger en sí mismo contiene únicamente las situaciones en las que debe disparar una cierta rutina de acciones. Estas rutinas, son controladas por la clase controladora (handler).

La clase controladora contiene las rutinas definidas en pasos (métodos) detallados que queremos que sean ejecutas cada vez y en cada situación según llame el trigger. Utilizar nombres descriptivos en nuestros métodos, además de buena práctica, nos ayudará a identificar cuándo aplicar cada uno. Un ejemplo:

Public with sharing class ContactHandler {
   
    public static void isBeforeInsertAndUpdate(List<Contact> newContacts){
   
            developmentValidator.updateDevelopmentId(newContacts);
    }
}

Cuando tienes «rutinas» que ejecutan los mismos métodos, utilizar una clase ayudante además de la clase controladora puede ser de ayuda. A veces la clase ayudante no es necesaria si tu trigger solo se dispara en uno o dos casos únicos controlados. Pero si prevés que tu trigger crecerá y reutilizará métodos ya definidos, en lugar de tener una clase controladora (handler) llena de código duplicado, quizás deberías considerar tener una clase ayudante para llamarlo.

También puedes incluir estos métodos dentro de la clase controladora y llamarlos de manera local. Pero una clase ayudante mejorará la legibilidad de tu código y la limpieza.

Ejemplo completo de un trigger (o disparador)

El trigger

Con este enfoque, finalmente tenemos un trigger libre de lógica en el que controlamos exactamente cada situación en la que nuestro trigger se dispara y qué dispara.

trigger saleCaseUnitTrigger on Sale_case_unit__c (after insert, before insert, after update, before update, after delete, before delete) {

	if(Trigger.isInsert && Trigger.isAfter){
		saleCaseUnitHandler.isAfterInsert();
	}

	if(Trigger.isUpdate && Trigger.isBefore){
		if (checkRecursive.runOnce()){
			saleCaseUnitHandler.isBeforeUpdate();
		}	
	}
	
	if(Trigger.isDelete && Trigger.isAfter){
		saleCaseUnitHandler.isAfterDelete();
	}
	
}

La clase controladora (handler)

A veces si tu trigger no es muy grande, pueden dejar toda la lógica en esta clase. Pero es probable que tengas métodos que se repitan en varios casos.

El propósito de esta clase controladora es definir los pasos que el trigger debe ejecutar para casa caso específico en el que hemos definido, sin lugar a dudas de qué corre en cada caso.

public with sharing  class saleCaseUnitHandler {
    public static void isAfterInsert(){
      SaleCaseUnitHandlerHelper.checkIfListingIsAvailable();
    }
    public static void isBeforeUpdate(){
      SaleCaseUnitHandlerHelper.checkIfListingIsInAnotherSaleCase(Trigger.new);
    }

    public static void isAfterDelete(){
      saleCaseUnitHandlerHelper.changeListingStatusWhenSCUDeleted(Trigger.old);
    }
}

La clase ayudante

En esta clase escribimos todos los métodos que ejecutan propiamente la lógica del trigger, y que serán llamados por la clase controladora en cada caso.

public with sharing class SaleCaseUnitHandlerHelper {

    public static void checkIfListingIsAvailable(){
       // Your actions...
    }

    public static void checkIfListingIsInAnotherSaleCase(List<Sale_Case_Unit__c> updatedSCUs)    {
       // Your actions...
     
}

    public static void changeListingStatusWhenSCUDeleted(List<Sale_Case_Unit__c> deletedSCUs)       {
     // Your actions...
    }


}

Si conoces algún otro framework fácil de entender para principiantes, ¡por favor compártelo en la sección de comentarios!

Peace and Code

Nadine.