it-swarm.com.de

Anwendbarkeit des Grundsatzes der Einzelverantwortung

Ich bin kürzlich auf ein scheinbar triviales architektonisches Problem gestoßen. Ich hatte ein einfaches Repository in meinem Code, das so 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 im System viele Aufrufe von _userRepository.Add() und SaveChanges gab, habe ich beschlossen, SaveChanges folgendermaßen 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, die Benachrichtigungen sendet.

Ich wurde jedoch darauf hingewiesen, dass meine Änderung von SaveChanges gegen das Prinzip der Einzelverantwortung verstößt und dass SaveChanges nur Ereignisse speichern 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: nur einige Nebenfunktionen zur Funktion hinzufügen. Und SRP verbietet Ihnen nicht, Protokollierungs- oder Auslöseereignisse in Ihren Funktionen zu verwenden. Es besagt lediglich, dass diese Logik in anderen Klassen gekapselt werden sollte, und es ist in Ordnung, wenn ein Repository diese anderen Klassen aufruft.

40
Andre Borges

Ja, es kann eine gültige Voraussetzung sein, ein Repository zu haben, das bestimmte Ereignisse für bestimmte Aktionen wie Add oder SaveChanges auslöst - und 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 sieht möglicherweise etwas kompliziert aus. Nehmen wir im Folgenden an, dass diese Anforderung im Kontext Ihres Systems vollkommen gerechtfertigt ist.

Also ja , das Codieren der Ereignismechanik sowie der Protokollierung sowie das Speichern in einer Methode verstößt gegen die SRP . In vielen Fällen handelt es sich wahrscheinlich um einen akzeptablen Verstoß, insbesondere wenn niemand jemals die Wartungsverantwortung für "Änderungen speichern" und "Ereignis auslösen" auf verschiedene Teams/Betreuer verteilen möchte. Aber nehmen wir an, eines Tages möchte jemand genau dies tun. Kann es 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 lassen und ein Proxy-Repository zu erstellen, das genau dieselbe Öffentlichkeit hat Schnittstelle, verwendet das ursprüngliche Repository erneut und fügt den Methoden die zusätzliche Ereignismechanik hinzu.

// In EventFiringUserRepo:
public void SaveChanges()
{
  _basicRepo.SaveChanges();
   FireEventsForNewlyAddedUsers();
}

private void FireEventsForNewlyAddedUsers()
{
  foreach (var newUser in _basicRepo.DataContext.GetAddedUsers())
  {
     _eventService.RaiseEvent(new UserCreatedEvent(newUser))
  }
}

Sie können die Proxy-Klasse NotifyingRepository oder ObservableRepository nennen, wenn Sie möchten, nach dem Vorbild von @ Peter hoch gewählte Antwort (was eigentlich nicht sagt, wie das zu lösen ist SRP-Verletzung, nur zu sagen, dass die Verletzung in Ordnung ist).

Die neue und die alte Repository-Klasse sollten beide von einer gemeinsamen Schnittstelle abgeleitet sein, wie z. B. in der klassischen Beschreibung des Proxy-Musters gezeigt .

Initialisieren Sie dann in Ihrem ursprünglichen Code _userRepository durch ein Objekt der neuen Klasse EventFiringUserRepo. Auf diese Weise halten Sie das ursprüngliche Repository von der Ereignismechanik getrennt. Bei Bedarf können Sie das Ereignisauslöserepository 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 Problem anzusprechen: führt das nicht zu Proxys über Proxys über Proxys usw. ? Tatsächlich schafft das Hinzufügen der Ereignismechanik eine Grundlage, um weitere hinzuzufügen Anforderungen vom Typ "E-Mails senden" durch einfaches Abonnieren der Ereignisse, sodass Sie sich auch bei diesen Anforderungen ohne zusätzliche Proxys an die SRP halten. Aber das einzige, was hier einmal hinzugefügt werden muss, ist die Ereignismechanik selbst.

Ob sich diese Art der Trennung im Kontext Ihres Systems wirklich lohnt, müssen Sie und Ihr Prüfer selbst entscheiden. Ich würde die Protokollierung wahrscheinlich 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.

14
Doc Brown

Das Senden einer Benachrichtigung, dass sich der persistente Datenspeicher geändert hat, erscheint beim Speichern sinnvoll.

Natürlich sollten Sie Hinzufügen nicht als Sonderfall behandeln - Sie müssten auch Ereignisse für Ändern und Löschen auslösen. Es ist die spezielle Behandlung des "Add" -Falls, die riecht, den Leser dazu zwingt, zu erklären, warum er riecht, und letztendlich einige Leser des Codes zu dem Schluss führt, dass er gegen SRP verstoßen muss.

Ein "Benachrichtigungs" -Repository, das bei Änderungen abgefragt, geändert und Ereignisse ausgelöst werden kann, ist ein völlig normales Objekt. Sie können erwarten, dass Sie in so ziemlich jedem Projekt mit angemessener Größe mehrere Variationen davon finden.


Aber ist ein "benachrichtigendes" Repository tatsächlich das, was Sie brauchen? Sie haben C # erwähnt: Viele Leute würden zustimmen, dass die Verwendung eines System.Collections.ObjectModel.ObservableCollection<> Anstelle von System.Collections.Generic.List<>, Wenn letzteres alles ist, was Sie brauchen, alle Arten von schlecht und falsch ist, aber nur wenige würden sofort auf SRP verweisen.

Was Sie jetzt tun, ist, Ihren UserList _userRepository Gegen einen ObservableUserCollection _userRepository Zu tauschen. Ob dies die beste Vorgehensweise ist oder nicht, hängt von der Anwendung ab. Obwohl es den _userRepository Ohne Frage erheblich weniger leicht macht, verstößt es meiner bescheidenen Meinung nach nicht gegen SRP.

29
Peter

Ja, es ist ein Verstoß gegen das Prinzip der Einzelverantwortung 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, welche Benutzer eine E-Mail erhalten haben, Fehler, erneutes Senden usw. usw.

Auf diese Weise können Sie mit Fehlern, Abstürzen und dergleichen umgehen und vermeiden, dass Ihr Repository alle Anforderungen erfasst, die die Idee haben, dass Ereignisse auftreten, "wenn etwas in die Datenbank übernommen wird".

Das Repository weiß nicht, dass ein Benutzer, den Sie hinzufügen, ein neuer Benutzer ist. Ihre Verantwortung besteht einfach darin, den Benutzer zu speichern.

Es lohnt sich wahrscheinlich, die folgenden Kommentare zu erweitern.

Das Repository weiß nicht, dass ein Benutzer, den Sie hinzufügen, ein neuer Benutzer ist. Ja, es gibt eine Methode namens Hinzufügen. Die Semantik impliziert, dass alle hinzugefügten Benutzer neue Benutzer sind. Kombinieren Sie alle an Add übergebenen Argumente, bevor Sie Save aufrufen - und Sie erhalten alle neuen Benutzer

Falsch. Sie verschmelzen "Zum Repository hinzugefügt" und "Neu".

"Zum Repository hinzugefügt" bedeutet genau das, was es sagt. Ich kann Benutzer zu verschiedenen Repositorys hinzufügen, entfernen und erneut hinzufügen.

"Neu" ist ein Status 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 datenbankzentriertem Denken zu vermeiden. Sie haben Edge-Case-Prozesse, die dem Repository nicht neue Benutzer hinzufügen, und wenn Sie E-Mails an sie senden, sagt das gesamte Unternehmen: "Natürlich sind dies keine 'neuen' Benutzer! Die tatsächliche Regel lautet X. ""

Diese Antwort ist meiner Meinung nach völlig verfehlt: Das Repo ist genau die zentrale Stelle im Code, die weiß, wann neue Benutzer hinzugefügt werden

Falsch. Aus den oben genannten Gründen ist es 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 zu diesen Anwendungen hinzufügen, wird die E-Mail nicht gesendet.

16
Ewan

Ist das ein gültiger Punkt?

Ja, obwohl es sehr von der Struktur Ihres Codes abhängt. Ich habe nicht den vollständigen Kontext, daher werde ich versuchen, allgemein zu sprechen.

Es scheint mir, dass das Auslösen eines Ereignisses hier im Wesentlichen dasselbe ist wie das Protokollieren: nur einige Nebenfunktionen zur Funktion hinzufügen.

Es ist absolut nicht. Die Protokollierung ist nicht Teil des Geschäftsablaufs, kann deaktiviert werden, sollte keine (geschäftlichen) Nebenwirkungen verursachen und den Status und die Gesundheit Ihrer Anwendung in keiner Weise beeinflussen, selbst wenn Sie aus irgendeinem Grund nicht in der Lage waren, zu protokollieren alles mehr. Vergleichen Sie das nun mit der Logik, die Sie hinzugefügt haben.

Und SRP verbietet Ihnen nicht, Protokollierungs- oder Auslöseereignisse in Ihren Funktionen zu verwenden. Es besagt lediglich, dass diese Logik in anderen Klassen gekapselt werden sollte, und es ist in Ordnung, wenn ein Repository diese anderen Klassen aufruft.

SRP arbeitet zusammen mit ISP (S und I in SOLID). Sie haben am Ende viele Klassen und Methoden, die sehr spezifische Dinge tun und sonst nichts. Sie sind sehr fokussiert, sehr einfach zu aktualisieren oder zu ersetzen und im Allgemeinen einfach 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 gesagt haben, 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 bei jeder Erstellung eines Benutzers unbedingt eine Benachrichtigung senden 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, der eine Methode, Add(user), das sowohl den Speicher (den Aufruf Ihres Repositorys) als auch die Benachrichtigung als einzelne Geschäftsaktion behandelt. Oder machen Sie es in Ihrem Original-Snippet nach _userRepository.SaveChanges();

7
async

Bei SRP geht es theoretisch um Menschen , wie Onkel Bob in seinem Artikel The Single Responsibility Principle erklärt. Vielen Dank an Robert Harvey für die Bereitstellung in Ihrem Kommentar.

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 SRP. Ansonsten ist es so.

4
user949300

Obwohl es technisch gesehen nichts auszusetzen hat, wenn Repositorys Ereignisse benachrichtigen, würde ich vorschlagen, sie unter funktionalen Gesichtspunkten zu betrachten, bei denen die Bequemlichkeit einige Bedenken aufwirft.

Einen Benutzer erstellen, entscheiden, was ein neuer Benutzer ist und seine Beständigkeit sind 3 verschiedene Dinge.

Prämisse von mir

Berücksichtigen Sie die vorherige Prämisse, bevor Sie entscheiden, ob das Repository der richtige Ort ist, um Geschäftsereignisse zu benachrichtigen (unabhängig vom SRP). Beachten Sie, dass ich Geschäftsereignis sagte, weil UserCreated für mich eine andere Konnotation hat als UserStored oder UserAdded 1. Ich würde auch betrachten, dass jede dieser Veranstaltungen an unterschiedliche Zielgruppen gerichtet ist.

Auf der einen Seite ist das Erstellen von Benutzern eine geschäftsspezifische Regel, die möglicherweise eine Persistenz beinhaltet oder nicht. Dies kann mehr Geschäftsvorgänge und mehr Datenbank-/Netzwerkvorgänge beinhalten. Operationen, 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(); den Benutzer erfolgreich persistiert hat. Dies hängt von der Transaktionsspanne der Datenbank ab. Dies könnte beispielsweise für Datenbanken wie MongoDB zutreffen, bei denen es sich um atomare Transaktionen handelt, für herkömmliche RDBMS, die ACID-Transaktionen implementieren, bei denen möglicherweise mehr Transaktionen beteiligt sind und noch festgeschrieben werden müssen.

Ist das ein gültiger Punkt?

Es könnte sein. Ich wage jedoch zu sagen, dass es nicht nur um SRP (technisch gesehen) geht, sondern auch um Bequemlichkeit (funktional gesehen).

  • Ist es bequem, Geschäftsereignisse von Komponenten auszulösen, die nicht über die laufenden Geschäftsvorgänge informiert sind ?
  • Stellen sie den richtigen Ort genauso dar wie den richtigen Moment dafür ?
  • Soll ich diesen Komponenten erlauben, meine Geschäftslogik durch solche Benachrichtigungen zu orchestrieren ?
  • Könnte ich die durch vorzeitige Ereignisse verursachten Nebenwirkungen ungültig machen ? 2

Es scheint mir, dass das Auslösen eines Ereignisses hier im Wesentlichen dasselbe ist wie das Protokollieren

Absolut nicht. Die Protokollierung soll jedoch keine Nebenwirkungen haben, da Sie vorgeschlagen haben, dass das Ereignis UserCreated wahrscheinlich andere Geschäftsvorgänge verursacht. Wie Benachrichtigungen. 3

es heißt nur, dass eine solche Logik in anderen Klassen gekapselt sein sollte, und es ist in Ordnung, wenn ein Repository diese anderen Klassen aufruft

Nicht unbedingt wahr. SRP ist nicht nur ein klassenspezifisches Problem. Es arbeitet auf verschiedenen Abstraktionsebenen wie Ebenen, Bibliotheken und Systemen! Es geht um Zusammenhalt, darum, zusammenzuhalten, was sich aus den gleichen Gründen ändert durch die Hand der gleichen Stakeholder . Wenn sich die Benutzererstellung ( Anwendungsfall) ändert, ändert sich wahrscheinlich auch der Moment und die Gründe für das Eintreten des Ereignisses.


1: Dinge angemessen zu benennen ist auch wichtig.

2: Angenommen, wir haben UserCreated nach _dataContext.SaveChanges(); gesendet, aber die gesamte Datenbanktransaktion ist später aufgrund von Verbindungsproblemen oder Verstößen gegen Einschränkungen fehlgeschlagen. Seien Sie vorsichtig bei der vorzeitigen Ausstrahlung von Ereignissen, da deren Nebenwirkungen schwer rückgängig zu machen sind (wenn dies überhaupt möglich ist).

3: Benachrichtigungsprozesse, die nicht angemessen behandelt werden, können dazu führen, dass Sie Benachrichtigungen auslösen, die nicht rückgängig gemacht werden können

2
Laiv

Mach dir keine Sorgen über das Prinzip der Einzelverantwortung. Es wird Ihnen hier nicht helfen, eine gute Entscheidung zu treffen, da Sie ein bestimmtes Konzept subjektiv als "Verantwortung" auswählen können. Sie könnten sagen, dass die Verantwortung der Klasse darin besteht, die Datenpersistenz für die Datenbank zu verwalten, oder Sie könnten sagen, dass ihre Verantwortung darin besteht, alle Arbeiten im Zusammenhang mit der Erstellung eines Benutzers auszuführen. Dies sind nur verschiedene Ebenen des Verhaltens der Anwendung, und beide sind gültige konzeptionelle Ausdrücke einer "einzelnen Verantwortung". Dieses Prinzip ist also 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 vollständig 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 Lesbarkeitskosten wert, aber nicht, wenn Sie automatisch ein zusätzliches externes System aufrufen, dessen Auswirkungen für Endbenutzer erkennbar sind. (Die Protokollierung kann hier ausgeschlossen werden, da sie im Wesentlichen das Führen von Aufzeichnungen zu Debugging-Zwecken bewirkt. Endbenutzer verwenden das Protokoll nicht, sodass die ständige Protokollierung keinen Schaden anrichtet.) Schlimmer noch, dies verringert die Flexibilität beim Timing des Versendens der E-Mail, wodurch es unmöglich wird, 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 dies tut:

public void AddUserAndNotify(IUserRepository repo, IEmailNotification notifier, MyUser user)
{
    repo.Add(user);
    repo.SaveChanges();
    notifier.SendUserCreatedNotification(user);
}

Ob dies einen Mehrwert bietet, hängt jedoch von den Besonderheiten Ihrer Anwendung ab.


Ich würde 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 alle festgeschrieben werden, ist erneut überraschend, da SaveChanges speziell an diese Instanz des Benutzer-Repositorys gebunden ist.

Das einfachste Muster zum Verwalten einer Datenbanktransaktion ist ein äußerer using Block:

using (DataContext context = new DataContext())
{
    _userRepository.Add(context, user);
    context.SaveChanges();
    notifier.SendUserCreatedNotification(user);
}

Dies gibt dem Programmierer eine explizite Kontrolle darüber, wann Änderungen für all Repositorys gespeichert werden, zwingt den Code, die Abfolge von Ereignissen, die vor einem Commit auftreten müssen, explizit zu dokumentieren, und stellt sicher, dass bei einem Fehler ein Rollback ausgegeben wird (vorausgesetzt dass DataContext.Dispose ein Rollback ausgibt) und versteckte Verbindungen zwischen statusbehafteten Klassen vermeidet.

Ich würde es auch vorziehen, die E-Mail nicht direkt in der Anfrage zu senden. Es wäre robuster, den Bedarf für eine Benachrichtigung in einer Warteschlange aufzuzeichnen. Dies würde eine bessere Fehlerbehandlung ermöglichen. Insbesondere wenn beim Senden der E-Mail ein Fehler auftritt, kann dieser später erneut versucht werden, ohne das Speichern des Benutzers zu unterbrechen, und es wird vermieden, dass der Benutzer erstellt wird, die Site jedoch einen Fehler zurückgibt.

using (DataContext context = new DataContext())
{
    _userRepository.Add(context, user);
    _emailNotificationQueue.AddUserCreateNotification(user);
    _emailNotificationQueue.Commit();
    context.SaveChanges();
}

Es ist besser, zuerst die Benachrichtigungswarteschlange festzuschreiben, da der Verbraucher der Warteschlange vor dem Senden der E-Mail überprüfen kann, ob der Benutzer vorhanden ist, falls der Aufruf von context.SaveChanges() 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 Risiko als auch Nutzen) nach, wenn Sie Code auf eine bestimmte Weise schreiben. Ich finde, dass das "Prinzip der Einzelverantwortung" mir nicht sehr oft dabei hilft, während das "Prinzip der geringsten Überraschung" mir oft hilft, in den Kopf eines anderen Entwicklers zu gelangen (sozusagen) und darüber nachzudenken, was passieren könnte.

1
jpmc26

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 und dann in die Diskussion darüber verwickelt werden sollte, 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 Mail ändert. Der Kommunikationsexperte kann verlangen, dass sich der Wortlaut der Mail ändert. Und der UX-Experte könnte entscheiden, dass die E-Mail tatsächlich an einem anderen Punkt im Onboarding-Ablauf gesendet werden soll. Daher unterliegt die Klasse mehreren Anforderungsänderungen aus unabhängigen Quellen. Dies verstößt gegen die 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 Bedenken abhängt. Ereignisse sind eine wirklich gute Möglichkeit, die SRP aufrechtzuerhalten, da Sie eine E-Mail durch das Speichern auslösen lassen können, ohne dass das Repository von der E-Mail betroffen ist oder überhaupt davon weiß.

1
JacquesB

Derzeit macht SaveChangeszwei Dinge: Es speichert die Änderungen nd protokolliert, dass es dies tut. Jetzt möchten Sie noch etwas hinzufügen: E-Mail-Benachrichtigungen senden.

Sie hatten die clevere Idee, ein Ereignis hinzuzufügen, aber dies wurde wegen Verstoßes gegen das Single Responsibility Principle (SRP) kritisiert, 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 auf Alle Hooks für dieses Ereignis, von denen es jetzt drei gibt: Speichern, Protokollieren und schließlich Senden von E-Mails.

Entweder Sie lösen das Ereignis zuerst aus oder Sie müssen SaveChanges hinzufügen. Ihre Lösung ist eine Mischung aus beidem. Es geht nicht auf den bestehenden Verstoß ein, sondern ermutigt ihn, zu verhindern, dass er über drei Dinge hinaus zunimmt. Das Umgestalten des vorhandenen Codes zur Einhaltung von SRP erfordert möglicherweise mehr Arbeit als unbedingt erforderlich. Es liegt an Ihrem Projekt, wie weit sie SRP bringen wollen.

0
CJ Dennis