Ich bin vor kurzem auf ein scheinbar triviales architektonisches Problem gestoßen. Ich hatte ein einfaches Repository in meinem Code, das wie folgt aufgerufen wurde (Code ist in C #):
var user = /* create user somehow */;
_userRepository.Add(user);
/* do some other stuff*/
_userRepository.SaveChanges();
SaveChanges
war ein einfacher Wrapper, der Änderungen an der Datenbank festschreibt:
void SaveChanges()
{
_dataContext.SaveChanges();
_logger.Log("User DB updated: " + someImportantInfo);
}
Nach einiger Zeit musste ich dann eine neue Logik implementieren, die jedes Mal E-Mail-Benachrichtigungen sendet, wenn ein Benutzer im System erstellt wurde. Da es viele Aufrufe zum _userRepository.Add()
und SaveChanges
um das System gab, habe ich beschlossen, SaveChanges
wie folgt zu aktualisieren :
void SaveChanges()
{
_dataContext.SaveChanges();
_logger.Log("User DB updated: " + someImportantInfo);
foreach (var newUser in dataContext.GetAddedUsers())
{
_eventService.RaiseEvent(new UserCreatedEvent(newUser ))
}
}
Auf diese Weise kann externer Code UserCreatedEvent abonnieren und die erforderliche Geschäftslogik verarbeiten, mit der Benachrichtigungen gesendet werden.
Es wurde mir jedoch darauf hingewiesen, dass meine Änderung SaveChanges
gegen das Prinzip der Einzelverantwortung verstößt und SaveChanges
lediglich Ereignisse retten und nicht auslösen sollte.
Ist das ein gültiger Punkt? Es scheint mir, dass das Auslösen eines Ereignisses hier im Wesentlichen dasselbe ist wie das Protokollieren: der Funktion wird lediglich eine Nebenfunktion hinzugefügt. Und SRP verbietet Ihnen nicht, Protokollierungs- oder Auslöseereignisse in Ihren Funktionen zu verwenden. Es heißt lediglich, dass eine solche Logik in anderen Klassen gekapselt werden sollte, und es ist in Ordnung, dass ein Repository diese anderen Klassen aufruft.
quelle
Antworten:
Ja, es kann eine gültige Anforderung sein, ein Repository zu haben, das bestimmte Ereignisse für bestimmte Aktionen wie
Add
oderSaveChanges
auslöst. Ich werde dies nicht in Frage stellen (wie einige andere Antworten), nur weil Ihr spezielles Beispiel für das Hinzufügen von Benutzern und das Senden von E-Mails möglicherweise wie folgt aussieht ein bisschen erfunden. Lassen Sie uns im Folgenden davon ausgehen, dass diese Anforderung im Kontext Ihres Systems vollkommen gerechtfertigt ist.Also ja , die Mechanik Ereignis codiert , sowie die Protokollierung sowie die Speicherung in einem Verfahren gegen die SRP . In vielen Fällen ist dies wahrscheinlich ein akzeptabler Verstoß, insbesondere wenn niemand die Wartungsverantwortung für "Änderungen speichern" und "Ereignis auslösen" auf verschiedene Teams / Betreuer verteilen möchte. Nehmen wir jedoch an, dass jemand eines Tages genau dies tun möchte. Kann dies auf einfache Weise gelöst werden, indem der Code dieser Bedenken in verschiedene Klassenbibliotheken gestellt wird?
Die Lösung hierfür besteht darin, Ihr ursprüngliches Repository für das Festschreiben von Änderungen an der Datenbank verantwortlich zu machen und ein Proxy- Repository mit genau derselben öffentlichen Schnittstelle zu erstellen, das ursprüngliche Repository wiederzuverwenden und den Methoden die zusätzlichen Ereignismechanismen hinzuzufügen.
Sie können die Proxy-Klasse a anrufen
NotifyingRepository
oder,ObservableRepository
wenn Sie möchten, gemäß der von @ Peter hoch bewerteten Antwort .Die neue und die alte Repository-Klasse sollten beide von einer gemeinsamen Schnittstelle abgeleitet sein, wie in der klassischen Proxy-Musterbeschreibung gezeigt .
Initialisieren Sie dann in Ihrem ursprünglichen Code
_userRepository
durch ein Objekt der neuenEventFiringUserRepo
Klasse. Auf diese Weise halten Sie das ursprüngliche Repository von der Ereignismechanik getrennt. Bei Bedarf können Sie das Ereignisauslösungs-Repository und das ursprüngliche Repository nebeneinander haben und die Anrufer entscheiden lassen, ob sie das erstere oder das letztere verwenden.Um ein in den Kommentaren erwähntes Anliegen anzusprechen: Führt das nicht dazu, dass Proxies auf Proxies auf Proxies aufgesetzt werden, und so weiter? Tatsächlich schafft das Hinzufügen der Ereignismechanik eine Grundlage, um weitere Anforderungen des Typs "E-Mails senden" hinzuzufügen, indem Sie einfach die Ereignisse abonnieren und sich ohne zusätzliche Proxys an die SRP mit diesen Anforderungen halten. Aber das eine, was hier einmal hinzugefügt werden muss, ist die Eventmechanik selbst.
Ob sich diese Art der Trennung im Kontext Ihres Systems wirklich lohnt, müssen Sie und Ihr Prüfer selbst entscheiden. Wahrscheinlich würde ich die Protokollierung nicht vom ursprünglichen Code trennen, auch nicht durch Verwendung eines anderen Proxys, nicht durch Hinzufügen eines Protokollierers zum Listener-Ereignis, obwohl dies möglich wäre.
quelle
SaveChanges()
wird der Datenbankdatensatz nicht tatsächlich erstellt, und es kann zu einem Rollback kommen. Es scheint, als müssten SieAcceptAllChanges
das TransactionCompleted-Ereignis entweder überschreiben oder abonnieren.Das Senden einer Benachrichtigung, dass sich der persistente Datenspeicher geändert hat, erscheint beim Speichern sinnvoll.
Natürlich sollten Sie Add nicht als Sonderfall behandeln - Sie müssten auch Ereignisse für Modify und Delete auslösen. Es ist die spezielle Behandlung des "Add" -Falls, die den Leser dazu zwingt, zu erklären, warum er riecht, und letztendlich einige Leser des Codes dazu bringt, zu der Schlussfolgerung zu gelangen, dass er gegen die SRP verstoßen muss.
Ein "Benachrichtigungs" -Repository, das abgefragt, geändert und Ereignisse bei Änderungen ausgelöst werden kann, ist ein ganz normales Objekt. Sie können davon ausgehen, dass Sie in nahezu jedem Projekt mit angemessener Größe mehrere Variationen finden.
Aber ist ein "Benachrichtigungs" -Repository tatsächlich das, was Sie brauchen? Sie erwähnten C #: Viele Leute würden zustimmen, dass die Verwendung von a
System.Collections.ObjectModel.ObservableCollection<>
anstelle von,System.Collections.Generic.List<>
wenn letzteres alles ist, was Sie benötigen, alle Arten von schlecht und falsch ist, aber nur wenige würden sofort auf SRP verweisen.Was Sie jetzt tun, tauscht Ihr
UserList _userRepository
mit einemObservableUserCollection _userRepository
. Ob dies die beste Vorgehensweise ist oder nicht, hängt von der Anwendung ab. Aber während es zweifellos das_userRepository
beträchtlich geringere Gewicht macht, verletzt es meiner bescheidenen Meinung nach SRP nicht.quelle
ObservableCollection
in diesem Fall besteht darin, dass das entsprechende Ereignis nicht beim Aufruf vonSaveChanges
, sondern beim Aufruf von ausgelöst wirdAdd
, was zu einem ganz anderen Verhalten als dem im Beispiel gezeigten führen würde. Sehen Sie sich meine Antwort an, wie Sie das ursprüngliche Repo leicht halten und trotzdem bei der SRP bleiben, indem Sie die Semantik intakt halten.ObservableCollection<>
undList<>
zum Vergleich und Kontext aufgerufen . Ich wollte nicht empfehlen, die tatsächlichen Klassen für die interne Implementierung oder die externe Schnittstelle zu verwenden.Ja, es ist ein Verstoß gegen das Prinzip der einheitlichen Verantwortung und ein gültiger Punkt.
Ein besseres Design wäre, wenn ein separater Prozess "neue Benutzer" aus dem Repository abruft und die E-Mails sendet. Verfolgen Sie, welche Benutzer eine E-Mail erhalten haben, welche Fehler aufgetreten sind, welche erneut gesendet wurden usw. usw.
Auf diese Weise können Sie mit Fehlern, Abstürzen und Ähnlichem umgehen und vermeiden, dass Ihr Repository alle Anforderungen abruft, bei denen der Eindruck entsteht, dass Ereignisse eintreten, "wenn ein Commit für die Datenbank ausgeführt wird".
Das Repository weiß nicht, dass ein Benutzer, den Sie hinzufügen, ein neuer Benutzer ist. Die Verantwortung liegt einfach darin, den Benutzer zu speichern.
Es lohnt sich wahrscheinlich, die folgenden Kommentare zu erweitern.
Falsch. Sie haben die Konflikte "Zum Repository hinzugefügt" und "Neu".
"Zum Repository hinzugefügt" bedeutet genau das, was es sagt. Ich kann Benutzer zu verschiedenen Repositories hinzufügen, entfernen und wieder hinzufügen.
"Neu" ist ein Zustand eines Benutzers, der durch Geschäftsregeln definiert ist.
Derzeit lautet die Geschäftsregel möglicherweise "Neu == wurde gerade zum Repository hinzugefügt". Dies bedeutet jedoch nicht, dass es keine separate Verantwortung ist, diese Regel zu kennen und anzuwenden.
Sie müssen vorsichtig sein, um diese Art von datenbankorientiertem Denken zu vermeiden. Sie werden Edge-Case-Prozesse haben, die nicht neue Benutzer zum Repository hinzufügen, und wenn Sie E-Mails an sie senden, wird das gesamte Unternehmen sagen: "Natürlich sind dies keine" neuen "Benutzer! Die eigentliche Regel lautet X".
Falsch. Aus den oben genannten Gründen ist es außerdem kein zentraler Ort, es sei denn, Sie fügen den E-Mail-Sendecode tatsächlich in die Klasse ein, anstatt nur ein Ereignis auszulösen.
Sie haben Anwendungen, die die Repository-Klasse verwenden, aber nicht über den Code zum Senden der E-Mail verfügen. Wenn Sie Benutzer in diesen Anwendungen hinzufügen, wird die E-Mail nicht gesendet.
quelle
Add
. Seine Semantik impliziert, dass alle hinzugefügten Benutzer neue Benutzer sind. Kombinieren Sie alle Argumente, dieAdd
vor dem Aufruf übergeben wurdenSave
- und Sie erhalten alle neuen Benutzer.Ja, obwohl es sehr von der Struktur Ihres Codes abhängt. Ich habe nicht den vollständigen Kontext, also werde ich versuchen, allgemein zu sprechen.
Es ist absolut nicht. Die Protokollierung ist nicht Teil des Geschäftsablaufs. Sie kann deaktiviert werden. Sie sollte keine (geschäftlichen) Nebenwirkungen verursachen und den Zustand und die Gesundheit Ihrer Anwendung in keiner Weise beeinflussen, auch wenn Sie aus irgendeinem Grund keine Protokollierung durchführen konnten alles mehr. Vergleichen Sie das jetzt mit der Logik, die Sie hinzugefügt haben.
SRP arbeitet zusammen mit ISP (S und I in SOLID). Sie haben am Ende viele Klassen und Methoden, die ganz bestimmte Dinge tun und sonst nichts. Sie sind sehr fokussiert, sehr einfach zu aktualisieren oder zu ersetzen und im Allgemeinen einfach (er) zu testen. In der Praxis gibt es natürlich auch einige größere Klassen, die sich mit der Orchestrierung befassen: Sie weisen eine Reihe von Abhängigkeiten auf und konzentrieren sich nicht auf atomisierte Aktionen, sondern auf Geschäftsaktionen, für die möglicherweise mehrere Schritte erforderlich sind. Solange der Geschäftskontext klar ist, können sie auch als Einzelverantwortung bezeichnet werden. Wie Sie jedoch richtig sagten, möchten Sie mit zunehmendem Code möglicherweise einen Teil davon in neue Klassen / Schnittstellen abstrahieren.
Nun zurück zu Ihrem speziellen Beispiel. Wenn Sie unbedingt eine Benachrichtigung senden müssen, wenn ein Benutzer erstellt wird, und möglicherweise sogar andere, speziellere Aktionen ausführen müssen, können Sie einen separaten Dienst erstellen, der diese Anforderung kapselt, z. B.
UserCreationService
eine MethodeAdd(user)
, die den Speicher (den Aufruf) verwaltet in Ihr Repository) und die Benachrichtigung als einzelne Geschäftsaktion. Oder machen Sie es in Ihrem Original-Snippet nach_userRepository.SaveChanges();
quelle
If the purpose of my event would be to send new user data to Google Analytics - then disabling it would have the same business effect as disabling logging: not critical, but pretty upsetting
. Was ist, wenn Sie vorzeitige Ereignisse auslösen, die falsche "Nachrichten" verursachen? Was ist, wenn die Analyse "Benutzer" berücksichtigt, die aufgrund von Fehlern bei der DB-Transaktion nicht endgültig erstellt wurden? Was ist, wenn das Unternehmen Entscheidungen in falschen Räumlichkeiten trifft, die von ungenauen Daten gestützt werden? Sie konzentrieren sich zu sehr auf die technische Seite des Problems. "Manchmal kann man den Wald vorIn der SRP geht es theoretisch um Menschen , wie Onkel Bob in seinem Artikel Das Prinzip der Einzelverantwortung erklärt . Vielen Dank, dass Sie Robert Harvey in Ihrem Kommentar zur Verfügung gestellt haben.
Die richtige Frage lautet:
Welcher "Stakeholder" hat die Anforderung "E-Mails senden" hinzugefügt?
Wenn dieser Stakeholder auch für die Datenpersistenz verantwortlich ist (unwahrscheinlich, aber möglich), verstößt dies nicht gegen die SRP. Ansonsten ist es so.
quelle
Während es technisch gesehen nichts auszusetzen gibt, wenn Repositorys Ereignisse melden, würde ich vorschlagen, es unter funktionalen Gesichtspunkten zu betrachten, bei denen die Bequemlichkeit einige Bedenken aufwirft.
Voraussetzung von mir
Berücksichtigen Sie die vorherige Voraussetzung, bevor Sie entscheiden, ob das Repository der richtige Ort für die Benachrichtigung von Geschäftsereignissen ist (unabhängig von der SRP). Beachten Sie, dass ich das Geschäftsereignis sagte , weil es für mich
UserCreated
eine andere Konnotation hat alsUserStored
oderUserAdded
1 . Ich würde auch davon ausgehen, dass sich jedes dieser Ereignisse an ein anderes Publikum richtet.Auf der einen Seite ist das Erstellen von Benutzern eine geschäftsspezifische Regel, die möglicherweise keine Persistenz beinhaltet. Möglicherweise sind mehr Geschäftsvorgänge und mehr Datenbank- / Netzwerkvorgänge erforderlich. Vorgänge, die der Persistenzschicht nicht bekannt sind. Die Persistenzschicht verfügt nicht über genügend Kontext, um zu entscheiden, ob der Anwendungsfall erfolgreich beendet wurde oder nicht.
Auf der anderen Seite ist es nicht unbedingt wahr, dass
_dataContext.SaveChanges();
der Benutzer erfolgreich bestanden hat. Dies hängt von der Transaktionsspanne der Datenbank ab. Zum Beispiel könnte es für Datenbanken wie MongoDB zutreffen, welche Transaktionen atomar sind, aber nicht für herkömmliche RDBMS, die ACID-Transaktionen implementieren, bei denen möglicherweise mehr Transaktionen beteiligt sind und noch festgeschrieben werden müssen.Es könnte sein. Ich kann jedoch sagen, dass es nicht nur um SRP (technisch gesehen), sondern auch um Komfort (funktional gesehen) geht.
Absolut nicht. Die Protokollierung sollte jedoch keine Nebenwirkungen haben, da Sie vermuteten, dass das Ereignis
UserCreated
wahrscheinlich andere Geschäftsvorgänge auslöst. Wie Benachrichtigungen. 3Nicht unbedingt wahr. SRP ist nicht nur ein klassenspezifisches Anliegen. Es funktioniert auf verschiedenen Abstraktionsebenen, wie Ebenen, Bibliotheken und Systemen! Es geht um Zusammenhalt, darum, zusammenzuhalten, was sich aus den gleichen Gründen durch die Hand der gleichen Stakeholder ändert . Wenn sich die Benutzererstellung ( Anwendungsfall ) ändert, ändert sich wahrscheinlich auch der Zeitpunkt und die Gründe für das Eintreten des Ereignisses.
1: Dinge angemessen zu benennen ist auch wichtig.
2: Angenommen, wir haben
UserCreated
nach gesendet_dataContext.SaveChanges();
, aber die gesamte Datenbanktransaktion ist später aufgrund von Verbindungsproblemen oder Verstößen gegen Einschränkungen fehlgeschlagen. Seien Sie vorsichtig bei der vorzeitigen Übertragung von Ereignissen, da die Nebenwirkungen nur schwer rückgängig gemacht werden können (sofern dies überhaupt möglich ist).3: Nicht angemessen behandelte Benachrichtigungsprozesse können dazu führen, dass Sie Benachrichtigungen auslösen, die nicht rückgängig gemacht werden können
quelle
Before
oderPreview
das macht keine Garantien überhaupt Gewißheit.Nein, dies verstößt nicht gegen die SRP.
Viele scheinen der Meinung zu sein, dass das Prinzip der Einzelverantwortung bedeutet, dass eine Funktion nur "eine Sache" tun sollte und dann in die Diskussion darüber verwickelt wird, was "eine Sache" ausmacht.
Aber das ist nicht das, was das Prinzip bedeutet. Es geht um Bedenken auf Unternehmensebene. Eine Klasse sollte nicht mehrere Anliegen oder Anforderungen implementieren, die sich auf Unternehmensebene unabhängig voneinander ändern können. Nehmen wir an, eine Klasse speichert den Benutzer und sendet eine fest codierte Begrüßungsnachricht per E-Mail. Mehrere unabhängige Bedenken können dazu führen, dass sich die Anforderungen einer solchen Klasse ändern. Der Designer könnte verlangen, dass sich das HTML / Stylesheet der E-Mail ändert. Der Kommunikationsexperte kann eine Änderung des Mailtextes verlangen. Und der UX-Experte könnte entscheiden, dass die E-Mail tatsächlich an einer anderen Stelle im Onboarding-Ablauf gesendet werden soll. Die Klasse unterliegt also mehreren Anforderungsänderungen aus unabhängigen Quellen. Dies verstößt gegen das SRP.
Das Auslösen eines Ereignisses verstößt dann jedoch nicht gegen die SRP, da das Ereignis nur vom Speichern des Benutzers und nicht von anderen Belangen abhängt. Ereignisse sind eine wirklich gute Möglichkeit, die SRP aufrechtzuerhalten, da Sie durch das Speichern eine E-Mail auslösen können, ohne dass das Repository von der E-Mail betroffen ist oder auch nur davon Kenntnis hat.
quelle
Sorgen Sie sich nicht um das Prinzip der Einzelverantwortung. Hier hilft es Ihnen nicht, eine gute Entscheidung zu treffen, da Sie ein bestimmtes Konzept subjektiv als "Verantwortung" auswählen können. Sie können sagen, dass die Verantwortung der Klasse darin besteht, die Datenpersistenz in der Datenbank zu verwalten, oder dass die Verantwortung darin besteht, die gesamte Arbeit im Zusammenhang mit der Erstellung eines Benutzers auszuführen. Dies sind nur verschiedene Verhaltensebenen der Anwendung, und beide sind gültige konzeptionelle Ausdrücke einer "einzelnen Verantwortung". Daher ist dieses Prinzip für die Lösung Ihres Problems nicht hilfreich.
Das nützlichste Prinzip in diesem Fall ist das Prinzip der geringsten Überraschung . Stellen wir also die Frage: Ist es überraschend, dass ein Repository mit der primären Rolle, Daten in einer Datenbank zu speichern, auch E-Mails sendet?
Ja, es ist sehr überraschend. Dies sind zwei völlig getrennte externe Systeme, und der Name
SaveChanges
bedeutet nicht, dass auch Benachrichtigungen gesendet werden. Die Tatsache, dass Sie dies an ein Ereignis delegieren, macht das Verhalten noch überraschender, da jemand, der den Code liest, nicht mehr leicht erkennen kann, welche zusätzlichen Verhaltensweisen aufgerufen werden. Indirektion beeinträchtigt die Lesbarkeit. Manchmal sind die Vorteile die Kosten für die Lesbarkeit wert, aber nicht, wenn Sie automatisch ein zusätzliches externes System aufrufen, dessen Auswirkungen für Endbenutzer erkennbar sind. (Protokollierung kann hier ausgeschlossen werden, da es sich im Wesentlichen um eine Aufzeichnung zum Debuggen handelt. Endbenutzer verbrauchen das Protokoll nicht, sodass es nicht schadet, immer zu protokollieren.) Schlimmer noch, dies verringert die Flexibilität beim Timing Dies macht es unmöglich, andere Vorgänge zwischen dem Speichern und der Benachrichtigung zu verschachteln.Wenn Ihr Code normalerweise eine Benachrichtigung senden muss, wenn ein Benutzer erfolgreich erstellt wurde, können Sie eine Methode erstellen, die Folgendes ausführt:
Ob dies einen Mehrwert bringt, hängt jedoch von den Besonderheiten Ihrer Anwendung ab.
Ich würde eigentlich die Existenz der
SaveChanges
Methode überhaupt entmutigen . Diese Methode schreibt vermutlich eine Datenbanktransaktion fest, aber andere Repositorys haben möglicherweise die Datenbank in derselben Transaktion geändert . Die Tatsache, dass sie alle festschreiben, ist erneut überraschend, da sieSaveChanges
speziell an diese Instanz des Benutzer-Repositorys gebunden ist.Das einfachste Muster für die Verwaltung einer Datenbanktransaktion ist ein äußerer
using
Block:Dies gibt dem Programmierer eine explizite Kontrolle darüber, wann Änderungen für alle Repositorys gespeichert werden, erzwingt, dass der Code die Abfolge der Ereignisse, die vor einem Commit auftreten müssen, explizit dokumentiert, stellt sicher, dass ein Rollback bei einem Fehler ausgegeben wird (vorausgesetzt, dass
DataContext.Dispose
ein Rollback ausgegeben wird ), und vermeidet, dass sie ausgeblendet werden Verbindungen zwischen stateful Klassen.Ich würde es auch vorziehen, die E-Mail nicht direkt in der Anfrage zu senden. Es wäre robuster, die Notwendigkeit einer Benachrichtigung in einer Warteschlange aufzuzeichnen . Dies würde eine bessere Fehlerbehandlung ermöglichen. Insbesondere wenn beim Versenden der E-Mail ein Fehler auftritt, kann dieser später erneut versucht werden, ohne dass das Speichern des Benutzers unterbrochen wird, und es wird vermieden, dass der Benutzer erstellt wird, die Site jedoch einen Fehler zurückgibt.
Es ist besser, zuerst die Benachrichtigungswarteschlange festzuschreiben, da der Benutzer der Warteschlange überprüfen kann, ob der Benutzer vorhanden ist, bevor er die E-Mail sendet, falls der
context.SaveChanges()
Anruf fehlschlägt. (Andernfalls benötigen Sie eine vollständige Zwei-Phasen-Commit-Strategie, um Heisenbugs zu vermeiden.)Das Endergebnis soll praktisch sein. Denken Sie tatsächlich über die Konsequenzen (sowohl in Bezug auf das Risiko als auch den Nutzen) nach, die das Schreiben von Code auf eine bestimmte Art und Weise hat. Ich finde, dass das Prinzip der "Einzelverantwortung" mir dabei nicht sehr oft hilft, während das Prinzip der geringsten Überraschung mir oft hilft, in den Kopf eines anderen Entwicklers zu geraten (sozusagen) und darüber nachzudenken, was passieren könnte.
quelle
My repository is not sending emails. It just raises an event
Ursache Wirkung. Das Repository löst den Benachrichtigungsprozess aus.Derzeit
SaveChanges
tut zwei Dinge: Es speichert die Änderungen und meldet , dass es so tut. Jetzt möchten Sie noch etwas hinzufügen: E-Mail-Benachrichtigungen senden.Sie hatten die clevere Idee, ein Ereignis hinzuzufügen, das jedoch wegen Verstoßes gegen das Einzelverantwortungsprinzip (Single Responsibility Principle, SRP) kritisiert wurde, ohne zu bemerken, dass es bereits verletzt wurde.
Um eine reine SRP-Lösung zu erhalten, lösen Sie zuerst das Ereignis aus und rufen Sie dann alle Hooks für dieses Ereignis auf, von denen es jetzt drei gibt: Speichern, Protokollieren und endgültiges Senden von E-Mails.
Entweder triggern Sie das Ereignis zuerst oder Sie müssen es ergänzen
SaveChanges
. Ihre Lösung ist eine Mischung aus beiden. Es geht nicht auf die bestehende Verletzung ein, sondern regt an, zu verhindern, dass sie über drei Dinge hinausgeht. Das Umgestalten des vorhandenen Codes zur Einhaltung von SRP erfordert möglicherweise mehr Arbeit als unbedingt erforderlich. Es liegt an Ihrem Projekt, wie weit es mit SRP gehen soll.quelle
Der Code hat bereits die SRP verletzt - dieselbe Klasse war für die Kommunikation mit dem Datenkontext und die Protokollierung verantwortlich.
Sie aktualisieren es einfach, um 3 Verantwortlichkeiten zu haben.
Eine Möglichkeit, die Verantwortung auf 1 zu reduzieren, besteht darin, das zu abstrahieren
_userRepository
. mache es zu einem Befehlssender.Es verfügt über eine Reihe von Befehlen sowie eine Reihe von Listenern. Es bekommt Befehle und sendet sie an seine Zuhörer. Möglicherweise werden diese Listener angewiesen, und vielleicht können sie sogar sagen, dass der Befehl fehlgeschlagen ist (was wiederum an Listener gesendet wird, die bereits benachrichtigt wurden).
Jetzt haben die meisten Befehle möglicherweise nur einen Listener (den Datenkontext). SaveChanges hat vor Ihren Änderungen 2 - den Datenkontext und dann den Logger.
Ihre Änderung fügt dann einen weiteren Listener hinzu, um Änderungen zu speichern, dh neue vom Benutzer erstellte Ereignisse im Ereignisdienst auszulösen.
Dies hat einige Vorteile. Sie können den Protokollierungscode jetzt entfernen, aktualisieren oder replizieren, ohne sich um den Rest Ihres Codes zu kümmern. Sie können beim Speichern von Änderungen weitere Trigger für weitere Dinge hinzufügen, die dies benötigen.
All dies wird entschieden, wenn das
_userRepository
erstellt und verkabelt wird (oder wenn diese zusätzlichen Funktionen im laufenden Betrieb hinzugefügt / entfernt werden; die Möglichkeit, die Protokollierung hinzuzufügen / zu verbessern, während die Anwendung ausgeführt wird, könnte von Nutzen sein).quelle