Prinzipien des Unit Of Work-Musters
Lernziele
Nachdem Sie diese Lektion abgeschlossen haben, sind Sie in der Lage, die folgenden Aufgaben auszuführen:
- Effektives Verwalten Ihrer DML-Vorgänge und Vermeiden partieller Datenbank-Updates
- Erläutern der Funktionen und Vorteile der Apex-Implementierung des Musters
- Anwenden des Unit Of Work-Musters auf die Service-Methode applyDiscount aus der vorherigen Einheit
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 54:05, für den Fall, dass Sie zurückspringen und den Anfang des Schritts noch einmal sehen möchten.)
Prinzipien des Unit Of Work-Musters
Die Unit Of Work ist ein Entwurfsmuster, das sich wiederholenden Code bei der Implementierung von Transaktionsverwaltung und den Codierungsaufwand für die Einhaltung der DML-Massenverarbeitung durch umfangreiche Verwendung von Karten und Listen reduziert. Es ist keine Voraussetzung für die Implementierung einer Service-Schicht, kann dies aber erleichtern. Wir erläutern Ihnen die Funktionsweise anhand eines Vorher-Nachher-Beispiels.
Das in diesem Modul verwendete Unit Of Work-Muster basiert auf dem von Martin Fowler beschriebenen Unit of Work-Muster: "Maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems." (Pflegt eine Liste der von einer Geschäftstransaktion betroffenen Objekte und koordiniert das Schreiben von Änderungen und die Lösung von Parallelitätskonflikten.)
In Bezug auf die Salesforce-Plattform bedeutet dies, dass das Muster für folgende Anwendungsfälle verwendet wird:
- Aufzeichnen von Datensatzaktualisierungen, -einfügungen und -löschungen, um eine bestimmte Geschäftsanforderung zu implementieren
- Aufzeichnen von Datensatzbeziehungen, um das Einfügen untergeordneter oder verwandter Datensätze durch weniger Codierungsaufwand zu erleichtern
- Bei der Anweisung, Änderungen in die Datenbank zu schreiben (festzuschreiben), werden alle erfassten Datensätze als Massenvorgang geschrieben
- Kapseln durchgeführter DML in SavePoint, sodass der Entwickler dies nicht jedes Mal für jede Service-Methode implementieren muss
Implementieren einer Service-Methode ohne Unit Of Work
Um die Vorteile des Unit Of Work-Musters besser zu verstehen, sehen wir uns zunächst den zu schreibenden Code ohne Unit Of Work in jeder Service-Methode an, halten uns dabei aber weiterhin an die zuvor behandelten bewährten Vorgehensweisen. Wir müssen Code für eine bestimmte Geschäftsanforderung schreiben, der Code soll aber auch als Baustein zur Implementierung von Folgendem dienen:
-
DML-Massenverarbeitung und -Optimierung: Der Code kann einige oder alle Opportunity- oder OpportunityLineItem-Datensätze aktualisieren, abhängig vom Flow der Logik. Er erstellt und füllt zwei Listen, um nur die gelesenen Datensätze zu erfassen, die aktualisiert werden müssen.
-
Fehlerbehandlung und Transaktionsverwaltung: Gemäß den Designprinzipien der Service-Schicht muss müssen bei einem Fehler alle oder keine Änderungen festgeschrieben werden, unabhängig davon, ob der Aufrufer ausgelöste Ausnahmen abfängt. Denken Sie daran, dass die Plattform nur dann automatisch zurückgesetzt wird, wenn Ausnahmen nicht behandelt werden, was aus Benutzer- und Ausnahmesicht nicht wünschenswert ist. Es hat sich bewährt, dass der Code Service-Schicht mithilfe von SavePoint und der try/catch-Semantik einen Transaktionsumfang verwaltet.
Das folgende Beispiel verwendet einen SavePoint
, um die Datenbankvorgänge gemäß den Designüberlegungen innerhalb einer Service-Methode zu kapseln. Warum? Stellen Sie sich ein Szenario vor, in dem der zweite DML-Vorgang fehlschlägt. Ohne SavePoint
in der Methode:
- Wenn der Aufrufer die Ausnahme nicht behandelt, wird die gesamte Transaktion, einschließlich des ersten DML-Vorgangs, zurückgesetzt, da dies das Standardverhalten für Apex-Transaktionen ist.
- Fängt der Aufrufer die Ausnahme ab und verhindert damit, dass sie weitergegeben wird oder ein SavePoint wiederhergestellt wird, aktualisiert die Apex-Laufzeitumgebung die Opportunity-Belegposten (erste DML-Anwendung), was eine teilweise Aktualisierung der Datenbank zur Folge hat.
Wird das Unit of Work-Muster nicht verwendet, ist es eine bewährte Vorgehensweise, mehrere DML-Vorgänge zu behandeln, wie in diesem Beispiel gezeigt. Wie Sie jedoch in den nächsten Abschnitten sehen werden, kann die Unit of Work dies für Sie erledigen.
public static void applyDiscounts(Set<Id> opportunityIds, Decimal discountPercentage) { // Validate parameters // ... // Query Opportunities and Lines // ... // Update Opportunities and Lines (if present) List<Opportunity> oppsToUpdate = new List<Opportunity>(); List<OpportunityLineItem> oppLinesToUpdate = new List<OpportunityLineItem>(); // Do some work... Decimal factor = 1 - (discountPercentage==null ? 0 : discountPercentage / 100); for(Opportunity opportunity : opportunities) { // Apply to Opportunity Amount if(opportunity.OpportunityLineItems!=null && opportunity.OpportunityLineItems.size()>0) { for(OpportunityLineItem oppLineItem : opportunity.OpportunityLineItems) { oppLineItem.UnitPrice = oppLineItem.UnitPrice * factor; oppLinesToUpdate.add(oppLineItem); } } else { opportunity.Amount = opportunity.Amount * factor; oppsToUpdate.add(opportunity); } } // Update the database SavePoint sp = Database.setSavePoint(); try { update oppLinesToUpdate; update oppsToUpdate; } catch (Exception e) { // Rollback Database.rollback(sp); // Throw exception on to caller throw e; } }
Apex-Implementierung des Unit Of Work-Musters
Der Rest dieser Einheit bezieht sich auf eine Apex Open Source-Bibliothek, die eine Implementierung von Martin Fowlers Unit of Work-Muster enthält. Die Implementierung erfolgt mittels einer einzelnen Klasse, fflib_SObjectUnitOfWork – öffnen Sie dies also in einem anderen Browserfenster. In der nächsten Einheit setzen wir diese Klasse in der Praxis ein, doch zunächst sehen wir uns einige ihrer wichtigsten Methoden an.
Diese Klasse macht Methoden zugänglich, damit eine Instanz der fflib_SObjectUnitOfWork
-Klasse während der Ausführung des Service-Codes mithilfe der "register"-Methoden Datensätze erfassen kann, die erstellt, aktualisiert oder gelöscht werden müssen. Darüber hinaus kapselt die commitWork-Methode den SavePoint und die try/catch-Konvention.
Die Aktualisierung der Datenbank mit DML erfolgt nur, wenn die commitWork-Methode aufgerufen wird. Daher kann der Service-Code die register-Methoden so häufig wie nötig aufrufen, auch in Schleifen. Dieser Ansatz erlaubt es dem Entwickler, sich auf die Geschäftslogik zu konzentrieren und nicht auf Code, um mehrere Listen und Karten zu verwalten.
Wie in der folgenden Abbildung dargestellt, wird der Umfang der Unit Of Work durch den Beginn und das Ende des Codes Ihrer Service-Methode bestimmt. Rufen Sie commitWork nur einmal im Umfang der Service-Methode auf.
Befolgen Sie diese Schritte, um die Unit Of Work in die Methoden Ihres Service-Codes einzuschließen.
- Initialisieren Sie eine einzelne Unit Of Work und verwenden Sie diese, um den gesamten Umfang des Vorgangs zu definieren, den die Service-Methode durchführt.
- Legen Sie fest, dass die Logik der Service-Schicht bei ihrer Ausführung Datensätze in der Unit Of Work registriert.
- Rufen Sie die commitWork-Methode der Unit Of Work auf, um die DML in einem Massenvorgang zusammenzufassen und auszuführen.
Das folgende Diagramm veranschaulicht die obigen Schritte und erzwingt den Umfang der einzelnen Schritte bei der Ausführung des Codes der Service-Methode.
Um die "Unit of Work"-Klasse zu verwenden, müssen Sie sie mit einer Liste der Objekte erstellen, mit denen Ihr Code interagiert. Die Objekte müssen in der Reihenfolge ihrer Abhängigkeit aufgeführt werden, um sicherzustellen, dass die registrierten über- und untergeordneten Datensätze von der Methode commitWork in der richtigen Reihenfolge eingefügt werden. Der Umgang mit der Über-/Unterordnungsbeziehung fflib_SObjectUnitWork wird in der nächsten Einheit behandelt.
fflib_SObjectUnitOfWork uow = new fflib_SObjectUnitOfWork( new List<SObjectType> { OpportunityLineItem.SObjectType, Opportunity.SObjectType } );
Implementieren einer Service-Methode mit Unit Of Work
Im folgenden Beispiel wird das Unit Of Work-Muster auf den Service angewandt, den wir in der vorherigen Einheit erstellt haben. Code, an dem sich nichts geändert hat, wird hier nicht abgebildet. Beachten Sie, dass die Listen verschwunden sind und dass der SavePoint nicht von Try/Catch-Code umgeben ist, da dies alles von der fflib_SObjectUnitOfWork
-Klasse erledigt wird.
public static void applyDiscounts(Set<Id> opportunityIds, Decimal discountPercentage) { // Unit of Work fflib_SObjectUnitOfWork uow = new fflib_SObjectUnitOfWork( new List<SObjectType> { OpportunityLineItem.SObjectType, Opportunity.SObjectType } ); // Validate parameters // ... // Query Opportunities and Lines // ... // Update Opportunities and Lines (if present) // ... for(Opportunity opportunity : opportunities) { // Apply to Opportunity Amount if(opportunity.OpportunityLineItems!=null && opportunity.OpportunityLineItems.size()>0) { for(OpportunityLineItem oppLineItem : opportunity.OpportunityLineItems) { oppLineItem.UnitPrice = oppLineItem.UnitPrice * factor; uow.registerDirty(oppLineItem); } } else { opportunity.Amount = opportunity.Amount * factor; uow.registerDirty(opportunity); } } // Commit Unit of Work uow.commitWork(); }
Die Klasse fflib_SObjectUnitOfWork aggregiert DML-Vorgänge und kapselt sie in einem SavePoint, wenn die commitWork-Methode aufgerufen wird.
In komplexerem Code mit mehreren Ebenen und Klassen können Sie entscheiden, SObjectUnitOfWork zu übergeben (oder eine statische Version zu verwenden). Der aufgerufene Code kann dann weiterhin seine eigenen Datenbankänderungen registrieren, da der Inhaber der Unit Of Work (in diesem Fall die Service-Schicht) eine gesamte Festschreibungs- oder Rollback-Phase für ihn durchführt.
Ressourcen
- Apex Enterprise Patterns – GitHub-Repository
- Separation of Concerns (Wikipedia)
- "Enterprise Architecture Patterns" von Martin Fowler
- Martin Fowler’s Unit of Work Patterns
- Managing your DML and Transactions with a Unit of Work
- Doing more work with the Unit of Work