Vermeiden eines anämischen Domänenmodells - ein echtes Beispiel


76

Ich versuche, anämische Domänenmodelle zu verstehen und warum sie angeblich ein Anti-Muster sind.

Hier ist ein Beispiel aus der Praxis.

Ich habe eine Mitarbeiterklasse, die eine Menge Eigenschaften hat - Name, Geschlecht, Benutzername usw.

public class Employee
{
    public string Name { get; set; }
    public string Gender { get; set; }
    public string Username { get; set; }
    // Etc.. mostly getters and setters
}

Als nächstes haben wir ein System, bei dem eingehende Telefonanrufe und Website-Anfragen (als "Leads" bezeichnet) unter den Vertriebsmitarbeitern gleichmäßig rotiert werden. Dieses System ist recht komplex, da es Round-Robin-Anfragen, die Überprüfung auf Feiertage, Mitarbeiterpräferenzen usw. umfasst. Daher ist dieses System derzeit in einen Dienst unterteilt: EmployeeLeadRotationService.

public class EmployeeLeadRotationService : IEmployeeLeadRotationService
{
     private IEmployeeRepository _employeeRepository;
     // ...plus lots of other injected repositories and services

     public void SelectEmployee(ILead lead)
     {
         // Etc. lots of complex logic
     }
}

Dann haben wir auf der Rückseite unseres Website-Anfrageformulars folgenden Code:

public void SubmitForm()
{
    var lead = CreateLeadFromFormInput();

    var selectedEmployee = Kernel.Get<IEmployeeLeadRotationService>()
                                 .SelectEmployee(lead);

    Response.Write(employee.Name + " will handle your enquiry. Thanks.");
}

Ich habe nicht wirklich viele Probleme mit diesem Ansatz, aber angeblich ist dies etwas, vor dem ich schreien sollte, weil es ein anämisches Domänenmodell ist .

Aber für mich ist nicht klar, wohin die Logik im Lead-Rotationsdienst gehen soll. Sollte es in Führung gehen? Sollte es in den Mitarbeiter gehen?

Was ist mit all den injizierten Repositorys usw., die der Rotationsdienst benötigt - wie würden sie in den Mitarbeiter injiziert, da wir im Umgang mit einem Mitarbeiter die meiste Zeit keine dieser Repositorys benötigen?


Wie sieht ein also ILeadaus, wenn es nicht offensichtlich ist, .SelectEmployee () darin zu platzieren?
Simon Buchan

In diesem Fall handelt es sich um eine Webanfrage, die eine Eigenschaft für Kommentare usw. enthält. Wir haben jedoch auch telefonische Anfragen, Anwendungen, Angebote usw., die sich alle geringfügig unterscheiden. Die ILead-Schnittstelle hätte Eigenschaften wie LocationOfLead, TimeOfLead usw.
cbp

1
Ich denke, es ist offensichtlicher, .SelectEmployee () an die Spitze zu setzen als den Mitarbeiter, aber das geht nicht auf die anderen Bedenken ein: das Einfügen abhängiger Repositorys; das Fehlen von SoC; Die Gesamtkomplexität, den gesamten SelectEmployee-Code in der Lead-Klasse zu haben (tatsächlich benötigen wir eine LeadBase-Klasse, damit wir den Code nicht in allen ererbenden Lead-Klassen wiederverwenden), wenn wir uns häufig (z. B. während der Berichterstellung) befinden wirklich nicht daran interessiert, wie der Mitarbeiter ausgewählt wurde.
cbp

Könnte von Interesse sein: medium.com/@wrong.about/…
Vadim Samokhin

Antworten:


55

In diesem Fall handelt es sich nicht um ein anämisches Domänenmodell. Bei einem anämischen Domänenmodell geht es speziell um die Validierung und Transformation der Objekte . Ein Beispiel hierfür wäre, wenn eine externe Funktion den Status der Mitarbeiter tatsächlich ändert oder deren Details aktualisiert.

In diesem Fall nehmen Sie alle Mitarbeiter und wählen anhand ihrer Informationen einen aus. Es ist in Ordnung, ein separates Objekt zu haben, das andere untersucht und Entscheidungen darüber trifft, was es findet. Es ist NICHT in Ordnung, ein Objekt zu haben, mit dem ein Objekt von einem Zustand in einen anderen überführt wird.

Ein Beispiel für ein anämisches Domänenmodell in Ihrem Fall wäre eine externe Methode

updateHours(Employee emp) // updates the working hours for the employee

Dadurch wird ein Mitarbeiterobjekt verwendet und die für die Woche geleisteten Arbeitsstunden aktualisiert. Dabei wird sichergestellt, dass Flags gesetzt werden, wenn die Stunden einen bestimmten Grenzwert überschreiten. Das Problem dabei ist, dass Sie, wenn Sie nur Mitarbeiterobjekte haben, nicht wissen, wie Sie deren Stunden innerhalb der richtigen Einschränkungen ändern können. In diesem Fall besteht die Möglichkeit darin, die updateHours-Methode in die Employee-Klasse zu verschieben. Das ist der Kern des Anti-Musters des Anemic Domain Model.


Was aber, wenn der Mitarbeiter ein beständiges Objekt für die Datenbank ist? Warum sollte ich dort eine Methode einfügen? Dieselbe Frage gilt für DTOs, in denen Sie keine Methoden einfügen. Wo würden Sie dann die updateHours-Methode platzieren?
Pascal

updateHoursgehört in die Employee-Klasse. Sie sollten alle Daten übergeben, die zum Aktualisieren der Stunden erforderlich sind, z. B. die abgeschlossene Aufgabe. Collaborator-Objekte sind ebenfalls in Ordnung, vorzugsweise jedoch keine Dienste.
MauganRa

30

Ich denke, Ihr Design ist hier in Ordnung. Wie Sie wissen, ist das Anti-Pattern des anämischen Domänenmodells eine Gegenreaktion gegen den Trend, in Domänenobjekten codiertes Verhalten zu vermeiden. Umgekehrt bedeutet dies jedoch nicht, dass das gesamte Verhalten in Bezug auf ein Domänenobjekt von diesem Objekt gekapselt werden muss.

Als Faustregel gilt, dass Verhalten, das eng mit dem Domänenobjekt verbunden ist und vollständig in Bezug auf diese eine Domänenobjektinstanz definiert ist, in das Domänenobjekt aufgenommen werden kann. Um die Verantwortlichkeiten klar zu halten, ist es ansonsten am besten, sie extern in einen Mitarbeiter / Dienst zu stellen, wie Sie es getan haben.


5
genau. Dies ist wirklich ein externes Modul (LeadQueueManager oder was auch immer) mit viel interner Logik - dies ist absolut kein anämisches Domänenmodell. Was weiß ein Mitarbeiter über die Planung von Anrufwarteschlangen? Nichts;)
TomTom

14

Es ist alles in Ihrem Kopf - betrachten Sie den Rotationsdienst als Teil des Domänenmodells und das Problem löst sich auf.

Bei der Rotation müssen Informationen über viele Mitarbeiter gespeichert werden, damit sie weder zu einem Lead noch zu einem einzelnen Mitarbeiterobjekt gehören. Es verdient, ein Domänenobjekt an sich zu sein.

Das Umbenennen von "RotationService" in "Organization.UserSupportDepartment" macht dies deutlich.


0

Wenn Ihr Domänenmodell nur Rollen und Dinge enthält, keine Aktivitäten als Verhalten, ist es anämisch. Ich spreche jedoch von Verhalten in Bezug auf ein Modell, kein Objekt . Ich spreche in einer anderen Antwort über den Unterschied zwischen ihnen ... https://stackoverflow.com/a/31780937/116442

Mit Ihrer Frage brechen Sie meine ersten beiden Modellierungsregeln für die Domänenanalyse: -

  1. Verhalten modelliert als (aufgezeichnete) Aktivitäten bilden das Herzstück eines Domänenmodells. Fügen Sie sie zuerst hinzu.
  2. Modelldomänenaktivitäten als Klassen, nicht als Methoden.

Ich würde dem Modell eine Aktivität "Anfrage" hinzufügen. Damit hat das Modell Verhalten und kann ohne externen Controller oder Skript kombiniert und als Gruppe von Objekten arbeiten.

EnquiryHandlerModel

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.