Skip to main content

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 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 Force.com 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 innerhalb einer Visualforce- oder Lightning-Steuerfeldklasse 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 Force.com-Plattform aufgerufen werden kann.

Möglichkeiten, wie Apex-Logik in der Force.com-Plattform aufgerufen werden kann: Apex UI-Steuerfelder, Apex-Webservices, Apex-REST-Services, aufrufbare Methoden, Handler für eingehende E-Mails, Batch-Apex, geplantes Apex und Queueable Apex.

Hinweis

Hinweis

Apex-Auslöser fehlen in der Liste, weil die Logik zur Domänenschicht Ihrer Anwendung gehört, die eng an den Objekten und damit der Bearbeitung von Datensätzen in Ihrer Anwendung ausgerichtet ist. Die Domänenlogik wird sowohl direkt als auch indirekt innerhalb der Service-Schicht und natürlich über die Plattform-Benutzeroberfläche und APIs aufgerufen.

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 Force.com-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 dem spezifischen Client-Nutzungsvorgang InvoiceService.handleTaxCodeForACME(...) beruht. 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 Force.com 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 aufzurufen InvoiceService.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 Modifikator without 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 Systemmethode Database.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.
Hinweis

Hinweis

In Apex werden Datenbanktransaktionen automatisch festgeschrieben, wenn die Anforderung ohne Fehler abgeschlossen wird, und im Falle einer unbehandelten Ausnahme rückgängig gemacht. Allerdings ist es nicht erwünscht, eine Anforderung mit Fehlern zu beenden, da die Plattformbehandlung dieser Ausnahmen für den Endbenutzer oft nicht gut zugänglich (Batch-Apex-Aufträge) oder ästhetisch ansprechend (weiße Seite, schwarzer Text) ist. Aus diesem Grund fangen Entwickler Ausnahmen häufig ab und leiten sie entsprechend weiter. Ein möglicher Nebeneffekt bei diesem Ansatz ist, dass die Plattform dies als gültigen Abschluss der Anforderung ansieht und Datensätze, die eingefügt oder aktualisiert wurden, bis zum Auftreten des Fehlers festschreibt. Indem Sie die oben genannten Designprinzipien für die Service-Schicht bezüglich Zustandslosigkeit und Transaktionsmanagement befolgen, können Sie dieses Problem vermeiden.

Verwenden von Services in Apex

Sehen wir uns einige Codebeispiele an. Stellen Sie sich vor, Sie verfügen über eine benutzerdefinierte Schaltfläche im Opportunity-Layout, die, wenn sie angeklickt wird, eine Visualforce-Seite anzeigt, die den Benutzer auffordert, einen Rabattprozentsatz für den Opportunity-Betrag oder, falls vorhanden, die zugehörigen Opportunity-Belegposten einzugeben.

Sehen wir uns an, wie Sie die Methode OpportunitiesService.applyDiscounts von verschiedenen Stellen aus einsetzen können. Visualforce, Batch.Apex und JavaScript Remoting werden unten angegeben. Das folgende Beispiel dreht sich um eine einzelne Opportunity, die über ein Steuerfeld StandardController ausgewählt wird. Beachten Sie, dass die Fehlerbehandlung des Steuerfelds durch das Steuerfeld und nicht durch den Service erfolgt, da Visualforce seine eigene Art der Fehlerbehebung hat.

public PageReference applyDiscount() {
    try {
        // Apply discount entered to the current Opportunity
        OpportunitiesService.applyDiscounts(
            new Set<ID> { standardController.getId() }, DiscountPercentage);
    } catch (Exception e) {
        ApexPages.addMessages(e);
    }          
    return ApexPages.hasMessages() ?null :standardController.view();
}

Im folgenden Beispiel werden mehrere Opportunities über einen StandardSetController ausgewählt.

public PageReference applyDiscounts() {
    try {
        // Apply discount entered to the selected Opportunities
        OpportunitiesService.applyDiscounts(
           // Tip:Creating a Map from an SObject list gives easy access to the Ids (keys)
           new Map<Id,SObject>(standardSetController.getSelected()).keyValues(),
           DiscountPercentage
        );
    } catch (Exception e) {
        ApexPages.addMessages(e);
    }          
    return ApexPages.hasMessages() ?null :standardController.view();               
}

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 Visualforce-Steuerfeld 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) { }    
}

Dieses Beispiel kapselt die Service-Methode und stellt sie über JavaScript Remoting bereit. Hier werden keine Ausnahmen abgefangen, da JavaScript Remoting über integriertes Marshalling ausgelöster Ausnahmen verfügt. Wir möchten dies nutzen und die Ausnahmen an den JavaScript-Client-Code übergeben, um die integrierten Möglichkeiten zu verwenden.

public class OpportunityController {
    @RemoteAction
    public static void applyDiscount(Id opportunityId, Decimal discountPercent) {
        // Call service
        OpportunitiesService.applyDiscounts(new Set<ID> { opportunityId }, discountPercent);
    }
}

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

Lernen Sie weiter kostenlos!
Registrieren Sie sich für einen Account, um fortzufahren.
Was ist für Sie drin?
  • Holen Sie sich personalisierte Empfehlungen für Ihre Karriereplanung
  • Erproben Sie Ihre Fähigkeiten mithilfe praktischer Aufgaben und Quizze
  • Verfolgen Sie Ihre Fortschritte nach und teilen Sie sie mit Arbeitgebern
  • Nutzen Sie Mentoren und Karrierechancen