5

Ich arbeite zur Zeit mit ASP .NET Core 1.0 mit Entity Framework Core. Ich habe einige komplexe Berechnungen mit Daten aus der Datenbank und ich bin nicht sicher, wie man eine richtige Architektur ohne Aufbau eines anämischen Domänenmodell (http://www.martinfowler.com/bliki/AnemicDomainModel.html)Domain Model und zugehörige Daten (Anemic Domain Model)

(vereinfacht) Beispiel für die Verwendung Dependency Injection bauen:

ich habe folgende Einheiten:

public class Project { 
    public int Id {get;set;} 
    public string Name {get;set;}   
} 

public class TimeEntry 
{ 
    public int Id {get;set;} 
    public DateTime Date {get;set;} 
    public int DurationMinutes {get;set;} 
    public int ProjectId {get;set;} 
    public Project Project {get;set;}   
} 

public class Employee { 
    public int Id {get;set;} 
    public string Name {get;set;} 
    public List<TimeEntry> TimeEntries {get;set;} 
} 

ich möchte einige komplexe Berechnungen tun, um eine monatliche Timesheet zu berechnen. Da ich nicht auf die Datenbank innerhalb der Employee-Entität zugreifen kann, berechne ich das TimeSheet in einer EmployeeService.

public class EmployeeService { 
    private DbContext _db; 
    public EmployeeService(DbContext db) { 
     _db = db; 
    } 

    public List<CalculatedMonth> GetMonthlyTimeSheet(int employeeId) { 
     var employee = _db.Employee.Include(x=>x.TimeEntry).ThenInclude(x=>x.Project).Single(); 
     var result = new List<CalculatedMonth>(); 

     //complex calculation using TimeEntries etc here 

     return result; 
    } 
} 

Wenn ich die Timesheet erhalten will, muss ich die EmployeeService injizieren und GetMonthlyTimeSheet nennen.

Also - ich am Ende mit vielen GetThis() und GetThat() Methoden in meinem Service, obwohl diese Methoden würde perfekt in die Employee Klasse selbst passen.

Was ich will, erreichen, ist so etwas wie:

public class Employee { 
    public int Id {get;set;} 
    public string Name {get;set;} 
    public List<TimeEntry> TimeEntries {get;set;} 

    public List<CalculatedMonth> GetMonthlyTimeSheet() {    
     var result = new List<CalculatedMonth>(); 

     //complex calculation using TimeEntries etc here 

     return result; 
    } 
} 

public IActionResult GetTimeSheets(int employeeId) { 
    var employee = _employeeRepository.Get(employeeId); 
    return employee.GetTimeSheets(); 
} 

... aber, dass ich sicherstellen müssen, dass die Liste der TimeEntries aus der Datenbank gefüllt wird (EF-Core nicht verzögertes Laden nicht unterstützt) . Ich möchte nicht .Include (x => y) alles bei jeder Anfrage, weil ich manchmal nur den Namen des Mitarbeiters ohne die Zeiteinträge benötigt und es würde die Leistung der Anwendung beeinträchtigen.

Kann mir jemand in eine Richtung zeigen, wie man das richtig baut?

Edit: Eine Möglichkeit (aus den Kommentaren der ersten Antwort) wäre:

public class Employee { 
    public int Id {get;set;} 
    public string Name {get;set;} 
    public List<TimeEntry> TimeEntries {get;set;} 

    public List<CalculatedMonth> GetMonthlyTimeSheet() { 
     if (TimeEntries == null) 
      throw new PleaseIncludePropertyException(nameof(TimeEntries)); 

     var result = new List<CalculatedMonth>(); 

     //complex calculation using TimeEntries etc here 

     return result; 
    } 
} 

public class EmployeeService { 
    private DbContext _db; 
    public EmployeeService(DbContext db) { 
     _db = db; 
    } 

    public Employee GetEmployeeWithoutData(int employeeId) { 
     return _db.Employee.Single(); 
    } 

    public Employee GetEmployeeWithData(int employeeId) { 
     return _db.Employee.Include(x=>x.TimeEntry).ThenInclude(x=>x.Project).Single(); 
    } 
} 

public IActionResult GetTimeSheets(int employeeId) { 
    var employee = _employeeService.GetEmployeeWithData(employeeId); 
    return employee.GetTimeSheets(); 
} 
+0

Der erste Schritt, um in das Anemic-Domänenmodell zu gelangen, besteht darin, Ihre Setter 'privat' zu machen und korrekte Konstruktoren der Objekte mit Parametervalidierung zu erstellen. Auch wenn Sie mit EF arbeiten, benötigen Sie mindestens einen 'geschützten' Konstruktor, wenn Ihre 'öffentlichen' Konstruktoren nicht parameterlos sind, wie ich es vorschlage. –

+0

Ich denke, es ist Arbeit, wenn man bedenkt, wie Sie Ihr Modell testen können. Den Code testbar zu machen, ist oft, IMHO, eine ausgezeichnete Möglichkeit, Design-Probleme und ihre Lösungen herauszufordern. Sind Sie zum Beispiel froh, dass Sie Ihren 'EmployeeService' mit dem obigen Design testen (Einheit oder Integration) können? –

+0

@ Jetro223 wie Sie es vorgestellt haben, ist Ihr Modell ** anämisch, ob Sie es wollen oder nicht. – guillaume31

Antwort

0

Wenn ich Ihre Frage richtig verstanden Sie einen Trick mit Injektion eines Service in Ihre Einheiten verwenden können, die hilft es die Arbeit machen, zB:

public class Employee() 
{ 
    public object GetTimeSheets(ICalculatorHelper helper) 
    { 
    } 
} 

Dann in Ihrem Dienst, der die Mitarbeiter hält man es im Konstruktor erhalten würde und für die Berechnungen an die Arbeitnehmer-Klasse übergeben. Dieser Dienst kann eine Fassade sein, z.B. um alle Daten zu erhalten und die Initialisierung durchzuführen oder was auch immer Sie wirklich brauchen.

Was die TimeEntries, können Sie sie mit einer Funktion wie diese:

private GetTimeEntries(ICalculationHelper helper) 
{ 
    if (_entries == null) 
    { 
     _entries = helper.GetTimeEntries(); 
    } 
    return _entries; 
} 

Es ist natürlich auf Sie Strategie der Caching hängt und so weiter, wenn dieses Muster zu Ihnen passt.

Persönlich finde ich es ziemlich einfach mit anämischen Klassen zu arbeiten und habe viel Geschäftslogik in Diensten. Ich gebe einige in die Objekte, wie z.B. Berechnung von FullName aus Vorname und Nachname. Normalerweise Sachen, die keine anderen Dienste beinhalten. Obwohl es eine Frage der Präferenz ist.

+0

Und wie hilft der ICalculatorHelper hier? Sein Problem besteht darin, dass die Daten in das Objekt geladen werden können oder nicht. –

+0

@HristoYankov: es hilft insofern, dass er sich nicht darum kümmern muss, wie man die Daten holt, das ist Aufgabe des 'ICalculatorHelper' (oder eines anderen benannten Dienstes) und er kann die Logik für die Berechnung entweder innerhalb des Modells behalten (wenn es spezifisch für das Modell ist) oder in dem Taschenrechner-Helfer, wenn es außerhalb des Bereichs des gegebenen Modells ist – Tseng

+0

Das ist einfach falsch. Die Daten sollen bereits in der Klasse sein. Die Klasse sollte die Daten nicht zweimal empfangen - einmal von einer Eigenschaft und ein anderes Mal von einer Abhängigkeitsabhängigkeit des Parameters. –

3

Versuchen Sie nicht, Abfrageprobleme mit Ihren Aggregaten zu lösen.Ihre Aggregate sind dazu gedacht, Befehle zu verarbeiten und Invarianten zu schützen. Sie bilden eine Konsistenzgrenze um eine Datenmenge.

Ist das Objekt Employee für den Schutz der Integrität der Arbeitszeittabelle eines Mitarbeiters verantwortlich? Wenn nicht, gehören diese Daten nicht in die Klasse Employee.

Lazy-Loading kann für CRUD-Modelle in Ordnung sein, wird jedoch normalerweise als Anti-Pattern betrachtet, wenn wir Aggregate entwerfen, weil diese so klein und zusammenhängend wie möglich sein sollten.

Treffen Sie geschäftliche Entscheidungen basierend auf dem berechneten Ergebnis aus Arbeitszeittabellen? Gibt es Invarianten zu schützen? Spielt es eine Rolle, ob die Entscheidung über veraltete Stundenzettel getroffen wurde? Wenn die Antwort auf diese Fragen nein ist, dann ist Ihre Berechnung wirklich nichts mehr als eine Abfrage.

Platzieren von Abfragen in Serviceobjekten ist in Ordnung. Diese Dienstobjekte können sogar außerhalb des Domänenmodells (z. B. in der Anwendungsschicht) existieren, aber es gibt keine strikte Regel, die zu befolgen ist. Sie können auch einige Aggregate laden, um auf die erforderlichen Daten zur Verarbeitung der Berechnungen zuzugreifen. In der Regel ist es jedoch besser, direkt in die Datenbank zu wechseln. Dies ermöglicht eine bessere Trennung zwischen Ihren liest & schreibt (CQRS).

Verwandte Themen