Empieza a escribir para buscar.

Salesforce Salesforce FFLib Enterprise Architecture

Arquitectura Salesforce con FFLib: Patrones Enterprise

Guía completa sobre la implementación de patrones arquitectónicos enterprise en Salesforce utilizando FFLib. Incluye Domain Layer, Selector Layer, Service Layer y Unit of Work.

15 min de lectura

Introducción: Mi perspectiva sobre fflib

Cuando se trata de desarrollar aplicaciones empresariales en Salesforce, la escalabilidad y el mantenimiento no son opcionales. He visto muchos proyectos enredarse en su propia complejidad por falta de una estructura clara. Aquí es donde entra en juego fflib, una implementación de los Apex Enterprise Patterns que, en mi opinión, es una de las herramientas más potentes para un desarrollador de Salesforce.

Más que una simple librería, fflib nos ofrece un lenguaje común y un conjunto de planos para construir software de forma ordenada. Adopta un enfoque por capas que impone una separación de responsabilidades (Separation of Concerns), un principio fundamental que a menudo se pasa por alto en el desarrollo de Apex. En este artículo, quiero compartir mi experiencia práctica con esta arquitectura, desglosando sus capas, los patrones que implementa y, lo más importante, el impacto real que tiene en la calidad y agilidad del desarrollo.

Las 4 capas clave de la arquitectura

La magia de fflib reside en su estructura de capas, donde cada una tiene una misión muy específica. Olvídate de las clases monolíticas y los triggers que hacen de todo. Aquí, el código se organiza así:

  • Capa de Dominio (Domain Layer): Es la guardiana de la lógica de negocio a nivel de registro. Piensa en ella como la experta en un objeto específico (e.g., Account, Lead).
  • Capa Selector (Selector Layer): Actúa como un repositorio centralizado para todas las consultas SOQL de un objeto. Es la única capa que debería hablar directamente con la base de datos para leer datos.
  • Capa de Servicio (Service Layer): Es el cerebro que orquesta operaciones complejas. Cuando un proceso de negocio involucra múltiples objetos o pasos, el servicio toma el control.
  • Unidad de Trabajo (Unit of Work): Gestiona las operaciones DML (insert, update, delete) de forma inteligente, agrupándolas para garantizar la integridad de los datos en una transacción.

Vamos a analizar cada una desde una perspectiva práctica.

Capa de Dominio (Domain Layer): La primera línea de defensa

En mi experiencia, la capa de Dominio es la primera línea de defensa contra la inconsistencia de datos y la lógica de negocio dispersa. Su responsabilidad es clara: encapsular todas las reglas, validaciones y comportamientos que pertenecen a un único registro de un objeto.

Imagina que tienes una regla que dice: “Un Lead no puede ser contactado si su score es inferior a 50”. En lugar de codificar esta lógica en un trigger, un controlador Lightning y una clase batch, la centralizas en un método dentro de LeadsDomain.

// LeadsDomain.cls
public class LeadsDomain extends fflib_SObjectDomain {
    // El constructor recibe la lista de registros del trigger
    public LeadsDomain(List<Lead> records) {
        super(records);
    }

    // Método invocado antes de una actualización
    public override void onBeforeUpdate(Map<Id, SObject> oldMap) {
        for(Lead newLead : (List<Lead>) this.records) {
            Lead oldLead = (Lead) oldMap.get(newLead.Id);
            // Lógica de validación centralizada
            if (newLead.Status == 'Contacted' && newLead.Score__c < 50) {
                newLead.addError('No se puede contactar un Lead con score inferior a 50.');
            }
        }
    }
}

Mi opinión y experiencia: Adoptar la capa de Dominio cambia las reglas del juego para los triggers. He participado en proyectos donde este patrón fue clave para encapsular las operaciones DML propias del objeto y simplificar drásticamente el ciclo de vida de los triggers, garantizando un comportamiento consistente. Aunque el framework de triggers de fflib puede parecer complejo al principio, el valor que aporta al centralizar la lógica es inmenso y evita la deuda técnica que surge de tener reglas de negocio duplicadas.

Capa Selector (Selector Layer): Consultas inteligentes y reutilizables

La capa Selector es la materialización del patrón Repositorio. Su única misión es construir y ejecutar consultas SOQL. En lugar de escribir queries directamente en tus servicios o controladores, le pides los datos al selector correspondiente.

// ILeadsSelector.cls (Interfaz)
public interface ILeadsSelector extends fflib_ISObjectSelector {
    List<Lead> selectRecentLeadsWithHighValue(Integer limitSize);
}

// LeadsSelector.cls (Implementación)
public class LeadsSelector extends fflib_SObjectSelector implements ILeadsSelector {
    public List<Schema.SObjectField> getSObjectFieldList() {
        return new List<Schema.SObjectField>{
            Lead.Name, Lead.Company, Lead.Status, Lead.Score__c
        };
    }
    
    public List<Lead> selectRecentLeadsWithHighValue(Integer limitSize) {
        return (List<Lead>) Database.query(
            newQueryFactory()
                .setCondition('Score__c > 100 AND Status = \'New\'')
                .setOrderBy(new fflib_QueryFactory.OrderBy('CreatedDate', 'DESC'))
                .setLimit(limitSize)
                .toSOQL()
        );
    }
}

Mi opinión y experiencia: El beneficio más obvio es la reutilización. Pero para mí, el verdadero poder de los Selectores reside en tres aspectos. Primero, la consistencia, que evita las temidas excepciones de campo no consultado. Segundo, la optimización, ya que centralizar las queries facilita implementar caché. Y tercero, la seguridad. En un caso práctico que recuerdo, el patrón Selector no solo hizo las consultas más eficientes, sino que permitió al equipo crear queries personalizadas fácilmente, reduciendo la duplicación de código y minimizando riesgos de SOQL injection.

Capa de Servicio (Service Layer): El cerebro de la operación

Si el Dominio se enfoca en un solo registro, el Servicio es el orquestador de procesos de negocio completos. Es la capa que coordina las interacciones entre diferentes dominios y selectores para cumplir con un caso de uso.

Por ejemplo, un servicio como LeadConversionService podría:

  1. Llamar a LeadsSelector para obtener los datos del Lead.
  2. Llamar a AccountsSelector para ver si ya existe una cuenta.
  3. Invocar a AccountsDomain y ContactsDomain para preparar los nuevos registros.
  4. Usar la UnitOfWork para registrar la creación de la Cuenta, el Contacto y la actualización del Lead.
// ILeadConversionService.cls
public interface ILeadConversionService {
    void convertLeads(Set<Id> leadIds);
}

// LeadConversionService.cls
public class LeadConversionService implements ILeadConversionService {
    private fflib_ISObjectUnitOfWork uow;
    // ... Inyección de dependencias para selectores y otros servicios

    public void convertLeads(Set<Id> leadIds) {
        // 1. Orquestar la lógica...
        // 2. Llamar a selectores...
        // 3. Usar dominios para aplicar reglas...
        // 4. Registrar cambios en la Unit of Work...
        uow.commitWork();
    }
}

Mi opinión y experiencia: La capa de Servicio es el pegamento que une la arquitectura. En una aplicación real, he observado cómo el servicio actúa como un mediador (Mediator Pattern) entre diferentes partes del sistema, fomentando la reutilización de código y reduciendo errores. Al funcionar como una fachada (Facade Pattern), proporciona una API de negocio clara. Esto significa que un trigger, un LWC o una API externa pueden invocar el mismo servicio, garantizando una ejecución idéntica de la lógica en todas partes.

Unidad de Trabajo (Unit of Work): Transacciones seguras y atómicas

El patrón Unit of Work es, en mi opinión, una de las joyas de fflib. Su función es simple pero revolucionaria para Apex: desacopla la lógica de negocio de las operaciones DML.

En lugar de hacer un insert o update inmediato, registras la intención de hacerlo en una instancia de fflib_SObjectUnitOfWork.

// Obtener una instancia de la Unit of Work
fflib_ISObjectUnitOfWork uow = Application.UnitOfWork.newInstance();

// Registrar operaciones en lugar de ejecutarlas
uow.registerNew(newAccount);
uow.registerDirty(existingContact);
uow.registerDeleted(oldOpportunity);

// ... más lógica de negocio ...

// Al final, confirmar todos los cambios en una sola transacción
uow.commitWork();

Mi opinión y experiencia: La Unit of Work nos da dos beneficios clave: atomicidad transaccional y eficiencia DML. Agrupa todas las operaciones del mismo tipo (e.g., todos los inserts de Cuentas) en una sola llamada DML, lo que es fundamental para no superar los límites del governor. Más importante aún es la atomicidad. Un equipo con el que trabajé reportó que la UoW les aseguró la consistencia de datos al agrupar las operaciones en una sola transacción y facilitar enormemente los rollbacks.

Sin embargo, es crucial entender esta atomicidad en escenarios de procesamiento masivo. Su naturaleza es estrictamente “todo o nada”. Esto significa que si un solo registro de un lote de 200 falla una validación, la transacción entera se revierte y ningún registro se guarda. No es que no “bulkifique” en términos de eficiencia DML, sino que su objetivo es la integridad total por encima del éxito parcial. Por ello, cuando se necesita procesar registros de forma masiva y se prefiere guardar los registros válidos y apartar los erróneos, la estrategia correcta a menudo implica diseñar servicios que manejen lotes más pequeños o usar métodos nativos como Database.insert(records, allOrNone: false) fuera del patrón UoW.

El poder del mocking: Pruebas unitarias reales en Apex

Aquí es donde fflib realmente brilla y justifica cualquier curva de aprendizaje. Gracias a la separación de capas y el uso de interfaces, el framework fflib Apex Mocks nos permite escribir pruebas unitarias verdaderamente aisladas.

Podemos probar la lógica de un servicio sin tocar la base de datos. ¿Cómo? “Mockeando” sus dependencias. Le decimos al test: “Cuando el servicio llame al LeadsSelector, no vayas a la base de datos; en su lugar, devuelve esta lista de Leads que yo he preparado”.

Esto nos permite verificar la lógica pura de nuestro servicio de forma rápida y determinista. Los tests se ejecutan en milisegundos en lugar de segundos, lo que acelera drásticamente los ciclos de CI/CD.

Conclusión: fflib como framework, no como dogma

He visto equipos abrumarse tratando de implementar fflib al 100% desde el día uno. Mi consejo es verlo como un framework de herramientas, no como un dogma inflexible.

No necesitas adoptarlo todo de golpe. Empieza por donde más valor te aporte. En mi experiencia, el camino más pragmático es:

  1. Empezar con Selectores y Servicios: Organizar las consultas y la lógica de negocio ya es una gran victoria.
  2. Introducir la Unit of Work: Una vez que los servicios están en su sitio, gestionar las DML de forma centralizada es el siguiente paso natural.
  3. Adoptar los Dominios gradualmente: Refactorizar los triggers para usar el patrón de Dominio puede ser el último paso, ya que a menudo requiere un esfuerzo más coordinado.

Al final del día, fflib nos proporciona un estándar arquitectónico probado que conduce a sistemas más mantenibles, escalables y robustos. El éxito no está en seguir las reglas ciegamente, sino en entender los principios que hay detrás y aplicarlos con criterio para construir software de alta calidad en la plataforma Salesforce. La recompensa es un código del que puedes sentirte orgulloso y un desarrollo más ágil a largo plazo.