Appliquer les principes de la couche Service dans Apex
Objectifs de formation
Une fois cette unité terminée, vous pourrez :
- Créer une classe Apex Service et l'utiliser efficacement dans votre application.
- Exposer une classe Apex Service en tant qu'API.
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 à 38 min 48 s, au cas où vous voudriez revenir en arrière et regarder à nouveau le début de l’étape.)
Création de services
Passons à un peu de code ! Si vous utilisez une encapsulation complète de gestion d'état et de base de données, une approche de mise en œuvre consiste à utiliser une classe correctement dénommée avec des méthodes statiques qui représentent les opérations du service.
Les méthodes figurant dans votre classe représentent les opérations de service qui accèdent aux informations dont elles ont besoin via l'environnement et les paramètres transmis. La logique de la méthode met à jour la base de données ou renvoie les informations du type de retour de la méthode à l'aide des exceptions personnalisées d'Apex pour signifier un échec. L'exemple suivant montre un service permettant d’appliquer une remise donnée à un ensemble d'opportunités (et des éléments de ligne, s'il en existe).
public with sharing class OpportunitiesService { public static void applyDiscounts(Set<Id> opportunityIds, Decimal discountPercentage) { // Validate parameters if(opportunityIds==null || opportunityIds.size()==0) throw new OpportunityServiceException('Opportunities not specified.'); if(discountPercentage<0 || discountPercentage>100) throw new OpportunityServiceException('Invalid discount to apply.'); // Query Opportunities and Lines (SOQL inlined for this example, see Selector pattern in later module) List<Opportunity> opportunities = [SELECT Amount, (SELECT UnitPrice FROM OpportunityLineItems) FROM Opportunity WHERE Id IN :opportunityIds]; // Update Opportunities and Lines (if present) List<Opportunity> oppsToUpdate = new List<Opportunity>(); List<OpportunityLineItem> oppLinesToUpdate = new List<OpportunityLineItem>(); 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; } } public class OpportunityServiceException extends Exception {} }
Si nous appliquons le principe de conception de configuration décrit plus haut, nous pouvons ajouter une version surchargée du service susmentionné avec un paramètre Options, qui permet à l'appelant d'ordonner au service d'ignorer l’exécution de la tâche. Le renvoi des valeurs réduites permet au client de mettre en œuvre un aperçu des remises qui seraient appliquées.
public static List<Decimal> applyDiscounts( List<Id> opportunityIds, Decimal discountPercentage, Options config)
La signature de la méthode dans l'exemple complet ci-dessus prend une liste d'ID, ce qui est également conforme aux principes de conception. Toutefois, un seul paramètre a été utilisé pour la remise. On suppose donc que la même remise est appliquée à toutes les opportunités. Toutefois, s'il est nécessaire d'autoriser différentes remises par opportunité, vous pouvez utiliser une classe de paramètre, comme illustré ici.
public class OpportunityService { public class ApplyDiscountInfo { public Id OpportunityId; public Decimal DiscountPercentage; } public static void applyDiscounts(List<ApplyDiscountInfo> discInfos) { // Implementation... } }
Exposition de services en tant qu'API
Tout le monde aime les API ! L'exposition de la logique de votre service à des parties externes via une API dans votre application est indispensable pour développer un écosystème fort d'innovation et d'intégration des partenaires à vos produits.
Si vous considérez votre couche de services comme entièrement testée, robuste et ouverte à tous les clients (et pourquoi ne serait-ce pas le cas, puisque vous l’utilisez aussi ?), la manière la plus simple de la présenter aux développeurs Apex consiste alors à modifier la classe et les modificateurs de méthode pour passer de public
à global
. Terminé !
global class OpportunityService { global class ApplyDiscountInfo { global Id OpportunityId; global Decimal DiscountPercentage; } global static void applyDiscounts(List<ApplyDiscountInfo> discInfos) { // Implementation... } }
Toutefois, dans la vie, rien n'est aussi simple qu'il paraît. Si vous créez un package AppExchange, l'utilisation de global a des implications lors du changement des signatures des méthodes entre les versions. Veillez à bien comprendre ces implications.
Il est également utile d’envisager d’exposer votre API pour des appelants hors plate-forme, par exemple mobiles ou IoT. Une manière d'y parvenir consiste à utiliser le protocole REST. Voici une implémentation d'une API REST Apex personnalisée.
@RestResource(urlMapping='/opportunity/*/applydiscount') global with sharing class OpportunityApplyDiscountResource { @HttpPost global static void applyDiscount(Decimal discountPercentage) { // Parse context RestRequest req = RestContext.request; String[] uriParts = req.requestURI.split('/'); Id opportunityId = uriParts[2]; // Call the service OpportunitiesService.applyDiscounts( new Set<Id> { opportunityId }, discountPercentage); } }
L'ID de l'opportunité est prélevée dans l'URI, et le pourcentage de remise dans les informations publiées. Comme dans l'exemple JavaScript Remoting de l'unité précédente, la gestion des exceptions est laissée à l'appelant. La plate-forme gère les exceptions sous la forme de la réponse JSON ou XML appropriée.
Conseil professionnel : Pensez à exposer certaines méthodes invocables afin que les utilisateurs de Flow Builder puissent accéder aux fonctionnalités de votre couche de service sans écrire de code. Ces plate-formes sont vraiment géniales.
Ressources
- Séparation des préoccupations (Wikipédia)
- Modèle de couche de service de Martin Fowler
- Modèles d’architecture d’entreprise de Martin Fowler