Skip to main content

Aprender principios sobre la Capa de selectores

Objetivos de aprendizaje

Después de completar esta unidad, podrá:

  • Explicar los beneficios del uso de la capa de selectores.
  • Explicar las funcionalidades de la capa de selectores.
  • Comprender cómo encaja la capa de selectores en la arquitectura de su aplicación y la plataforma.

Siga el proceso con Trail Together

¿Desea seguir el proceso con un experto a medida que realiza este paso? Mire este video que forma parte de la serie Trail Together.

(Este video comienza en el minuto 36:04, en caso de que desee rebobinar y mirar el comienzo del paso nuevamente).

Conocer la capa de selectores

Este módulo se centra principalmente en garantizar una adecuada separación de intereses para que las bases de código de nivel de negocio complejas y de gran tamaño sean más claras, autodocumentadas, sólidas y adaptables a cambios debido a la refactorización o la evolución funcional. Continuemos con esta tendencia.

En esta unidad, obtenga información sobre el patrón de selector, una capa de código que encapsula la lógica responsable de consultar información de los objetos estándares y sus objetos personalizados. La capa de selectores envía esos datos al código de la capa de servicios y la capa de dominios. También puede reutilizar clases de selector de otras áreas que requieran consultas, como Apex por lotes y los controladores.

La ilustración siguiente muestra en qué lugar encaja la clase de selector dentro de la jerarquía de clases de patrones. La usan principalmente las clases de la capa de servicios (detrás del límite de servicio), como las propias clases de dominio y servicio. Sin embargo, los controladores de interfaz y las clases de Apex por lotes también pueden aprovecharla directamente.

La clase de servicio en la jerarquía.

Una capa de selectores contiene el código responsable de consultar registros de la base de datos. Aunque puede colocar consultas SOQL en otras capas, pueden suceder varias cosas a medida que el código se vuelva más complejo.

  • Incoherencias de consulta: si se realizan las mismas consultas desde lugares diferentes para obtener la misma información o los mismos criterios (o variantes sutiles), pueden aparecer incoherencias en su aplicación. Quizás se deban aplicar determinados criterios, y perderse tras copiar y pegar el código una y otra vez. Considere que una consulta es como un fragmento de lógica. Sabe que copiar y pegar fragmentos de lógica no es una buena idea. Tampoco es una buena idea hacerlo con el código.
  • Incoherencias de datos de consulta: cuando los datos de registros consultados (principalmente sObjects) empiezan a aparecer por su lógica, por arriba, por abajo y a los lados del código en la organización, los métodos de llamada que reciben los registros pueden volverse frágiles. Quizás un fragmento de código solicita que se le pase una Cuenta u Oportunidad, pero no puede garantizar de antemano qué campos se consultaron y, con ello, el contenido resultante, lo que provoca el temido error de tiempo de ejecución System.SObjectException:SObject row was retrieved via SOQL without querying the requested field:X.Y (La fila SObject se recuperó mediante SOQL sin consultar el campo solicitado:X.Y). Los desarrolladores pueden acabar repitiendo otra consulta sobre el mismo conjunto de registros sencillamente para consultar los campos solicitados. O quizás las rutas de código fluyan de tal modo que los registros de cuenta consultados por diferentes consultas pasen a una función compartida. Ninguna de las opciones es buena.
  • Incoherencias de seguridad: Salesforce requiere que todo el código Apex cumpla con la seguridad de objetos del usuario activo. Apex puede ejecutarse en el nivel de sistema, por lo que es responsabilidad del desarrollador comprobar la seguridad antes de realizar la consulta. Por fortuna, no se necesita mucho código Apex para realizar esta acción, pero es fácil omitirla y no es fácil de comprobar en pruebas de unidades. Si esto se mantiene en un selector, todo es mucho más fácil.

El patrón de selector que vamos a analizar ayuda a resolver estos problemas.

Patrón de selector

A continuación se indica la definición de Martin Fowler del patrón de asignador en el que se basa el patrón de selector.

Patrón de asignador: “Una capa de asignadores (473) que traslada datos entre objetos y una base de datos a la vez que los mantiene independientes con respecto a los demás y al propio asignador”. De Fowler, Martin

Este patrón usa el término selector en lugar de asignador. La palabra “selector” refleja mejor una diferencia clave en la implementación de Salesforce del patrón de asignador, una que Stephen Willcock observó al desarrollar la implementación de Apex original de este patrón. La clase base fflib_SObjectSelector que se presenta en esta unidad se basa en esto.

La diferencia está en que el patrón de selector no asigna un conjunto de resultados de base de datos a un objeto de datos o incluso una representación de dominio. Por el contrario, en el contexto de Salesforce, su función principal es proporcionar registros de SObject, la forma nativa de la plataforma para expresar los datos consultados en la base de datos. En algunos casos, como verá más adelante en esta unidad, puede proporcionar otras representaciones de datos de Apex, pero básicamente se limita a seleccionar datos en esta instancia, de ahí la base para el cambio de nombre de este patrón.

En cuanto a la separación de intereses, un selector se encarga de proporcionar lo siguiente:

  • Visibilidad, capacidad de reutilización y capacidad de mantenimiento: El selector facilita la búsqueda y el mantenimiento de lógica de consulta de base de datos. Al actualizar criterios de consulta o reflejar cambios de esquema, como al agregar campos usados comúnmente, lo hace con mayor facilidad y menor riesgo para el resto de la base de código. El selector debería usar las referencias de tiempo de compilación a nombres de campo, aunque se estén generando consultas dinámicas. De este modo se garantiza que, si se trata de eliminar campos, la plataforma evite la eliminación cuando existan referencias en el código.
  • Predictibilidad de los datos consultados: Debe quedar claro lo que el selector está haciendo en nombre del método y lo que está devolviendo. Devolver campos completados de forma incoherente en registros de sObject no siempre es un buen modelo, ya que aquello que estaba relacionado con los campos no queda claro para el emisor de la llamada o a quien el emisor pase los datos de registros resultantes, lo que daría lugar a excepciones de tiempo de ejecución. El patrón de selector garantiza que los registros de SObject tengan un conjunto mínimo de campos consultados. Los desarrolladores pueden ver esta parte del “contrato de selector del resto de la organización”.
  • Seguridad: Proporciona un medio para que el emisor active o desactive (para situaciones hipotéticas a nivel de sistema) comprobaciones de seguridad que aplican el uso compartido y permisos aplicados al contexto de usuario actual.
  • Adaptación a la plataforma: Optimice las consultas lo más posible mediante la expresión de criterios principalmente a través de conjuntos, con lo que se fomenta que el emisor se masifique en su código cuando llame a los métodos de selector. Los selectores también deberían poder equilibrar la necesidad de coherencia de los datos de campo consultados frente a los datos de campo óptimos cuando se trate con conjuntos de datos más grandes y la pila sea una preocupación.

Consideraciones de diseño

Al redactar una clase de selector, considere los siguientes aspectos de diseño:

Uso de una clase de selector

¿Cómo podemos poner en práctica estas consideraciones de diseño? A continuación se muestra un ejemplo sencillo que se usa para inicializar una instancia de clase de dominio con registros. Este selector usa métodos no estáticos para proporcionar su funcionalidad, de modo que se crea una instancia. Si el emisor mantiene la instancia del selector, existe un ámbito para realizar algunas tareas de configuración y almacenamiento en caché si es necesario.

List<Opportunity> opportunities =
   new OpportunitiesSelector().selectById(opportunityIds));

Aquí se incluye código de capa de servicios (puede que lo reconozca de la unidad anterior) que ahora usa un selector para consultar y pasar registros a una clase de dominio, que está realizando lógica de dominio en los registros consultados. Por ello, la lógica de SOQL en línea ahora se factorizó y se extrajo de la lógica de servicio y dominio.

List<Opportunity> opportunityRecords = new 
       OpportunitiesSelector().selectByIdWithProducts(opportunityIds);
Opportunities opportunities = new Opportunities(opportunityRecords);
opportunities.applyDiscount(discountPercentage, uow);

Este ejemplo muestra un selector en un método de inicio de Apex por lotes para controlar los registros que se están procesando en la ejecución.

public Database.QueryLocator start(Database.BatchableContext bc) {
    return new InvoicesSelector().queryLocatorInvoicesToProcess();
}

Prueba de clases de selector

Por supuesto, su código de clase de selector queda cubierto a través de sus pruebas sobre las capas de dominios o servicios. Sin embargo, si está interesado en realizar pruebas más puras sobre la unidad, las clases de selector son muy parecidas a cualquier otra clase: cree los datos de prueba, llame a los métodos y confirme los resultados.

@IsTest
private static void whenQueryOpportuntiesGetProducts() {
    
    // Given
    Set<Id> testRecordIds = setupTestOpportunities();
    // When
    OpportunitiesSelector selector = new OpportunitiesSelector();
    List<Opportunity> opportunities =
      selector.selectByIdWithProducts(testRecordIds);
    // Then
    System.assertEquals(10, opportunities.size());
    System.assertEquals(5, opportunities[0].OpportunityLineItems.size());
}

Si incluyó métodos QueryLocator en su selector, puede llamar al método de iterador para obtener sus registros resultantes y confirmar así los datos esperados en su prueba de selector.

Database.QueryLocator queryLocator =
    new ProductsSelector().queryLocatorById(new Set<Id> {product.Id});
Database.QueryLocatorIterator productsIterator = queryLocator.iterator();
Product2 queriedProduct = (Product2) productsIterator.next();

Recursos

¡Siga aprendiendo gratis!
Regístrese para obtener una cuenta y continuar.
¿Qué hay para usted?
  • Consiga recomendaciones personalizadas para sus objetivos profesionales
  • Practique sus aptitudes con retos prácticos y pruebas
  • Siga y comparta su progreso con empleadores
  • Póngase en contacto para recibir asesoramiento y oportunidades laborales