Skip to main content

Appliquer les principes de la couche Domaine dans Apex

Objectifs de formation

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

  • Créer une classe Domaine Apex
  • Intégrer votre code de retour aux valeurs par défaut et votre code de validation dans une classe Domaine
  • Définir des méthodes de la classe Domaine sur les événements de déclencheur Apex
  • Contrôler la mise en œuvre de l’application de la sécurité lors de l’exécution
Remarque

Remarque

Vous souhaitez apprendre en français ? Dans ce badge, les validations de défi pratique Trailhead se font en anglais. Les traductions sont fournies entre parenthèses à titre de référence. Dans votre Trailhead Playground, veillez (1) à définir les États-Unis comme région, (2) à sélectionner l’anglais comme langue, et (3) à copier et coller uniquement les valeurs en anglais. Suivez les instructions ici.

Consultez le badge Trailhead dans votre langue pour découvrir comment profiter de l’expérience Trailhead traduite.

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 à 17 min 30 s, au cas où vous voudriez revenir en arrière et regarder à nouveau le début de l’étape.)

Code de référence

Ce module fait référence aux classes Apex suivantes du projet Exemple de code commun Apex FFLIB. Nous vous conseillons de les ouvrir avant de commencer.

Création de classes Domaine

La classe fflib_SObjectDomain utilisée dans le code de déclencheur dans l’unité précédente étend une classe de base prenant en charge la fonctionnalité de gestionnaire de déclencheur et fournit des fonctionnalités utiles, telles que la sécurité des objets.

La classe fflib_SObjectDomain.

Remarque

Remarque : la plupart des méthodes existent sous forme de méthodes virtuelles. Les méthodes handle*XXXX* de cette classe de base sont chargées de veiller à ce que les méthodes on*XXXX* soient appelées aux moments opportuns. Pour les cas plus avancés, dans lesquels vous avez besoin d’une gestion directe, vous pouvez remplacer les méthodes de gestionnaire, mais sachez qu’à moins que vous n’appeliez la version de super-classe de la méthode handle*XXX, les méthodes on*XXXX* de la super-classe ne seront pas appelées. Reportez-vous aux méthodes de classe OpportunitiesTriggerHandler pour découvrir divers exemples.

La classe de base emploie le modèle de méthode pour fournir des points d’ancrage standard permettant d’implémenter une logique de domaine commune pour la validation des enregistrements via la méthode onValidate() et le retour aux valeurs de champ par défaut via la méthode onApplyDefaults().

Il existe également des méthodes permettant d’intégrer la logique relative à des événements de déclencheur Apex spécifiques. Enfin, le constructeur (pour lequel toutes les classes qui ont étendu la classe fflib_SObjectDomain doivent également procéder à une exposition) établit une liste des sObjects conformément à l’objectif de conception du traitement en masse décrit dans l’unité précédente.

Voici une implémentation simple de la classe de domaine Opportunities.

public class Opportunities extends fflib_SObjectDomain {
    public Opportunities(List<Opportunity> sObjectList) {
        super(sObjectList);
    }
    public class Constructor implements fflib_SObjectDomain.IConstructable {
        public fflib_SObjectDomain construct(List<SObject> sObjectList) {
            return new Opportunities(sObjectList);
        }
    }
}

Vous remarquerez que la classe interne de constructeur autorise la méthode de classe de base fflib_SObjectDomain.triggerHandler (utilisée dans l’exemple de déclencheur Apex présenté dans l’unité précédente) à créer une nouvelle instance de la logique de gestionnaire de déclencheur relative à une classe de domaine. Elle transmet également la liste sObject, qui est généralement Trigger.new.

Implémentation de la logique de retour des champs aux valeurs par défaut

Pour fournir un emplacement à la logique de retour des champs aux valeurs par défaut, la classe de base fflib_SObjectDomain expose la méthode onApplyDefaults(). Cette méthode est appelée depuis la méthode handleBeforeInsert() dans la classe de base fflib_SObjectDomain lors d’une invocation de déclencheur.

En plaçant la logique ici, vous garantissez que le retour aux valeurs par défaut est systématiquement appliqué dans toute l’application lors de l’ajout d’enregistrements. Si nécessaire, vous pouvez également l’appeler explicitement à partir d’un service permettant de présenter les valeurs d’enregistrement par défaut à un utilisateur, lorsque celui-ci accède par exemple à une interface utilisateur personnalisée via une page Visualforce ou un composant Lightning.

La classe de base expose à toutes les méthodes, via la propriété records, la liste sObject fournie lors de l’appel du constructeur. Bien que nous ne soyons pas exactement dans un scénario de déclenchement ici, nous vous recommandons fortement d’envisager un traitement en masse, conformément aux objectifs de conception de domaine présentés dans l’unité précédente.

public override void onApplyDefaults() {
    // Apply defaults to Opportunities
    for(Opportunity opportunity :(List<Opportunity>) Records) {
        if(opportunity.DiscountType__c == null) {
            opportunity.DiscountType__c = OpportunitySettings__c.getInstance().DiscountType__c;
        }               
    }
}
Remarque

Remarque : l’exemple ci-dessus est également valable pour l’utilisation d’une formule exprimée dans la définition du champ DiscountType__c. Bien entendu, la logique de retour aux valeurs par défaut peut concerner plusieurs champs ou d’autres enregistrements. Dans ce cas, vous devez utiliser du code Apex.

Implémentation de la logique de validation

Bien que vous puissiez remplacer toutes les méthodes de déclencheur ci-dessus pour implémenter la logique de validation, il est recommandé de ne le faire que pendant la phase postérieure à l’invocation du déclencheur Apex. En remplaçant l’une des deux méthodes onValidate() de la classe de base fflib_SObjectDomain, vous pouvez implémenter cette logique dans un endroit clairement défini.

public override void onValidate() {
    // Validate Opportunities
    for(Opportunity opp :(List<Opportunity>) this.records) {
        if(opp.Type.startsWith('Existing') && opp.AccountId == null) {
            opp.AccountId.addError('You must provide an Account when ' +
                'creating Opportunities for existing Customers.');
        }
    }
}

La méthode onValidate() ci-dessus est appelée à partir de la classe de base lorsque des enregistrements sont insérés dans l’objet. Si vous avez besoin d’une logique de validation sensible à la modification des données lors des mises à jour d’enregistrements, vous pouvez remplacer la variante suivante.

public override void onValidate(Map<Id,SObject> existingRecords) {
    // Validate changes to Opportunities
    for(Opportunity opp :(List<Opportunity>) Records) {
        Opportunity existingOpp = (Opportunity) existingRecords.get(opp.Id);
        if(opp.Type != existingOpp.Type) {
            opp.Type.addError('You cannot change the Opportunity type once it has been created');
        }
    }
}

Vous remarquerez que le code des méthodes de classe de base handleAfterInsert() et handleAfterUpdate() garantit l’application de cette bonne pratique de sécurité en appelant cette méthode uniquement pendant la phase postérieure du déclencheur Apex (une fois que tous les déclencheurs Apex de cet objet se sont exécutés). Ce comportement est particulièrement important pour les développeurs de packages AppExchange (voir la section Ressources).

Implémentation de la logique de déclenchement Apex

La logique de domaine que vous implémentez ne correspond pas nécessairement aux méthodes ci-dessus. En effet, d’après les consignes relatives à la séparation des préoccupations, il n’est pas strictement obligatoire d’implémenter la logique de retour aux valeurs par défaut ou de validation dans l’ensemble de ces méthodes. Il s’agit là uniquement d’une possibilité. Si vous préférez, vous pourriez la placer entièrement dans les méthodes ci-dessous.

Pour implémenter du code associé à un déclencheur Apex qui invoque des comportements via d’autres objets de domaine, voici un exemple (quelque peu artificiel, certes) du remplacement de la méthode onAfterInsert() pour mettre à jour le champ Description sur les comptes associés à chaque fois que de nouvelles opportunités sont insérées.

public override void onAfterInsert() {
    // Related Accounts
    List<Id> accountIds = new List<Id>();
    for(Opportunity opp :(List<Opportunity>) Records) {
        if(opp.AccountId!=null) {
            accountIds.add(opp.AccountId);
        }
    }
    // Update last Opportunity activity on related Accounts via the Accounts domain class
    fflib_SObjectUnitOfWork uow =
      new fflib_SObjectUnitOfWork(
        new Schema.SObjectType[] { Account.SObjectType });
    Accounts accounts = new Accounts([select Id from Account
      where id in :accountIds]);
    accounts.updateOpportunityActivity(uow);
    uow.commitWork();              
}

Voici quelques points à noter à propos de cet exemple :

  • Une instance de la classe Domaine nommée Accounts est initialisée à l’aide d’une requête SOQL en ligne. L’unité suivante de ce module présente un modèle qui aide à encapsuler la logique de requête pour assurer une meilleure réutilisation et une meilleure cohérence autour des données résultantes, un aspect important pour la logique de classe Domaine.
  • L’instance fflib_SObjectUnitOfWork est utilisée dans le contexte des déclencheurs Apex plutôt que dans un contexte de service, conformément au module de séparation des préoccupations. Dans ce cas, elle porte sur un événement ou une méthode de déclenchement. Ces méthodes sont directement appelées par la plate-forme et non par la couche de service. Ainsi, une unité de travail est créée et attribuée à la méthode Accounts afin qu’elle enregistre les mises à jour des enregistrements Account. Bien que cela ne soit pas présenté ici, il est généralement judicieux d’initialiser l’unité de travail dans un seul endroit afin d’éviter les doublons.
  • Ici, il est pertinent de procéder à une délégation à la classe de domaine Accounts car la mise à jour de l’activité fondée sur l’objet Accounts est davantage un comportement appartenant à l’objet Account qu’à Opportunity. Ce type de séparation des préoccupations entre les classes Domaine est également illustré dans la section suivante.

Pour référence, voici la méthode Accounts.updateOpportunityActivity.

public class Accounts extends fflib_SObjectDomain {
    public Accounts(List<Account> sObjectList) {
        super(sObjectList);
    }
    public void updateOpportunityActivity(fflib_SObjectUnitOfWork uow) {
        for(Account account :(List<Account>) Records) {
            account.Description = 'Last Opportunity Raised ' + System.today();
            uow.registerDirty(account);
        }
    }
}

Implémentation d’une logique personnalisée

Vous n’êtes pas obligé(e) d’implémenter exclusivement des méthodes qui peuvent être remplacées à partir de la classe de base. Rappelez-vous de la couche Service révisée présentée dans l’unité précédente.

public static void applyDiscounts(Set<Id> opportunityIds, Decimal discountPercentage) {
    // Unit of Work
    // ...   // Validate parameters
    // ...   // Construct Opportunities domain class
    Opportunities opportunities = new Opportunities( ...);
    // Apply discount via domain class behavior
    opportunities.applyDiscount(discountPercentage, uow);
    // Commit updates to opportunities     
    uow.commitWork();                      
}

Ce code s’appuie sur une méthode de classe Domaine pouvant appliquer une remise à une opportunité, ce qui encapsule davantage cette logique dans un emplacement associé à l’objet Domaine.

Lorsque cela est nécessaire, le code délègue l’application des remises au niveau des lignes à la classe de domaine OpportunityLineItems. Pour appuyer notre propos, supposons que la logique diffère pour les opportunités qui exploitent des lignes de produits.

Code utilisant une méthode de classe Domaine qui peut appliquer une remise à une opportunité.

vous pouvez consulter le code de la classe de domaine OpportunityLineItems ici.

L’instance fflib_ISObjectUnitOfWork est prise comme argument afin que l’appelant (dans ce cas précis, la méthode OpportunitiesService.applyDiscount) puisse la transmettre au code de domaine pour enregistrer le travail sur celui-ci, puis le transmettre à la méthode applyDiscount() de la classe de domaine OpportunityLineItems.

Logique métier dans la classe Domaine et dans la classe Service

Parfois, placer du code peut s’avérer tout sauf évident. Revenons au principe de séparation des préoccupations et intéressons-nous à celles relatives aux couches Service et Domaine.

Type de préoccupation d’application Service ou Domaine Exemple
S’assurer que les champs sont validés et que les valeurs par défaut leur sont appliquées de manière cohérente lors de la manipulation des données d’enregistrement.
Domaine Appliquer aux produits la politique de remise par défaut au fur et à mesure de leur ajout.
Répondre à une action utilisateur ou système impliquant de rassembler plusieurs informations ou de mettre à jour plusieurs objets. Cela consiste principalement à fournir à un groupe d’enregistrements les actions pouvant survenir et à coordonner tout ce qui est nécessaire pour mener à bien cette action (avec éventuellement d’autres services de support).
Service Créer et calculer des factures à partir d’ordres d’exécution. Susceptible de récupérer des informations du catalogue.
Gérer les modifications apportées aux enregistrements de l’application dans le cadre d’autres modifications d’enregistrement connexes ou lors de l’exécution d’une action utilisateur ou système. Cela consiste par exemple à assurer le retour aux valeurs par défaut lorsque cela est nécessaire. Si la modification d’un champ en affecte un autre, ce dernier est également mis à jour.
Domaine La manière dont réagit un objet Account lors de la création d’une opportunité ou la manière dont une remise est appliquée lors de l’exécution du processus de remise pour une opportunité. Remarque : ce type de logique peut débuter dans la couche Service, mais la gestion de la taille et de la complexité de la méthode de service ou l’amélioration de sa réutilisation peut être réalisée plus efficacement dans la couche Domaine.
Traiter un comportement commun qui s’applique à un certain nombre d’objets différents.
Domaine Calcul du prix sur les lignes de produit d’un produit d’opportunité ou d’un ordre d’exécution Remarque : vous pouvez placer cela dans une classe de base Domaine partagée, en remplaçant la méthode fflib_SObjectDomain afin de l’intégrer aux événements du déclencheur Apex, les classes Domaine concrètes étendant à leur tour cette classe avec leurs comportements.

Contrôle de l’application de la sécurité

Par défaut, la classe de base fflib_SObjectDomain applique la sécurité CRUD des objets Salesforce. Cependant, celle-ci est appelée pour tous les types d’accès à l’objet, qu’il s’effectue via un contrôleur ou un service. La logique de service peut tenter d’accéder à un objet pour le compte de l’utilisateur sans exiger d’autorisations sur l’objet.

Si vous préférez désactiver ce comportement par défaut et l’appliquer vous-même dans votre code de service, vous pouvez utiliser une des fonctionnalités de configuration de la classe de base. L’exemple suivant montre comment procéder dans chaque constructeur. Vous pouvez également créer votre propre classe de base à l’aide de ce code, puis l’étendre à toutes les classes de domaine.

public Opportunities(List<Opportunity> sObjectList) {
    super(sObjectList);        
    // Disable default Object Security checking    
    Configuration.disableTriggerCRUDSecurity();
}  
Remarque

Remarque : la classe de base n’offre pas de support générique pour l’application de la sécurité au niveau du champ lors des mises à jour. Cette application reste de votre responsabilité.

Test des classes Domaine

La factorisation de la logique en blocs plus petits et plus encapsulés est avantageuse dans le cadre du développement piloté par les tests (TDD), car vous pouvez plus facilement intégrer les classes de domaine dans vos tests et invoquer directement les méthodes. Cela ne signifie pas que vous ne devez pas tester votre couche de service, mais cela vous permet d’adopter une approche de test et de développement plus progressive.

Préparation au défi pratique

Pour relever ce défi, lancez le Trailhead Playground que vous avez utilisé dans le module Apex Enterprise Patterns : Couche Service. Vous aurez besoin des bibliothèques open source que vous avez déjà installées. Si vous utilisez un autre Trailhead Playground, lancez-le et installez d’abord la bibliothèque ApexMocks, puis la bibliothèque Apex Commons à l’aide des boutons Déployer vers Salesforce ci-dessous. Vous pouvez en apprendre plus sur ces bibliothèques et leurs contrats de licence open source respectifs dans leurs dépôts.

Déployer la bibliothèque open source ApexMocks.

Déployer vers Salesforce

Déployer la bibliothèque open source Apex Common.

Bouton Déployer vers Salesforce.

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