Prinzipien der Service-Schicht
Lernziele
Nachdem Sie diese Lektion abgeschlossen haben, sind Sie in der Lage, die folgenden Aufgaben auszuführen:
- Erklären der Ursprünge des Service-Musters aus Martin Fowlers Buch Patterns of Enterprise Application Architecture
- Bestimmen, welcher Apex-Code in die Service-Schicht gehört
- Erläutern, wie die Service-Schicht in Ihre Anwendungsarchitektur und die Plattform passt
- Entwickeln einer Service-Ebene, die den bewährten Vorgehensweisen der Plattform entspricht
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 17:45, für den Fall, dass Sie zurückspringen und den Anfang des Schritts noch einmal sehen möchten.)
Einführung
In der vorhergehenden Einheit wurde SOC als Mittel vorgestellt, Softwarearchitekten dazu zu bringen, verstärkt über die Schichtenbildung bei Anwendungslogik nachzudenken. Diese Einheit legt den Schwerpunkt auf die Definition und Nutzung der Service-Schicht als zentralen Einstiegspunkt für andere Schichten und Verbraucher (z. B. einer API) Ihrer Anwendung.
Die Service-Schicht "defines an application's boundary with a layer of services that establishes a set of available operations and coordinates the application's response in each operation" (definiert die Grenze einer Anwendung mit einer Schicht aus Services, die eine Reihe verfügbarer Vorgänge festlegt und die Antwort der Anwendung bei jedem Vorgang koordiniert). Martin Fowler / Randy Stafford, EAA Patterns
Die Service-Schicht hilft Ihnen, eine klare und strikte Kapselung von Code zu bilden, der Geschäftsaufgaben, Berechnungen und Prozesse implementiert. Es ist wichtig sicherzustellen, dass die Service-Schicht für die Verwendung in verschiedenen Kontexten, wie z. B. mobilen Anwendungen, Formularen der Benutzeroberfläche, Rich Web-Benutzeroberflächen und zahlreichen APIs bereit ist. Sie muss rein und abstrakt bleiben, um dem Wandel der Zeit sowie geänderten Anforderungen Stand zu halten. In den folgenden Abschnitten werden die Richtlinien für die Erstellung einer Implementierung der Service-Schicht in Apex definiert, wobei die bewährten Vorgehensweisen und Obergrenzen von Salesforce berücksichtigt werden.
Wer nutzt die Service-Schicht?
Man möchte fast sagen, dass "alle coolen Kids" eine Service-Schicht verwenden, doch technisch gesehen wird der Verbraucher einer Service-Schicht als "Client" bezeichnet. Ein Client ruft den Code Ihrer Service-Schicht auf. Dabei handelt es sich nicht um menschliche Interaktionen mit der Service-Schicht, sondern um andere Codeteile, die mit dem Benutzer oder System interagieren, wie z. B. ein UI-Steuerfeld oder Batch-Apex.
Ein Beispiel für einen Client ist Code, der in einem Visualforce-Steuerfeld oder einer @AuraEnabled
-Methode geschrieben wurde. Es gibt jedoch noch viele andere Clients (Verbraucher) für den Code Ihrer Service-Schicht, die Sie berücksichtigen sollten. Wenn Sie eine Liste der Kandidaten erstellen möchten, denken Sie an alle die Möglichkeiten, wie Apex-Logik in der Salesforce-Plattform aufgerufen werden kann.
Wie Sie sich vorstellen können, kann es nur allzu leicht passieren, dass Logik aus der Service-Schicht in den für andere Schichten und Zwecke erstellten Apex-Code gerät. Solche Lecks untergraben den Nutzen der Implementierung einer Service-Schicht, da sie zu Inkonsistenzen bei der Filterung gegenüber dem Endbenutzer. Wenn Benutzer beispielsweise mit Ihrer Anwendung über eine bestimmte Funktion interagieren, die über verschiedene Salesforce-Technologien zugänglich ist. Zum Beispiel eine bestimmte Berechnung, die sowohl über eine von Ihnen erstellte Lightning-Komponente als auch über einen Apex-REST-Service bereitgestellt wird. Das Verhalten muss in beiden Fällen einheitlich sein. Im nächsten Abschnitt behandeln wir das Design und die Verantwortlichkeiten der Service-Schicht und die Erwartungen, die der Code, der die Service-Schicht nutzt, haben sollte.
Plattforminnovation und -anpassbarkeit
Die oben genannten Technologien wurden im Laufe der Jahre sukzessive als neue Funktionen der Plattform eingeführt. Stellen Sie sich vor, Sie hätten den Code so geschrieben, dass er an eine bestimmte Funktion gebunden ist, und Sie müssten ihn jedes Mal überarbeiten. Stellen Sie sich nun vor, wie viel einfacher es wäre, Ihre Anwendung an diese und kommende Funktionen anzupassen, wenn Sie sich nicht erst um die Refakturierung Ihres Codes aus einem der vorherigen Bereiche kümmern müssten. Noch schlimmer wäre es, wenn Sie den Code duplizieren müssten, da Sie befürchten, die Refakturierung bestehende Funktionalität zerstört. Keine schöne Vorstellung, oder?
Designüberlegungen
-
Benennungskonventionen: Die Service-Schicht muss abstrakt genug sein, um von einer Reihe von Clients genutzt werden zu können. Diesen Aspekt decken oftmals die Verben und Substantive ab, die Sie in Klassen-, Methoden- und Parameternamen verwenden. Stellen Sie sicher, dass sie in allgemeinen Begriffen der Anwendung oder Aufgabe ausgedrückt werden und sich nicht auf einen bestimmten Client-Aufrufer beziehen. Dieser Methodenname basiert beispielsweise auf dem Geschäftsvorgang
InvoiceService.calculateTax(...)
, während dieser Methodenname auf einem spezifischen Client-Nutzungsvorgang beruht:InvoiceService.handleTaxCodeForACME(...)
. Der zweite Methodenname sollte Ihnen ein wenig Unbehagen bereiten.
-
Zusammenspiel zwischen Plattform und Aufrufer: Bilden Sie Methodensignaturen, die die bewährten Vorgehensweisen der Plattform unterstützen, und zwar ganz besonders die Massenverarbeitung. Eines der Hauptanliegen bei sämtlichem Code in Salesforce ist die Massenverarbeitung. Stellen Sie sich Services vor, die mit Listen anstelle einzelner Parametergruppen aufgerufen werden können. Die Parameter dieser Methode ermöglichen beispielsweise die Massenverarbeitung
InvoiceService.calculateTax(List<TaxCalculation> taxCalculations)
, während diese Methode den Aufrufer zwingt, die Methode wiederholt aufzurufenInvoiceService.calculateTax(Invoice invoice, TaxInfo taxCodeInfo)
. Auch hier gilt wird, dass Ihnen die zweite Option weniger gut gefallen sollte.
-
SOC-Überlegungen: Der Code der Service-Schicht kapselt Aufgaben- oder Prozesslogik, die normalerweise mehrere Objekte in Ihrer Anwendung verwendet. Stellen Sie sich dies als Orchestrator vor. Dagegen liegt Code, der sich speziell auf die Validierung, Feldwerte oder Berechnungen bezieht, die beim Einfügen, Aktualisieren und Löschen von Datensätzen auftreten, in der Verantwortung des zugehörigen Objekts. Dieser Code wird meist in Apex-Auslöser geschrieben und kann dort bleiben. Keine Sorge, wir stellen Ihnen in Kürze das Domänenmuster für diese Art von Code vor.
-
Sicherheit:: Der Code der Service-Schicht und der davon aufgerufene Code sollten standardmäßig unter Anwendung von Benutzersicherheit ausgeführt werden. Um sicherzugehen, dass dies der Fall ist, verwenden Sie den Modifikator
with sharing
für Ihre Apex Service-Klassen (besonders wichtig, wenn Sie solchen Code über den globalen Modifikator bereitstellen). Wenn Apex-Logik auf Datensätze außerhalb der Sichtbarkeit des Benutzers zugreifen muss, muss der Code den Ausführungskontext so kurz wie möglich erhöhen. Ein guter Ansatz ist die Verwendung einer privaten Apex Inner Class mit dem angewandtem Modifikatorwithout sharing
.
-
Marshalling: Vermeiden Sie es, vorzuschreiben, wie Aspekte der Interaktion mit der Service-Schicht behandelt werden, da bestimmte Aspekte besser den Aufrufern Ihres Service überlassen werden sollten, z. B. Semantik wie Fehlerbehandlung und Messaging. Anrufer haben oftmals eigene Mittel, diese zu interpretieren und zu verarbeiten. Visualforce, zum Beispiel, verwendet
<apex:pagemessages>
und Schedule-Aufträge verwenden höchstwahrscheinlich E-Mails, Chatter-Posts oder Protokolle zum Kommunizieren von Fehlern. In diesem Fall ist es also meist am besten, die standardmäßige Fehlerbehandlungs-Semantik von Apex zu nutzen und Ausnahmen auszulösen. Alternativ kann Ihr Service dem Anrufer eine teilweise Rückmeldung zum Datenbank-Update geben. In diesem Fall erstellen Sie eine entsprechende Apex-Klasse und geben eine Liste dieses Typs zurück.. Die SystemmethodeDatabase.insert
ist ein gutes Beispiel für diese Art von Methodensignatur.
-
Zusammengesetzte Services: Clients können zwar mehrere Service-Aufrufe nacheinander ausführen, dies kann jedoch ineffizient sein und zu Problemen bei Datenbanktransaktionen führen. Es ist besser, zusammengesetzte Services zu erstellen, die intern mehrere Service-Aufrufe in einem Service-Aufruf zusammenfassen. Wichtig ist auch, dass die Service-Schicht hinsichtlich der SOQL- und DML-Nutzung so optimiert wie möglich ist. Dies bedeutet nicht, dass granularere Services nicht bereitgestellt werden können; es bedeutet lediglich, dass Sie den Aufrufern die Möglichkeit geben sollten, bei Bedarf einen spezifischeren einzelnen Service zu verwenden.
-
Transaktionsmanagement und Zustandslosigkeit: Clients der Service-Schicht haben oft unterschiedliche Anforderungen an die Langlebigkeit des auszuführenden Prozesses und die zu verwaltenden Informationen. Zum Beispiel eine einzelne Anforderung an den Server und mehrere Anforderungen, die in separate Bereiche aufgeteilt werden: den Verwaltungszustand (wie etwa Batch-Apex) oder eine komplexe Benutzeroberfläche, die ihren eigenen Seitenzustand über mehrere Anforderungen hinweg pflegt. Angesichts dieser Unterschiede in der Zustandsverwaltung ist es am besten, Datenbankoperationen und Service-Zustand innerhalb des Methodenaufrufs an die Service-Schicht zu kapseln. Mit anderen Worten, machen Sie den Service zustandslos, um Aufrufkontexten die Flexibilität zu geben, ihre eigenen Zustandsverwaltungslösungen einzusetzen. Der Umfang einer Transaktion mit der Datenbank sollte auch in jeder Servicemethode enthalten sein, damit der Aufrufer dies nicht beispielsweise mit seinen eigenen
SavePoints
berücksichtigen muss.
-
Konfiguration: Möglicherweise haben Sie allgemeine Konfigurations- oder Verhaltensänderungen in einer Service-Schicht, wie z. B. die Übergabe der Kontrolle, damit der Client die Server-Schicht anweisen kann, Änderungen nicht festzuschreiben oder keine E-Mails zu versenden. Dieses Szenario kann nützlich sein, wenn der Client eine Vorschau- oder What-if-Funktionalität implementiert. Überlegen Sie gut, wie Sie dies konsistent implementieren: vielleicht als Methodenüberladung, die einen gemeinsamen Optionsparameter erfordert, ähnlich wie die DML-Methoden in Apex.
Verwenden von Services in Apex
Sehen wir uns an, wie Sie die Methode OpportunitiesService.applyDiscounts
von verschiedenen Stellen aus einsetzen können. Lightning-Komponenten und Batch-Apex werden alle nachfolgend aufgeführt.
Das folgende Beispiel dreht sich um eine einzelne Opportunity, die über eine Lightning-Komponente ausgewählt wird. Stellen Sie sich vor, Sie hätten eine Lightning-Komponente, die den Benutzer auffordert, einen Rabattprozentsatz für einen ausgewählten Opportunity-Betrag einzugeben. Beachten Sie, dass die Fehlerbehandlung in dieser Phase und nicht im Service erfolgt, da Lightning-Komponenten ihre eigene Art der Fehlerbehebung haben.
@AuraEnabled public void applyDiscount(Id opportunityId, Decimal discountPercentage) { try { // Apply discount entered to the current Opportunity OpportunitiesService.applyDiscounts( new Set<ID> { opportunityId }, discountPercentage); } catch(Exception e) { throw new AuraHandledException('Something went wrong: ' + e.getMessage()); } }
Das nachfolgende Beispiel zeigt die Verarbeitung von Datensatzblöcken mittels der execute-Methode von Batch-Apex. Wenn Sie genau hinsehen, werden Sie feststellen, dass sich die Ausnahmebehandlung vom vorherigen Beispiel mit Lightning-Komponente unterscheidet.
public with sharing class OpportunityApplyDiscountJob implements Database.Batchable<SObject> { public Decimal DiscountPercentage {get;private set;} public OpportunityApplyDiscountJob(Decimal discountPercentage) { // Discount to apply in this job this.DiscountPercentage = discountPercentage; } public Database.QueryLocator start(Database.BatchableContext ctx) { // Opportunities to discount return Database.getQueryLocator( 'SELECT Id FROM Opportunity WHERE StageName = \'Negotiation/Review\''); } public void execute(Database.BatchableContext BC, List<sObject> scope) { try { // Call the service OpportunitiesService.applyDiscounts( new Map<Id,SObject>(scope).keySet(),DiscountPercentage); } catch (Exception e) { // Email error, log error, chatter error etc.. } } public void finish(Database.BatchableContext ctx) { } }
In einer späteren Einheit befassen wir uns damit, wie man eine Service-Methode über eine REST-API zugänglich macht.
Weitere Vorteile und Überlegungen zur Apex-Service-Schicht
Etwas außerhalb des Themenbereichs dieser Einheit liegt die Implementierung von Services für simulierte Tests und die Parallelentwicklung. Services können das Factory-Muster zusammen mit Apex-Oberflächen verwenden, um die Implementierung dynamisch aufzulösen, anstatt sie direkt in die Methoden zu codieren. Dieser Ansatz ist nützlich, um den Umfang der Tests von Services flexibler zu gestalten. Allerdings benötigen Factories einige Strukturen und ein Framework, um Oberflächen, Möglichkeiten zur Registrierung von Klassen und andere lustige Dinge in Ihrem Code zu erstellen. Sie können sicher sein, dass ihr Einsatz einen Mehrwert in Bezug auf Simulationen und Laufzeitflexibilität auf Basis von Konfigurationen bietet.
Wird das Service-Schichtdesigns im Vorfeld definiert, ermöglicht dies zudem Entwicklern oder Entwicklerteams, besser zusammen oder parallel zu arbeiten. Diejenigen, die die Services aufrufen müssen, können Dummy-Implementierungen verwenden, um statische Daten zurückzugeben, während diejenigen, die die Services implementieren, an Code arbeiten können, ohne die Aufrufer zu beeinträchtigen. Diese Art der Entwicklung wird oft als Design by Contract (Dbc) bezeichnet und ist wirklich eine Riesengeschichte.
Übersicht
Die Investition in eine Service-Schicht für Ihre Anwendung bietet die technischen Vorteile der größeren Wiederverwendbarkeit und Anpassbarkeit sowie eine sauberere und kostengünstigere Implementierung einer API für Ihre Anwendung, ein Muss in der heutigen Cloud-integrierten Welt. Indem Sie sich genau an die oben beschriebenen Überlegungen zu Kapselung und Design halten, bilden Sie einen dauerhaften Kern für Ihre Anwendung, der in unserer schnelllebigen, innovativen Zeit eine solide Investition bleiben wird!
Ressourcen
- Separation of Concerns (Wikipedia)
- Martin Fowler’s Service Layer Pattern
- "Enterprise Architecture Patterns" von Martin Fowler