Anwenden der Prinzipien der Service-Schicht in Apex
Lernziele
Nachdem Sie diese Lektion abgeschlossen haben, sind Sie in der Lage, die folgenden Aufgaben auszuführen:
- Erstellen einer Service-Apex-Klasse und effektives Nutzen dieser Klasse in ihrer Anwendung
- Verfügbarmachen einer Service-Apex-Klasse als API
Mit Trail Together einem Dozenten folgen
Möchten Sie bei diesem Schritt einem Experten folgen? Schauen Sie sich dieses Video an, das Teil der Reihe "Trail Together" ist.
(Dieser Clip beginnt bei Minute 38:48, für den Fall, dass Sie zurückspringen und den Anfang des Schritts noch einmal sehen möchten.)
Erstellen von Services
Tauchen wir ein in den Code! Wenn Sie eine vollständige Kapselung der Datenbank- und Zustandsverwaltung verwenden, besteht ein Implementierungsansatz darin, eine passend benannte Klasse mit statischen Methoden zu verwenden, die die Vorgänge des Service darstellen.
Die Methoden in Ihrer Klasse stellen die Service-Vorgänge dar, die über die Umgebung und die übergebenen Parameter auf die benötigten Informationen zugreifen. Die Logik in der Methode aktualisiert die Datenbank oder gibt Informationen im Rückgabetyp der Methode zurück, wobei sie benutzerdefinierte Apex-Ausnahmen verwendet, um Fehler zu melden. Das folgende Beispiel zeigt einen Service, der dazu dient, einen bestimmten Rabatt auf eine Gruppe von Opportunities (und Belegposten, sofern vorhanden) anzuwenden.
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 {} }
Wenn wir die zuvor beschriebenen Überlegungen zum Konfigurationsdesign anwenden, können wir eine überladene Version des obigen Service mit einem Parameter "Options" hinzufügen, mit dem der Aufrufer den Service anweisen kann, die Festschreibung der Vorgänge zu überspringen. Die Rückgabe der Rabattwerte ermöglicht es dem Client, eine Vorschau der Rabatte zu implementieren, die angewandt würden.
public static List<Decimal> applyDiscounts( List<Id> opportunityIds, Decimal discountPercentage, Options config)
Die Methodensignatur im obigen, ungekürzten Beispiel akzeptiert eine Liste von IDs, was ebenfalls den Designüberlegungen entspricht. Es wurde jedoch nur ein einzelner Parameter für den Rabatt verwendet. Dabei wird davon ausgegangen, dass für alle Opportunities der gleiche Rabatt gilt. Wenn jedoch unterschiedliche Rabatte pro Opportunity gewährt werden sollen, können Sie eine Parameterklasse verwenden, wie nachfolgend gezeigt.
public class OpportunityService { public class ApplyDiscountInfo { public Id OpportunityId; public Decimal DiscountPercentage; } public static void applyDiscounts(List<ApplyDiscountInfo> discInfos) { // Implementation... } }
Offenlegen von Services als APIs
Alle lieben APIs! Das Offenlegen Ihrer Service-Logik für externe Parteien durch eine API in Ihre Anwendung ist ein Muss, um ein starkes Ökosystem von Innovationen und Partner-Integrationen rund um Ihre Produkte zu entwickeln.
Wenn Ihre Service-Schicht Ihrer Meinung nach vollständig getestet, robust und für jeden Client offen ist (und warum sollte sie dies nicht sein – Sie nutzen sie ja schließlich auch), dann ist der einfachste Weg, sie den Apex-Entwicklern zugänglich zu machen, die Klasse und Methodenmodifikatoren von public
in global
zu ändern. Tusch!
global class OpportunityService { global class ApplyDiscountInfo { global Id OpportunityId; global Decimal DiscountPercentage; } global static void applyDiscounts(List<ApplyDiscountInfo> discInfos) { // Implementation... } }
Das ist allerdings nicht so leicht, wie es sich anhört. Wenn Sie ein AppExchange-Paket erstellen, hat die Verwendung von global bestimmte Auswirkungen beim Ändern von Methodensignaturen zwischen Versionen. Stellen Sie sicher, dass Ihnen diese Auswirkungen klar sind.
Es ist auch eine Überlegung wert, Ihre API für Aufrufer außerhalb der Plattform, wie z. B. Mobilgeräte oder das IoT, zur Verfügung zu stellen. Das eignet sich das REST-Protokoll gut. Hier sehen Sie eine benutzerdefinierte Implementierung einer Apex-REST-API.
@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); } }
Die ID der Opportunity wird aus dem URI ermittelt, und der Rabattprozentsatz wird aus den geposteten Informationen übernommen. Wie beim Beispiel zu JavaScript Remoting in der vorherigen Einheit wird die Ausnahmebehandlung dem Aufrufer überlassen. Die Plattform ordnet Ausnahmen der entsprechenden JSON- oder XML-Antwort zu.
Expertentipp: Sie könnten einige aufrufbare Methoden verfügbar machen, damit Benutzer von Flow Builder auf die Funktionalität Ihrer Service-Schicht zugreifen können, ohne Code zu schreiben. Sind Plattformen nicht einfach überwältigend!
Ressourcen
- Separation of Concerns (Wikipedia)
- Martin Fowler’s Service Layer Pattern
- "Enterprise Architecture Patterns" von Martin Fowler