Skip to main content

Anwenden der Prinzipien der Domain-Schicht in Apex

Lernziele

Nachdem Sie diese Lektion abgeschlossen haben, sind Sie in der Lage, die folgenden Aufgaben auszuführen:

  • Erstellen einer Apex Domain-Klasse
  • Reflexives Ausführen Ihres Standardwertaktivierungs- und Validierungscodes in einer Domain-Klasse
  • Zuordnen von Methoden in der Domain-Klasse zu Apex-Auslöserereignissen
  • Steuern der Anwendung von Sicherheitsmaßnahmen zur Laufzeit
Hinweis

Hinweis

Lernen Sie auf Deutsch? Beginnen Sie die Aufgabe in einem Trailhead Playground in der Sprache Deutsch und verwenden Sie für die Navigation die in Klammern angegebenen Übersetzungen. Kopieren und fügen Sie nur die Angaben in Englisch ein, da zur Überprüfung der Aufgabe Daten in Englisch benötigt werden. Wenn Sie die Aufgabe in Ihrer deutschen Organisation nicht bestehen, empfehlen wir Ihnen folgende Vorgehensweise: (1) Stellen Sie das Gebietsschema auf USA um, (2) legen Sie Englisch als Sprache fest (Anweisungen dazu finden Sie hier) und (3) klicken Sie erneut auf die Schaltfläche "Check Challenge" (Aufgabe überprüfen).

Weitere Details dazu, wie Sie die übersetzte Trailhead-Umgebung optimal nutzen können, finden Sie unter dem Badge "Trailhead in Ihrer Sprache".

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:30, für den Fall, dass Sie zurückspringen und den Anfang des Schritts noch einmal sehen möchten.)

Referenzierter Code

Dieses Modul referenziert die folgenden Apex-Klassen aus dem Projekt FFLIB Apex Common Samplecode. Diese sollten Sie öffnen, bevor Sie beginnen.

Erstellen von Domain-Klassen

Die im Auslösercode der vorherigen Einheit verwendete Klasse fflib_SObjectDomain erweitert eine Basisklasse durch die Unterstützung für Auslöser-Handler-Funktionen und bietet nützliche Funktionen wie etwa Objektsicherheit.

fflib_SObjectDomain-Klasse

Hinweis

Hinweis: Die meisten Methoden werden als virtuelle Methoden bereitgestellt. Die handle*XXXX*-Methoden in dieser Basisklasse haben die Aufgabe sicherzustellen, dass die on*XXXX*-Methoden zum richtigen Zeitpunkt aufgerufen werden. In schwierigeren Fällen, in denen ein direktes Handling erforderlich ist, können Sie die Handler-Methoden überschreiben. Sie sollten sich jedoch bewusst sein, dass die on*XXXX*-Methoden der Superklasse nur aufgerufen werden, wenn Sie die Superklassenversion der handle*XXX-Methode aufrufen. Verschiedene Beispiele dafür finden Sie in den Methoden der OpportunitiesTriggerHandler-Klasse.

Die Basisklasse verwendet das Schablonenmethodenmuster, um Standard-Hooks für die Implementierung allgemeiner Domain-Logik für die Validierung von Datensätzen mithilfe der onValidate()-Methode und die Standardwertaktivierung von Feldwerten mit der onApplyDefaults()-Methode bereitzustellen.

Es gibt auch Methoden für die Platzierung von Logik in Bezug auf spezifische Apex-Auslöserereignisse. Der Konstruktor (für den alle Klassen, mit denen die fflib_SObjectDomain-Klasse erweitert wurde, verfügbar gemacht werden müssen,) akzeptiert eine Liste von sObjects, um das in der vorherigen Einheit beschriebene Designziel der Massenverarbeitung zu unterstützen.

Dies ist eine grundlegende Implementierung der Domain-Klasse "Opportunities".

public class Opportunities extends fflib_SObjectDomain {
    public Opportunities(List<Opportunity> sObjectList) {
        super(sObjectList);
    }
    public class Constructor implements fflib_SObjectDomain.IConstructable {
        public fflib_SObjectDomain construct(List<SObject> sObjectList) {
            return new Opportunities(sObjectList);
        }
    }
}

Mit der inneren Klasse "Constructor" kann die Basisklassenmethode fflib_SObjectDomain.triggerHandler, die im Apex-Auslöserbeispiel aus der vorherigen Einheit behandelt wurde, eine neue Instanz der Auslöser-Handler-Logik für eine Domain-Klasse erstellen und an die sObject-Liste übergeben (meist Trigger.new).

Implementieren von Logik zur Aktivierung von Feldstandardwerten

Zur Platzierung von Logik zur Aktivierung von Feldstandardwerten stellt die Basisklasse fflib_SObjectDomain die onApplyDefaults()-Methode zur Verfügung. Diese Methode wird während eines Auslöseraufrufs von der handleBeforeInsert()-Methode in der Basisklasse fflib_SObjectDomain aufgerufen.

Wenn Sie Logik hier platzieren, stellen Sie sicher, dass die Standardwertaktivierung innerhalb der Anwendung einheitlich erfolgt, wenn Datensätze hinzugefügt werden. Sie können sie bei Bedarf auch explizit in einem Service aufrufen, der dazu beiträgt, Standarddatensatzwerte für einen Benutzer anzuzeigen, der beispielsweise über eine Visualforce-Seite oder Lightning-Komponente auf eine benutzerdefinierte Oberfläche zugreift.

Die Basisklasse macht die sObject-Liste verfügbar, die während des Konstruktoraufrufs über die Eigenschaft records für alle Methoden zur Verfügung gestellt wird. Obwohl dies streng genommen kein Auslöser-Szenario ist, empfehlen wird trotzdem wärmstens, die Massenverarbeitung in Betracht zu ziehen, um die Domain-Designziele aus der vorherigen Einheit umzusetzen.

public override void onApplyDefaults() {
    // Apply defaults to Opportunities
    for(Opportunity opportunity :(List<Opportunity>) Records) {
        if(opportunity.DiscountType__c == null) {
            opportunity.DiscountType__c = OpportunitySettings__c.getInstance().DiscountType__c;
        }               
    }
}
Hinweis

Hinweis: Das obige Beispiel kann auch durch die Verwendung einer Formel realisiert werden, die als Teil der Felddefinition DiscountType__c ausgedrückt wird. Natürlich könnte die Logik zur Standardwertaktivierung mehrere Felder oder andere Datensätze umfassen. In diesem Fall müssen Sie auf Apex-Codierung zurückgreifen.

Implementieren von Validierungslogik

Sie können die oben angegebenen Auslösermethoden zwar überschreiben, um Validierungslogik zu implementieren, die bewährten Vorgehensweisen sehen jedoch vor, dies erst in der After-Phase des Apex-Auslöseraufrufs zu tun. Durch Überschreiben einer der beiden onValidate()-Methoden der Basisklasse fflib_SObjectDomain können Sie diese Logik an einer klar definierten Position implementieren.

public override void onValidate() {
    // Validate Opportunities
    for(Opportunity opp :(List<Opportunity>) this.records) {
        if(opp.Type.startsWith('Existing') && opp.AccountId == null) {
            opp.AccountId.addError('You must provide an Account when ' +
                'creating Opportunities for existing Customers.');
        }
    }
}

Die oben angegebene onValidate()-Methode wird von der Basisklasse aus aufgerufen, wenn Datensätze im Objekt eingefügt werden. Wenn Sie Validierungslogik benötigen, die Datenänderungen während Datensatzaktualisierungen erfasst, können Sie die folgende Variante überschreiben.

public override void onValidate(Map<Id,SObject> existingRecords) {
    // Validate changes to Opportunities
    for(Opportunity opp :(List<Opportunity>) Records) {
        Opportunity existingOpp = (Opportunity) existingRecords.get(opp.Id);
        if(opp.Type != existingOpp.Type) {
            opp.Type.addError('You cannot change the Opportunity type once it has been created');
        }
    }
}

Beachten Sie, dass Code in den Basisklassenmethoden handleAfterInsert() und handleAfterUpdate() sicherstellt, dass bewährte Vorgehensweisen in puncto Sicherheit umgesetzt werden, indem diese Methode erst nach dem "After"-Teil des Apex-Auslösers aufgerufen wird (nachdem alle Apex-Auslöser für dieses Objekt abgeschlossen wurden). Dieses Verhalten ist für Entwickler von AppExchange-Paketen sehr wichtig (siehe Abschnitt "Ressourcen").

Implementieren von Apex-Auslöserlogik

Es ist nicht immer der Fall, dass sich die Domänenlogik, die Sie implementieren, den oben genannten Methoden zuordnen lässt. Tatsächlich ist es laut den Separation of Concerns-Richtlinien keine strikte Anforderung, die gesamte Standardwertaktivierungs- oder Validierungslogik in diesen Methoden zu implementieren. Das ist nur ein Vorschlag. Wenn es Ihnen lieber ist, können Sie auch die gesamte Logik in den nachfolgenden Methoden platzieren.

Was die Implementierung von Code im Zusammenhang mit Apex-Auslösern angeht, der Verhaltensweisen über andere Domain-Objekte aufruft, so folgt nun ein etwas konstruiertes Beispiel für das Überschreiben der onAfterInsert()-Methode, um das Feld "Beschreibung" zugehöriger Accounts zu aktualisieren, sobald neue Opportunities eingefügt werden.

public override void onAfterInsert() {
    // Related Accounts
    List<Id> accountIds = new List<Id>();
    for(Opportunity opp :(List<Opportunity>) Records) {
        if(opp.AccountId!=null) {
            accountIds.add(opp.AccountId);
        }
    }
    // Update last Opportunity activity on related Accounts via the Accounts domain class
    fflib_SObjectUnitOfWork uow =
      new fflib_SObjectUnitOfWork(
        new Schema.SObjectType[] { Account.SObjectType });
    Accounts accounts = new Accounts([select Id from Account
      where id in :accountIds]);
    accounts.updateOpportunityActivity(uow);
    uow.commitWork();              
}

Anmerkungen zum obigen Beispiel:

  • Eine Instanz der Domain-Klasse "Accounts" wird unter Verwendung einer Inline-SOQL-Abfrage initialisiert. In der nächsten Einheit dieses Moduls wird ein Muster vorgestellt, das die Wiederverwendung und Konsistenz der Ergebnisdaten durch Kapsel der Abfragelogik verbessert, was wichtig für die Domain-Klassenlogik ist.
  • Die fflib_SObjectUnitOfWork-Instanz wird in einem Apex-Auslöserkontext verwendet und nicht in einem Service-Kontext, wie im SOC-Modul beschrieben wird. In diesem Fall ist ihr Umfang ein Auslöserereignis oder eine Methode. Diese Methoden werden direkt von der Plattform aufgerufen, nicht von der Service-Schicht. Daher wird eine Unit of Work erstellt und an die Accounts-Methode übergeben, damit sie Änderungen an den Account-Datensätzen erfasst. Es hat sich bewährt, die Unit of Work immer an einer Position zu initialisieren, um doppelte Initialisierungen zu vermeiden (dies wird hier allerdings nicht demonstriert).
  • Die Delegation an die Domain-Klasse Accounts ist hier sinnvoll, da Aktualisierungen auf der Grundlage von Accounts eher ein Verhalten des Objekts "Account" als des Objekts "Opportunity" sind. Diese Art von SOC zwischen Domain-Klassen wird auch im nächsten Abschnitt noch verdeutlicht.

Hier ist die Accounts.updateOpportunityActivity-Methode zur Bezugnahme.

public class Accounts extends fflib_SObjectDomain {
    public Accounts(List<Account> sObjectList) {
        super(sObjectList);
    }
    public void updateOpportunityActivity(fflib_SObjectUnitOfWork uow) {
        for(Account account :(List<Account>) Records) {
            account.Description = 'Last Opportunity Raised ' + System.today();
            uow.registerDirty(account);
        }
    }
}

Implementieren von benutzerdefinierter Logik

Sie sind nicht auf die Implementierung von Methoden beschränkt, die von der Basisklasse aus überschrieben werden können. Erinnern Sie sich an die veränderte Service-Schicht aus der vorherigen Einheit.

public static void applyDiscounts(Set<Id> opportunityIds, Decimal discountPercentage) {
    // Unit of Work
    // ...   // Validate parameters
    // ...   // Construct Opportunities domain class
    Opportunities opportunities = new Opportunities( ...);
    // Apply discount via domain class behavior
    opportunities.applyDiscount(discountPercentage, uow);
    // Commit updates to opportunities     
    uow.commitWork();                      
}

Dieser Code verwendet eine Domain-Klassenmethode, die einen Rabatt auf eine Opportunity anwenden kann, wodurch diese Logik weiter an einer Position gekapselt wird, die mit dem Domain-Objekt verknüpft ist.

Falls erforderlich, delegiert der Code an die Domain-Klasse OpportunityLineItems, um Rabatte auf Belegpostenebene anzuwenden. Nehmen wir zum Zweck dieser Erläuterung an, dass die Logik für Opportunities, die Produktbelegposten nutzen, unterschiedlich ist.

Code mit einer Domain-Klassenmethode, die einen Rabatt auf eine Opportunity anwenden kann

Sie können sich den Code für die Domain-Klasse OpportunityLineItems hier ansehen.

Die fflib_SObjectUnitOfWork-Instanz wird als Argument angegeben, damit der Aufrufer (in diesem Fall die OpportunitiesService.applyDiscount-Methode) sie übergeben kann, damit der Domain-Code Arbeit dafür erfassen kann, und das Argument anschließend an die applyDiscount()-Methode der Domain-Klasse OpportunityLineItems übergeben werden kann.

Geschäftslogik in der Domain-Klasse im Vergleich zur Service-Klasse

Manchmal ist es eventuell nicht unbedingt eindeutig, wo Code platziert werden sollte. Kehren wir zurück zum SOC-Prinzip und sehen wir uns an, um welche Anforderungen es bei den Service- und Domain-Schichten geht.

Art der Anwendungsanforderung Service oder Domain Beispiel
Sicherstellen, dass die Validierung und Standardwertaktivierung von Feldern einheitlich abläuft, wenn Datensatzdaten geändert werden
Domain Anwenden von Standard-Rabattrichtlinie auf Produkte, die hinzugefügt werden
Reagieren auf eine Benutzer- oder Systemaktion, bei der verschiedene Informationen zusammengetragen oder mehrere Objekte aktualisiert werden müssen Stellt hauptsächlich Aktionen zur Verfügung, die für eine Datensatzsammlung auftreten können, und koordiniert alle für die Durchführung dieser Aktion notwendigen Vorgänge (eventuell mit anderen, unterstützenden Services)
Service Erstellen und Berechnen von Rechnungen aus Arbeitsaufträgen. Eventuell müssen Informationen aus Preisbüchern extrahiert werden.
Verarbeiten von Datensatzänderungen, die in der Anwendung auftreten, da sich andere, zugehörige Datensätze ändern oder eine Benutzer- bzw. Systemaktion ausgeführt wird. Beispiel: Aktivieren der Standardwerte für Werte, falls nötig. Wirkt sich die Änderung eines Felds auf ein anderes aus, wird auch dieses aktualisiert.
Domain Art und Weise, auf die das Account-Objekt reagiert, wenn eine Opportunity erstellt wird, oder, wie der Rabatt angewandt wird, wenn der Opportunity-Rabattprozess ausgeführt wird. Hinweis: Diese Art von Logik wird möglicherweise in der Service-Schicht gestartet, sollte jedoch besser in der Domain-Schicht bedient werden, um die Größe und Komplexität der Service-Methode handeln zu können bzw. ihre Wiederverwendung zu erleichtern.
Umgang mit allgemeinem Verhalten, das über eine Reihe unterschiedlicher Objekte hinweg gilt
Domain Berechnen des Preises für das Opportunity-Produkt oder Produktbelegposten in Arbeitsaufträgen. Hinweis: Sie könnten dies in einer gemeinsamen Domain-Basisklasse platzieren und die fflib_SObjectDomain-Methode überschreiben, um die Apex-Auslöserereignisse zu verknüpfen, wobei konkrete Domain-Klassen wiederum diese Klasse mit ihrem Verhalten erweitern.

Steuern der Durchsetzung von Sicherheitsmaßnahmen

Standardmäßig erzwingt die Basisklasse fflib_SObjectDomain CRUD-Sicherheit für Salesforce-Objekte. Sie wird jedoch für alle möglichen Arten von Zugriff auf das Objekt aufgerufen, ob über ein Steuerfeld oder einen Service. Service-Logik versucht eventuell, im Namen des Benutzers auf ein Objekt zuzugreifen, ohne dass dafür Berechtigungen für das Objekt erforderlich sind.

Wenn Sie dieses Standardverhalten lieber deaktivieren und selbst in Ihrem Service-Code umsetzen möchten, können Sie eine Konfigurationsfunktion der Basisklasse verwenden. Das folgende Beispiel zeigt, wie Sie dies in jedem Konstruktor realisieren. Eine andere Möglichkeit besteht darin, eine eigene Basisklasse mit diesem Code zu erstellen und diese Klasse dann für alle Domain-Klassen zu erweitern.

public Opportunities(List<Opportunity> sObjectList) {
    super(sObjectList);        
    // Disable default Object Security checking    
    Configuration.disableTriggerCRUDSecurity();
}  
Hinweis

Hinweis: Die Basisklasse bietet keine allgemeine Unterstützung für die Durchsetzung von Feldebenensicherheit bei Aktualisierungen. Diese Durchsetzung ist weiterhin Ihre Aufgabe.

Testen von Domain-Klassen

Das Faktorisieren von Logik in kleinere, stärker gekapselte Blöcke ist vorteilhaft für die testgetriebene Entwicklung (Test-driven Development, TDD), da Sie die Domain-Klassen in Ihren Tests einfacher erstellen und die Methoden direkt aufrufen können. Das soll nicht heißen, dass Sie Ihre Service-Schicht nicht testen sollen, es ermöglicht Ihnen jedoch einen eher inkrementellen Test- und Entwicklungsansatz.

Vorbereiten auf die praktische Aufgabe

Zum Absolvieren dieser praktischen Aufgabe starten Sie den Trailhead Playground, den Sie im Modul Apex Enterprise Patterns: Service-Schicht verwendet haben. Sie benötigen nämlich die Open Source-Bibliotheken, die Sie dort bereits installiert haben. Falls Sie einen anderen Trailhead Playground verwenden, starten Sie diesen und installieren zuerst die ApexMocks-Bibliothek und dann die Apex Common-Bibliothek mithilfe der nachfolgend angezeigten Schaltflächen In Salesforce bereitstellen. Weitere Informationen über die beiden Bibliotheken und die zugehörigen Open Source-Lizenzvereinbarungen finden Sie in ihren Repos.

Stellen Sie die Open Source-Bibliothek ApexMocks bereit.

In Salesforce bereitstellen

Stellen Sie die Open Source-Bibliothek Apex Common bereit.

Schaltfläche 'In Salesforce bereitstellen'

Ressourcen

Teilen Sie Ihr Trailhead-Feedback über die Salesforce-Hilfe.

Wir würden uns sehr freuen, von Ihren Erfahrungen mit Trailhead zu hören: Sie können jetzt jederzeit über die Salesforce-Hilfe auf das neue Feedback-Formular zugreifen.

Weitere Infos Weiter zu "Feedback teilen"