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
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.
Stellen Sie die Open Source-Bibliothek Apex Common bereit.
Ressourcen
- Separation of Concerns (Wikipedia)
- "Enterprise Architecture Patterns" von Martin Fowler
- Martin Fowler’s Unit of Work Patterns
- Apex Enterprise Patterns – GitHub-Repository
- Managing your DML and Transactions with a Unit of Work
- Doing more work with the Unit of Work