Skip to main content

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.
Remarque

Remarque

Vous souhaitez apprendre en français ? Commencez le défi dans un Trailhead Playground en français et utilisez les traductions fournies entre crochets pour naviguer. Copiez et collez uniquement les valeurs en anglais, car les validations de défi reposent sur les données en anglais. Si vous ne réussissez pas le défi dans votre organisation en français, nous vous recommandons (1) de définir le paramètre régional sur les États-Unis, (2) de définir la langue sur l’anglais en suivant les instructions ici, puis (3) de cliquer à nouveau sur le bouton « Vérifier le défi ».

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 à 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

Partagez vos commentaires sur Trailhead dans l'aide Salesforce.

Nous aimerions connaître votre expérience avec Trailhead. Vous pouvez désormais accéder au nouveau formulaire de commentaires à tout moment depuis le site d'aide Salesforce.

En savoir plus Continuer à partager vos commentaires