Comprendre les principes de l'unité de travail
Objectifs de formation
Une fois cette unité terminée, vous pourrez :
- Gérer efficacement vos opérations DML et éviter les mises à jour partielles de la base de données.
- Comprendre les fonctionnalités et les avantages de l’implémentation Apex du modèle.
- Appliquer le modèle Unité de travail à la méthode de service applyDiscount depuis l'unité précédente.
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 à 54 min 05 s, au cas où vous voudriez revenir en arrière et regarder à nouveau le début de l’étape.)
Principes de l'unité de travail
L'unité de travail est un modèle de conception qui réduit le code répétitif lors de la mise en œuvre de la gestion des transactions et les excès de code liés au respect du traitement DML en masse par une utilisation importante des cartes et des listes. Ce n'est pas indispensable à la mise en œuvre d'une couche de service, mais cela peut être une aide précieuse. Nous allons vous montrer un exemple avant/après pour expliquer le fonctionnement.
Le modèle d'unité de travail utilisé dans ce module est basé sur le modèle décrit par Martin Fowler : « Maintient une liste d’objets affectés par une transaction métier et coordonne la consignation des modifications et la résolution des problèmes de simultanéité. »
Sur Salesforce Platform, cela se traduit par le fait que le modèle gère les cas d’utilisation suivants :
- Enregistrement des mises à jour, insertions et suppressions d'enregistrements pour mettre en œuvre des exigences opérationnelles particulières
- Enregistrement des relations d'enregistrement pour faciliter l'insertion d'enregistrements enfants ou connexes avec moins de code
- En cas de demande de consignation (ou d'enregistrement) dans la base de données, traite en gros tous les enregistrements capturés
- Enveloppe le DML exécuté dans SavePoint, évite au développeur de devoir l’implémenter chaque fois qu'une méthode de services est écrite
Mise en œuvre d'une méthode de service sans unité de travail
Afin de mieux comprendre ce que le modèle d’unité de travail a à vous offrir, commençons par examiner le code dont nous avons besoin d’écrire sans utiliser l’unité de travail dans chaque méthode de service, tout en continuant à adhérer aux meilleures pratiques de conception que nous avons vues plus haut. Nous devons écrire le code pour une exigence opérationnelle particulière, mais il doit également servir de code standard pour mettre en œuvre les éléments suivants :
-
Optimisation et traitement en masse de DML : le code peut mettre à jour tout ou partie des enregistrements d'opportunité ou OpportunityLineItem en fonction du flux de logique. Il crée et renseigne de liste afin de maintenir uniquement les enregistrements lus qui doivent être mis à jour.
-
Gestion des erreurs et des transactions : conformément aux principes de conception de la couche Service, il doit valider toutes les modifications ou aucune en cas d'erreur, que l'appelant reçoive ou non les exceptions émises. N’oubliez pas que la plate-forme revient automatiquement en arrière uniquement si des exceptions ne sont pas gérées, ce qui n’est pas souhaitable du point de vue d’une exception utilisateur. La bonne pratique pour le code de la couche Service recommande de gérer l'ampleur d'une transaction à l'aide du service SavePoint et de la sémantique try/catch.
L’exemple suivant utilise un SavePoint
pour encapsuler et emballer les opérations de la base de données dans une méthode de service, conformément aux considérations de conception. Pourquoi ? Imaginez un scénario dans lequel la deuxième opération DML échoue. Voici ce qui arrive s’il n’y a pas de SavePoint
dans la méthode :
- Si l’appelant ne gère pas l’exception, l’ensemble de la transaction, y compris la première opération DML, sera annulé, car il s’agit du comportement transactionnel par défaut d’Apex.
- Si l’appelant intercepte l’exception, sans lui permettre de se propager ou sans restaurer un SavePoint, l’exécution d’Apex validera les mises à jour des lignes d’opportunité (première instruction DML), provoquant une mise à jour partielle de la base de données.
Lorsque vous n’utilisez pas le modèle d’unité de travail, il est recommandé de gérer les opérations DML multiples de la même manière que dans cet exemple. Cependant, comme vous le verrez dans les sections suivantes, l’unité de travail peut gérer cette étape pour vous.
public static void applyDiscounts(Set<Id> opportunityIds, Decimal discountPercentage) { // Validate parameters // ... // Query Opportunities and Lines // ... // Update Opportunities and Lines (if present) List<Opportunity> oppsToUpdate = new List<Opportunity>(); List<OpportunityLineItem> oppLinesToUpdate = new List<OpportunityLineItem>(); // Do some work... Decimal factor = 1 - (discountPercentage==null ? 0 : discountPercentage / 100); for(Opportunity opportunity : opportunities) { // Apply to Opportunity Amount if(opportunity.OpportunityLineItems!=null && opportunity.OpportunityLineItems.size()>0) { for(OpportunityLineItem oppLineItem : opportunity.OpportunityLineItems) { oppLineItem.UnitPrice = oppLineItem.UnitPrice * factor; oppLinesToUpdate.add(oppLineItem); } } else { opportunity.Amount = opportunity.Amount * factor; oppsToUpdate.add(opportunity); } } // Update the database SavePoint sp = Database.setSavePoint(); try { update oppLinesToUpdate; update oppsToUpdate; } catch (Exception e) { // Rollback Database.rollback(sp); // Throw exception on to caller throw e; } }
Mise en œuvre d'Apex dans le modèle d'unité de travail
Le reste de cette unité se rapporte à une bibliothèque Apex open source qui contient une implémentation du modèle d'unité de travail de Martin Fowler. Elle est implémentée via une classe unique, fflib_SObjectUnitOfWork, et vous pouvez donc l'ouvrir dans un autre onglet. Dans l'unité suivante, nous allons aborder cette classe, mais pour le moment, contentons-nous de comprendre un peu mieux certaines de ses méthodes clés.
Cette classe dévoile les méthodes qui permettent à une instance de la classe fflib_SObjectUnitOfWork
de capturer des enregistrements qui doivent être créés, mis à jour ou supprimés lorsque le code de service est exécuté via des méthodes d’enregistrement. En outre, la méthode commitWork encapsule le SavePoint et la convention try/catch.
La mise à jour de la base de données avec DML ne se produit que lorsque la méthode commitWork est appelée. Par conséquent, le code de service peut appeler les méthodes d'inscription aussi souvent et aussi fréquemment que nécessaire, même dans des boucles. Cette approche permet au développeur de se concentrer sur la logique métier et non sur le code pour gérer plusieurs listes et cartes.
Enfin, comme illustré sur le diagramme suivant, la portée de l'unité de travail est déterminée par le début et la fin de votre code de méthode de service. Appelez la méthode commitWork lorsque vous êtes dans le cadre de la méthode de service.
Pour inclure l'unité de travail dans vos méthodes de code de service, suivez ces étapes.
- Initialisez une unité de travail unique et utilisez-la pour examiner tout le travail effectué par la méthode de service.
- Faites en sorte que l'inscription de la logique de la couche de service soit enregistrée avec l'unité de travail lors de son exécution.
- Appelez la méthode commitWork de l'unité de travail pour traiter en masse et exécuter le DML.
Le diagramme suivant illustre les étapes ci-dessus et applique l'étendue de chaque étape en ce qui concerne l'exécution du code de la méthode de service.
Pour utiliser la classe d'unité de travail, vous devez la construire avec une liste des objets avec lesquels votre code interagit. Les objets doivent être en ordre de dépendance pour assurer que les enregistrements parents et enfants sont insérés par la méthode commitWork dans le bon ordre. Nous allons explorer un peu mieux la gestion de la relation parent-enfant fflib_SObjectUnitWork dans l’unité suivante.
fflib_SObjectUnitOfWork uow = new fflib_SObjectUnitOfWork( new List<SObjectType> { OpportunityLineItem.SObjectType, Opportunity.SObjectType } );
Mise en œuvre d'une méthode de service avec l'unité de travail
L'exemple suivant applique le modèle d'unité de travail au service que nous avons créé à l'unité précédente. Le code qui n'a pas changé n'est pas indiqué. Veuillez noter que les listes ont disparu et que le SavePoint n’est plus entouré par du code standard try/catch car il est entièrement géré par la classe fflib_SObjectUnitOfWork
.
public static void applyDiscounts(Set<Id> opportunityIds, Decimal discountPercentage) { // Unit of Work fflib_SObjectUnitOfWork uow = new fflib_SObjectUnitOfWork( new List<SObjectType> { OpportunityLineItem.SObjectType, Opportunity.SObjectType } ); // Validate parameters // ... // Query Opportunities and Lines // ... // Update Opportunities and Lines (if present) // ... for(Opportunity opportunity : opportunities) { // Apply to Opportunity Amount if(opportunity.OpportunityLineItems!=null && opportunity.OpportunityLineItems.size()>0) { for(OpportunityLineItem oppLineItem : opportunity.OpportunityLineItems) { oppLineItem.UnitPrice = oppLineItem.UnitPrice * factor; uow.registerDirty(oppLineItem); } } else { opportunity.Amount = opportunity.Amount * factor; uow.registerDirty(opportunity); } } // Commit Unit of Work uow.commitWork(); }
La classe fflib_SObjectUnitOfWork agrège les opérations DML et les englobe dans un SavePoint lorsque la méthode commitWork est appelée.
Dans un code plus complexe, avec plusieurs profondeurs et classes, vous pouvez choisir de transmettre SObjectUnitOfWork (ou d'en utiliser une statique). Le code appelé peut continuer à enregistrer ses propres mises à jour de base de données, en sachant que le propriétaire de l'unité de travail, dans ce cas la couche de service, n'exécute qu'une seule phase de validation ou de retour en arrière en son nom.
Ressources
- Apex Enterprise Patterns - Dépôt GitHub
- Séparation des préoccupations (Wikipédia)
- Modèles d’architecture d’entreprise de Martin Fowler
- Modèles d’unités de travail de Martin Fowler
- Gestion de votre DML et de vos transactions avec une unité de travail
- En faire plus avec l’unité de travail