Skip to main content

Apprendre les principes de la couche Sélecteur

Objectifs de formation

Une fois cette unité terminée, vous pourrez :

  • Expliquer les avantages de l’utilisation de la couche Sélecteur
  • Décrire les fonctionnalités de la couche Sélecteur
  • Comprendre le rôle de la couche Sélecteur dans votre architecture d’application et la plate-forme

Vidéo de démonstration Trail Together

Vous souhaitez être guidé pas à pas par un expert pendant que vous travaillez sur cette étape ? Regardez cette vidéo qui fait partie de la série Trail Together.

(Ce clip commence à 36 min 04 s, au cas où vous voudriez revenir en arrière et regarder à nouveau le début de l’étape.)

Découverte de la couche Sélecteur

Une grande partie de ce module est consacrée à la séparation adéquate des préoccupations, ce qui permet de rendre les vastes et complexes bases de code d’entreprise plus claires, auto-documentées, robustes et adaptables aux changements provoqués par la refactorisation ou l’évolution fonctionnelle. Continuons sur cette lancée.

Dans cette unité, vous découvrirez le modèle de sélecteur, une couche de code qui encapsule la logique chargée de demander des informations à vos objets standards et personnalisés. La couche Sélecteur injecte ces données dans votre code de la couche Domaine et de la couche Service. Vous pouvez également réutiliser des classes de sélecteur issues d’autres éléments nécessitant d’être interrogés, tels qu’Apex par lot et les contrôleurs.

L’illustration suivante montre l’emplacement de la classe Sélecteur dans la hiérarchie des classes de modèles. Elle est principalement utilisée par les classes de la couche Service (derrière la frontière de service), telles que les classes Domaine et les classes Service elles-mêmes. Toutefois, les contrôleurs d’IU et les classes Apex par lot peuvent également les exploiter directement.

Classe de service dans la hiérarchie.

Une couche de sélecteur contient le code chargé d’interroger des enregistrements dans la base de données. Bien que vous puissiez effectuer des requêtes SOQL dans d’autres couches, quelques problèmes peuvent survenir au fur et à mesure que votre code se complexifie.

  • Incohérence dans les requêtes : l’émission de requêtes identiques portant sur les mêmes informations ou critères (ou des variantes subtiles) à des endroits différents peut provoquer des incohérences dans votre application. Il se peut que certains critères nécessaires se perdent du fait que le code soit copié et collé à plusieurs reprises au fil du temps. Imaginez une requête comme un élément de logique. Vous savez très bien qu’il n’est pas judicieux de copier et coller des éléments de logique. C’est le cas pour le code également.
  • Incohérences dans les données de requête : lorsque des données d’enregistrement interrogées (essentiellement des sObjects) commencent à circuler dans votre logique, de haut en bas et aux côtés du code présent dans votre organisation, les méthodes d’appel recevant les enregistrements peuvent se fragiliser. Il se peut qu’un morceau de code demande qu’un objet Account (Compte) ou Opportunity (Opportunité) lui soit transmis, mais que celui-ci ne puisse pas garantir au préalable quels champs ont été interrogés et donc le contenu résultant. Cela provoque alors l’erreur d’exécution tant redoutée System.SObjectException:SObject row was retrieved via SOQL without querying the requested field:X.Y. Les développeurs peuvent finir par répéter une autre requête sur le même jeu d’enregistrements, simplement pour interroger les champs requis. Il se peut également que les chemins de code fonctionnent de sorte à ce que les enregistrements Account interrogés par différentes requêtes soient transmis à une fonction partagée. Quoi qu’il en soit, cela n’est pas souhaitable.
  • Incohérences dans la sécurité : Salesforce exige que l’ensemble du code Apex respecte la sécurité de l’objet de l’utilisateur actif. Comme Apex peut également s’exécuter au niveau du système, c’est au développeur qu’il incombe de vérifier la sécurité avant d’effectuer la requête. Heureusement, cela ne nécessite pas beaucoup de code Apex, mais cet aspect est souvent ignoré et difficile à tester lors des tests unitaires. Cantonner cela dans un sélecteur rend les choses beaucoup plus faciles.

Le modèle de sélecteur, dont nous allons maintenant parler, aide à résoudre les problèmes présentés ci-dessus.

Modèle de sélecteur

Voici la définition que donne Martin Fowler du modèle de mapper, sur lequel est basé le modèle de sélecteur.

Modèle de mapper « Une couche de mappers (473) déplaçant des données entre des objets et une base de données tout en les maintenant indépendantes les unes des autres et du mapper lui-même. » — Martin Fowler

Ce modèle emploie le terme « Sélecteur » plutôt que « Mapper ». Le terme Sélecteur reflète mieux une différence majeure dans l’implémentation que fait Salesforce du modèle de mapper, observée par Stephen Willcock lors du développement de l’implémentation Apex d’origine de ce modèle. La classe de base fflib_SObjectSelector présentée dans cette unité est construite sur cette différence.

Cette différence, c’est que le modèle de sélecteur ne mappe pas un jeu de résultats de base de données sur un objet de données ou sur une représentation Domaine. Dans le contexte de Salesforce, son rôle principal est de fournir des enregistrements SObject, qui sont le moyen natif de la plate-forme de représenter les données interrogées à partir de la base de données. Dans certains cas, comme vous le verrez plus loin dans cette unité, il peut fournir d’autres représentations Apex des données, mais dans ce cas précis, il s’agit essentiellement de sélectionner des données, d’où le changement de nom de ce modèle.

Voici ce qu’un sélecteur apporte en matière de séparation des préoccupations :

  • Visibilité, capacité de réutilisation et de maintenance : le sélecteur facilite la recherche et la maintenance de la logique de requête de base de données. Cela entre en jeu lors de la mise à jour de critères de requête ou de l’intégration de modifications de schéma, pour permettre, par exemple, l’ajout de champs couramment utilisés avec plus de facilité et moins de risques pour le reste de la base de code. Le sélecteur doit utiliser les références aux noms de champs effectuées lors de la compilation, même si des requêtes dynamiques sont en cours de construction. Cela garantit, en cas de suppression des champs, que la plate-forme empêche cette suppression si des références existent dans le code.
  • Prévisibilité des données interrogées : l’action du sélecteur dans le nom de la méthode et ce qu’il renvoie doivent être clairement définis. Le fait de renvoyer des champs remplis de manière incohérente dans les enregistrements sObject n’est pas toujours judicieux, car ce qui relevait du champ n’est alors pas clair pour l’appelant ou l’interlocuteur auquel il a transmis les données d’enregistrement résultantes, ce qui entraîne des exceptions d’exécution. Le modèle de sélecteur garantit qu’un ensemble minimal de champs est interrogé sur les enregistrements SObject. Les développeurs peuvent considérer ce comportement comme faisant partie du « contrat entre le sélecteur et le reste de l’organisation ».
  • Sécurité : le sélecteur donne à l’appelant un moyen d’activer ou de désactiver (pour les scénarios au niveau système) les contrôles de sécurité déclenchant le partage et les autorisations appliquées au contexte utilisateur actuel.
  • Sympathie de la plate-forme : optimise les requêtes en exprimant principalement les critères via des ensembles, ce qui incite l’appelant à utiliser le traitement en masse dans son code lors de l’appel des méthodes de sélection. Les sélecteurs doivent également pouvoir équilibrer le besoin de cohérence des données de champ interrogées par rapport aux données de champ optimales lorsque des ensembles de données plus importants sont impliqués et que la mémoire constitue une préoccupation.

Considérations de conception

Lors de l’élaboration d’une classe Sélecteur, vous devez tenir compte des aspects de conception suivants :

Utilisation d’une classe Sélecteur

Comment mettre en pratique ces considérations de conception ? Voici un exemple simple, utilisé pour initialiser une instance de classe Domaine à l’aide d’enregistrements. Ce sélecteur fait usage de méthodes non statiques pour accomplir sa fonction ; une instance est alors créée. Si l’appelant conserve l’instance de sélecteur, il est possible d’effectuer des opérations de configuration et de mise en cache si nécessaire.

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

Voici un code de couche Service (nous le reprenons de l’unité précédente, vous le reconnaîtrez donc peut-être) qui utilise désormais un sélecteur pour interroger et transmettre des enregistrements à une classe Domaine, qui, elle, exécute une logique de domaine sur les enregistrements interrogés. Ainsi, la logique SOQL en ligne a maintenant été exclue des logiques Service et Domaine.

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

Cet exemple présente un sélecteur dans une méthode de démarrage Apex par lot utilisé pour gérer les enregistrements en cours de traitement dans l’exécution.

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

Test des classes Sélecteur

Votre code de classe Sélecteur a bien évidemment déjà été éprouvé lors des tests que vous avez effectués au sein des couches Domaine ou Service. Toutefois, si vous souhaitez effectuer davantage de tests unitaires à proprement parler, le processus pour les classes Sélecteur est sensiblement le même que pour toutes les autres classes : créez les données de test, appelez les méthodes et procédez à l’assertion des résultats.

@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 vous avez intégré des méthodes QueryLocator à votre sélecteur, vous pouvez appeler la méthode iterator pour en obtenir les enregistrements résultants et ainsi procéder à l’assertion des données attendues dans votre test Sélecteur.

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

Ressources

Formez-vous gratuitement !
Créez un compte pour continuer.
Qu’est-ce que vous y gagnez ?
  • Obtenez des recommandations personnalisées pour vos objectifs de carrière
  • Mettez en pratique vos compétences grâce à des défis pratiques et à des questionnaires
  • Suivez et partagez vos progrès avec des employeurs
  • Découvrez des opportunités de mentorat et de carrière