Skip to main content

Anwenden der Unit of Work-Prinzipien in Apex

Lernziele

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

  • Beschreiben der "Unit of Work"-Klasse und ihrer Methode
  • Verwenden der fflib_SObjectUnitOfWork-Klasse und ihrer API in Apex
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 01:08:31, für den Fall, dass Sie zurückspringen und den Anfang des Schritts noch einmal sehen möchten.)

Erste Schritte mit der Unit Of Work

Da es sich hierbei um fortgeschrittene Inhalte handelt, ist es notwendig, ein komplexeres Szenario zu betrachten: die Erstellung einer ganz neuen Opportunity und aller benötigten abhängigen Datensätze – und das ist überraschend viel! Der folgende Testeinrichtungscode zeigt, wie dies ohne die Unit Of Work durchgeführt werden könnte. Ganz schön umfangreich, oder?

List<Opportunity> opps = new List<Opportunity>();
List<List<Product2>> productsByOpp = new List<List<Product2>>();
List<List<PricebookEntry>> pricebookEntriesByOpp = new List<List<PricebookEntry>>();
List<List<OpportunityLineItem>> oppLinesByOpp = new List<List<OpportunityLineItem>>();
for(Integer o=0; o<10; o++) {
    Opportunity opp = new Opportunity();
    opp.Name = 'Opportunity ' + o;
    opp.StageName = 'Open';
    opp.CloseDate = System.today();
    opps.add(opp);
    List<Product2> products = new List<Product2>();
    List<PricebookEntry> pricebookEntries = new List<PricebookEntry>();
    List<OpportunityLineItem> oppLineItems = new List<OpportunityLineItem>();
    for(Integer i=0; i<o+1; i++) {
        Product2 product = new Product2();
        product.Name = opp.Name + ' : Product : ' + i;
        products.add(product);
        PricebookEntry pbe = new PricebookEntry();
        pbe.UnitPrice = 10;
        pbe.IsActive = true;
        pbe.UseStandardPrice = false;
        pbe.Pricebook2Id = Test.getStandardPricebookId();
        pricebookEntries.add(pbe);
        OpportunityLineItem oppLineItem = new OpportunityLineItem();
        oppLineItem.Quantity = 1;
        oppLineItem.TotalPrice = 10;
        oppLineItems.add(oppLineItem);
    }
    productsByOpp.add(products);
    pricebookEntriesByOpp.add(pricebookEntries);
    oppLinesByOpp.add(oppLineItems);
}
// Insert Opportunities
insert opps;
// Insert Products
List<Product2> allProducts = new List<Product2>();
for(List<Product2> products : productsByOpp) {
    allProducts.addAll(products);
}
insert allProducts;
// Insert Pricebooks
Integer oppIdx = 0;
List<PricebookEntry> allPricebookEntries = new List<PricebookEntry>();
for(List<PriceBookEntry> pricebookEntries : pricebookEntriesByOpp) {
    List<Product2> products = productsByOpp[oppIdx++];
    Integer lineIdx = 0;
    for(PricebookEntry pricebookEntry : pricebookEntries) {
        pricebookEntry.Product2Id = products[lineIdx++].Id;
    }
    allPricebookEntries.addAll(pricebookEntries);
}
insert allPricebookEntries;
// Insert Opportunity Lines
oppIdx = 0;
List<OpportunityLineItem> allOppLineItems = new List<OpportunityLineItem>();
for(List<OpportunityLineItem> oppLines : oppLinesByOpp) {
    List<PricebookEntry> pricebookEntries = pricebookEntriesByOpp[oppIdx];
    Integer lineIdx = 0;
    for(OpportunityLineItem oppLine : oppLines) {
        oppLine.OpportunityId = opps[oppIdx].Id;
        oppLine.PricebookEntryId = pricebookEntries[lineIdx++].Id;
    }
    allOppLineItems.addAll(oppLines);
    oppIdx++;
}
insert allOppLineItems;

Jetzt schreiben wir den oben gezeigten Code um und verwenden dabei das Unit Of Work-Muster. Zuerst erstellen wir eine Instanz der Unit Of Work. Nicht vergessen: Die an den Konstruktor übergebenen Objekte müssen sich in der Reihenfolge ihrer Abhängigkeit befinden, damit die Methode commitWork sie in der richtigen Reihenfolge einfügt.

// Create a Unit Of Work
fflib_SObjectUnitOfWork uow = new fflib_SObjectUnitOfWork(
    new Schema.SObjectType[] {
        Product2.SObjectType,
        PricebookEntry.SObjectType,
        Opportunity.SObjectType,
        OpportunityLineItem.SObjectType
    }
);

In der vorherigen Einheit wurde bei der Service-Methode applyDiscount die Methode registerDirty verwendet, um Opportunity- und OpportunityLine-Datensätze zu registrieren, die aktualisiert werden müssen. Die Methode registerNew fügt neue Datensätze ein. Beim Einfügen von untergeordneten Datensätzen stellt diese Methode auch sicher, dass die korrekte ID des übergeordneten Datensatzes vor dem Einfügen der untergeordneten Datensätze angewendet wird. Dies geschieht in Zeile 22. Wenn Sie weitere Beziehungen herstellen müssen, können Sie auch die registerRelationship-Methode aufrufen (Zeile 21). Lassen wir unsere Unit of Work jetzt ihre Arbeit tun! Jetzt wird es spannend.

// Do some work!
for(Integer o=0; o<10; o++) {
    Opportunity opp = new Opportunity();
    opp.Name = 'UoW Test Name ' + o;
    opp.StageName = 'Open';
    opp.CloseDate = System.today();
    uow.registerNew(opp);
    for(Integer i=0; i<o+1; i++) {
      Product2 product = new Product2();
      product.Name = opp.Name + ' : Product : ' + i;
      uow.registerNew(product);
      PricebookEntry pbe = new PricebookEntry();
      pbe.UnitPrice = 10;
      pbe.IsActive = true;
      pbe.UseStandardPrice = false;
      pbe.Pricebook2Id = Test.getStandardPricebookId();
      uow.registerNew(pbe, PricebookEntry.Product2Id, product);
      OpportunityLineItem oppLineItem = new OpportunityLineItem();
      oppLineItem.Quantity = 1;
      oppLineItem.TotalPrice = 10;
      uow.registerRelationship(oppLineItem, OpportunityLineItem.PricebookEntryId, pbe);
      uow.registerNew(oppLineItem, OpportunityLineItem.OpportunityId, opp);
    }
}

Wie bereits in der vorherigen Einheit erwähnt, führen diese Methoden keine Datenbankvorgänge durch. Um dies zu tun, müssen Sie die Arbeit mit der Methode commitWork festschreiben, die die richtigen DML-Anweisungen mit den Datensätzen aufruft, die in der richtigen Reihenfolge von untergeordneten und dann übergeordneten Datensätzen registriert wurden. Bei Beziehungen ordnet sie zudem die übergeordnete ID den untergeordneten Datensätzen zu, wodurch Ihr Objektmodell beim Einfügen in die Datenbank effektiv zusammengefügt wird.

// Commit the work to the database!
uow.commitWork();

Beachten Sie, dass der Code nicht nur kürzer, sondern auch viel einfacher zu lesen ist: Die Abläufe in der Logik sind viel übersichtlicher ohne all die lästigen Listen! Dies sind weitere Beispiele für register-Methoden, die Sie verwenden können.

 // Inserts new Opportunity when committing
uow.registerNew(opp);
// Inserts new Opportunity Line Item and associates it with the given Opportunity record when committing
uow.registerNew(oppLineItem, OpportunityLineItem.OpportunityId, opp);
// Relates the given Opportunity Line Item to the given Price Book Entry when committing
uow.registerRelationship(oppLineItem, OpportunityLineItem.PricebookEntryId, pbe);

Übersicht

Bei diesem Modul lernten Sie die Vorteile kennen, die es mit sich bringt, wenn Sie Ihre Anwendungsimplementierung in Schichten betrachten, für die eigene Überlegungen und Anliegen gelten. Wie bei einem Organismus trägt jeder Teil dazu bei, Ihre Anwendung robust und langlebig zu machen.

Sie haben den ersten Schritt auf diesem Weg gemacht, indem Sie das schlagende Herz Ihrer Anwendung, die Geschäftslogik, isoliert haben. Sie können es dabei bewenden lassen oder die SOC-Prinzipien der Aufgabentrennung auf das Objektverhalten (Auslösercode) und die Abfrage von Informationen anwenden, die Ihre Anwendung benötigt. Sehen Sie sich unbedingt das Modul "Apex Enterprise Patterns: Domain- & Selector-Schichten" an, das mit den Domain- und Selector-Schichten der Anwendung fortgesetzt wird. Genießen Sie in der Zwischenzeit die Erstellung Ihrer Services!

Vorbereitung der Aufgaben

Für die Durchführung dieser Aufgaben müssen Sie einige Open Source-Bibliotheken verteilen. Die Klasse fflib_SObjectUnitOfWork ist Teil der Open Source-Bibliothek Apex Common, die von der Open Source-Bibliothek ApexMocks Framework abhängt. Sie müssen zuerst ApexMocks und dann Apex Commons installieren. Weitere Informationen über die beiden Bibliotheken und die zugehörigen Open Source-Lizenzvereinbarungen finden Sie in ihren Repos.

Um die Bibliotheken in Ihrer Organisation zu installieren, klicken Sie einfach unten auf die entsprechende "Bereitstellen"-Schaltfläche.

Stellen Sie die Open Source-Bibliothek ApexMocks bereit.

In Salesforce bereitstellen

Stellen Sie die Open Source-Bibliothek Apex Common bereit.

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"