Apex Academy
Módulo 7·03·34 min

Clases de servicio

El handler sabe de eventos; la regla de negocio no debería. Sacarla a una clase de servicio es lo que permite que un botón, un proceso nocturno y el trigger usen exactamente la misma regla.

En Admin esUn subflow que llaman varios flows distintos

✓ En esta sub-lección

  1. 01Distinguir lo que es del handler (traducir eventos) de lo que es del servicio (la regla de negocio).
  2. 02Escribir un servicio con métodos static que reciben listas y no saben nada de triggers.
  3. 03Decidir quién guarda: el servicio cambia registros, quien lo llama decide si hace falta DML.
  4. 04Reutilizar la misma regla desde otra puerta distinta del trigger.

La nutria: ¿Te acuerdas? · Repaso de la lección 2

¿Qué debe quedar dentro del trigger delgado?

Soporte está contento: la prioridad ya sale bien. Tan contento que pide más: un botón «Recalcular prioridad» para los casos antiguos, creados antes de que existiera la regla. Y Operaciones quiere revisar cada noche los casos del día. La regla es la misma; lo que cambia es la puerta por la que se entra. Si la regla vive en beforeInsert, ninguna de esas puertas tiene sentido llamándola.

QUIEN LLAMA

CaseTriggerHandler.beforeInsert(newCases)

SIEMPRE EL MISMO SERVICIO

CaseEscalationService.applyPriorityRules(cases)

↳ No hace falta update: en before, Salesforce guarda solo.

El handler recibe los casos del trigger y le pide al servicio que aplique la regla. El servicio no sabe que viene de un trigger: solo recibe una lista.

Tres puertas, un solo servicio. Cambia de pestaña y fíjate en quién guarda en cada caso.

La nutria: Ya lo hacías con subflows

Yo lo hacía constantemente: cuando dos flows necesitaban la misma lógica (calcular un descuento, asignar una cola), la sacaba a un autolaunched flow y la llamaba como subflow desde los dos. Cambiabas la regla una vez y todos la heredaban. Una clase de servicio es eso: el subflow de Apex. El trigger, un botón o un proceso programado son los flows que lo llaman.

Qué es del handler y qué es del servicio

HandlerServicio
Sabe de…Eventos: before, after, insert, update.El negocio: cuentas Hot, prioridades, tareas.
Quién lo llamaSolo su trigger.Cualquiera: handler, botón, proceso, test.
Sus métodos se llaman…Por el evento: beforeInsert.Por lo que hacen: applyPriorityRules.
Cómo son sus métodosDe instancia, uno por evento.static, reciben listas, bulk.

Con la , el handler adelgaza hasta quedarse en lo que de verdad es suyo: decidir qué reglas se aplican en cada evento y en qué orden. Cada método del handler pasa a ser una lista de llamadas al servicio. Los métodos del servicio son static porque no guardan nada entre llamadas: reciben una lista, hacen su trabajo y terminan, como una fórmula que no recuerda la ejecución anterior (Módulo 5).

Apex
public with sharing class CaseTriggerHandler {
    public void beforeInsert(List<Case> newCases) {
        CaseEscalationService.applyPriorityRules(newCases);
    }
    public void afterInsert(List<Case> newCases) {
        CaseEscalationService.createReviewTasks(newCases);
    }
}

public with sharing class CaseEscalationService {
    public static void applyPriorityRules(List<Case> cases) { /* la regla */ }
    public static void createReviewTasks(List<Case> cases)  { /* las tareas */ }
}
Los métodos static se llaman con el nombre de la clase delante, sin new: CaseEscalationService.applyPriorityRules(…).

Quién guarda: la pregunta que decide el diseño

applyPriorityRules cambia la prioridad en memoria y NO hace update. Parece un olvido, pero es la clave. Cuando la llama el handler en before insert, los casos todavía no se han guardado y Salesforce los guardará solo; un update ahí, además, fallaría, porque en un trigger before no se permite hacer DML sobre los propios registros de Trigger.new. Cuando la llama el botón, en cambio, los casos ya existen, y quien tiene que guardarlos es el botón. El servicio cambia los registros; quien lo llama decide si hace falta guardar.

Apex
public with sharing class CaseActions {
    // El botón «Recalcular prioridad»: otra puerta, la misma regla
    public static void recalculatePriority(Set<Id> caseIds) {
        List<Case> cases = [
            SELECT Id, Origin, AccountId, Priority
            FROM Case
            WHERE Id IN :caseIds
        ];
        CaseEscalationService.applyPriorityRules(cases);
        update cases;   // aquí sí: estos casos ya existen
    }
}
La consulta trae los campos que la regla va a leer: Origin y AccountId. Si faltara uno, la regla lanzaría un error al intentar leerlo.