2012-06-13 8 views
9

Ich bin ziemlich neu in DDD Welt und nachdem ich einige Bücher darüber gelesen (Evans DDD unter ihnen) Ich konnte nicht die Antwort auf meine Frage im Internet finden: Was ist der richtige Weg Erstellen von untergeordneten Entitäten mit DDD? Sie sehen, viele Informationen im Internet funktionieren auf einer einfachen Ebene. Aber Teufel in den Details und sie sind immer in Dutzenden von DDD-Proben aus Gründen der Einfachheit weggelassen.Proper Art der Erstellung von Kind-Entitäten mit DDD

Ich komme aus my own answer auf Similair Frage hier auf Stackoverflow. Ich bin nicht ganz mit meiner eigenen Vision zu diesem Problem zufrieden, also dachte ich, ich müsste das genauer ausführen.

Zum Beispiel muss ich ein einfaches Modell erstellen, die Autos Namen darstellen: Unternehmen, Modell und Modifikation (zum Beispiel, Nissan Teana 2012 - das wird "Nissan" Unternehmen, "Teana" Modell und "2012" Modifikation).

Die Skizze des Modells Ich möchte so aussieht erstellen:

CarsCompany 
{ 
    Name 
    (child entities) Models 
} 

CarsModel 
{ 
    (parent entity) Company 
    Name 
    (child entities) Modifications 
} 


CarsModification 
{ 
    (parent entity) Model 
    Name 
} 

So, jetzt muss ich Code erstellen. Ich verwende C# als Sprache und NHibernate als ORM. Dies ist wichtig und wird normalerweise nicht in umfangreichen DDD-Beispielen im Internet angezeigt.

Der erste Ansatz.

Ich beginne mit einfachen Ansatz mit typischen Objekterstellung über Factory-Methoden.

public class CarsCompany 
{ 
    public virtual string Name { get; protected set; } 
    public virtual IEnumerable<CarsModel> Models { get { return new ImmutableSet<CarsModel> (this._models); } } 


    private readonly ISet<CarsModel> _models = new HashedSet<CarsModel>(); 


    protected CarsCompany() 
    { 
    } 


    public static CarsCompany Create (string name) 
    { 
     if (string.IsNullOrEmpty (name)) 
      throw new ArgumentException ("Invalid name specified."); 

     return new CarsCompany 
     { 
      Name = name 
     }; 
    } 


    public void AddModel (CarsModel model) 
    { 
     if (model == null) 
      throw new ArgumentException ("Model is not specified."); 

     this._models.Add (model); 
    } 
} 


public class CarsModel 
{ 
    public virtual CarsCompany Company { get; protected set; } 
    public virtual string Name { get; protected set; } 
    public virtual IEnumerable<CarsModification> Modifications { get { return new ImmutableSet<CarsModification> (this._modifications); } } 


    private readonly ISet<CarsModification> _modifications = new HashedSet<CarsModification>(); 


    protected CarsModel() 
    { 
    } 


    public static CarsModel Create (CarsCompany company, string name) 
    { 
     if (company == null) 
      throw new ArgumentException ("Company is not specified."); 

     if (string.IsNullOrEmpty (name)) 
      throw new ArgumentException ("Invalid name specified."); 

     return new CarsModel 
     { 
      Company = company, 
      Name = name 
     }; 
    } 


    public void AddModification (CarsModification modification) 
    { 
     if (modification == null) 
      throw new ArgumentException ("Modification is not specified."); 

     this._modifications.Add (modification); 
    } 
} 


public class CarsModification 
{ 
    public virtual CarsModel Model { get; protected set; } 
    public virtual string Name { get; protected set; } 


    protected CarsModification() 
    { 
    } 


    public static CarsModification Create (CarsModel model, string name) 
    { 
     if (model == null) 
      throw new ArgumentException ("Model is not specified."); 

     if (string.IsNullOrEmpty (name)) 
      throw new ArgumentException ("Invalid name specified."); 

     return new CarsModification 
     { 
      Model = model, 
      Name = name 
     }; 
    } 
} 

Das Schlimme an diesem Ansatz ist, dass die Schaffung des Modells nicht an die übergeordneten Modell Sammlung nicht hinzugefügt:

using (var tx = session.BeginTransaction()) 
{ 
    var company = CarsCompany.Create ("Nissan"); 

    var model = CarsModel.Create (company, "Tiana"); 
    company.AddModel (model); 
    // (model.Company == company) is true 
    // but (company.Models.Contains (model)) is false 

    var modification = CarsModification.Create (model, "2012"); 
    model.AddModification (modification); 
    // (modification.Model == model) is true 
    // but (model.Modifications.Contains (modification)) is false 

    session.Persist (company); 
    tx.Commit(); 
} 

Nachdem die Transaktion verpflichtet ist und die Sitzung gespült wird, wird die ORM schreibe alles korrekt in die Datenbank und wenn wir das nächste Mal diese Firma laden, wird die Model Collection unser Modell korrekt halten. Das Gleiche gilt für die Modifikation. Bei diesem Ansatz bleibt unsere übergeordnete Entität in einem inkonsistenten Zustand, bis sie von der Datenbank erneut geladen wird. No Go.

Der zweite Ansatz.

Dieses Mal werden wir die sprachspezifische Option verwenden, um das Problem der Einstellung geschützter Eigenschaften anderer Klassen zu lösen - nämlich den Modifier "protected internal" für Setter und Konstruktor.

Dieses Mal wird bei jeder Entitätserstellung sowohl die übergeordnete als auch die untergeordnete Entität im konsistenten Zustand belassen. Aber die Validierung des untergeordneten Entitätsstatus ist in die übergeordnete Entität gelaufen (AddModel und AddModification Methoden). Da ich kein DDD-Experte bin, bin ich mir nicht sicher, ob es in Ordnung ist oder nicht. Es könnte in Zukunft weitere Probleme verursachen, wenn untergeordnete Entitätseigenschaften nicht einfach über Eigenschaften festgelegt werden könnten und das Einrichten eines Zustands basierend auf übergebenen Parametern eine komplexere Arbeit erfordern würde, die der Eigenschaft den Parameterwert zuweist. Ich hatte den Eindruck, dass wir die Logik der Entität in dieser Entität konzentrieren sollten, wo immer es möglich ist. Für mich verwandelt dieser Ansatz Elternobjekt in eine Art Entity & Factory Hybrid.

Der dritte Ansatz.

Ok, wir werden die Verantwortung für die Pflege der Eltern-Kind-Beziehungen invertieren.

public class CarsCompany 
{ 
    public virtual string Name { get; protected set; } 
    public virtual IEnumerable<CarsModel> Models { get { return new ImmutableSet<CarsModel> (this._models); } } 


    private readonly ISet<CarsModel> _models = new HashedSet<CarsModel>(); 


    protected CarsCompany() 
    { 
    } 


    public static CarsCompany Create (string name) 
    { 
     if (string.IsNullOrEmpty (name)) 
      throw new ArgumentException ("Invalid name specified."); 

     return new CarsCompany 
     { 
      Name = name 
     }; 
    } 


    protected internal void AddModel (CarsModel model) 
    { 
     this._models.Add (model); 
    } 
} 


public class CarsModel 
{ 
    public virtual CarsCompany Company { get; protected set; } 
    public virtual string Name { get; protected set; } 
    public virtual IEnumerable<CarsModification> Modifications { get { return new ImmutableSet<CarsModification> (this._modifications); } } 


    private readonly ISet<CarsModification> _modifications = new HashedSet<CarsModification>(); 


    protected CarsModel() 
    { 
    } 


    public static CarsModel Create (CarsCompany company, string name) 
    { 
     if (company == null) 
      throw new ArgumentException ("Company is not specified."); 

     if (string.IsNullOrEmpty (name)) 
      throw new ArgumentException ("Invalid name specified."); 

     var model = new CarsModel 
     { 
      Company = company, 
      Name = name 
     }; 

     model.Company.AddModel (model); 

     return model; 
    } 


    protected internal void AddModification (CarsModification modification) 
    { 
     this._modifications.Add (modification); 
    } 
} 


public class CarsModification 
{ 
    public virtual CarsModel Model { get; protected set; } 
    public virtual string Name { get; protected set; } 


    protected CarsModification() 
    { 
    } 


    public static CarsModification Create (CarsModel model, string name) 
    { 
     if (model == null) 
      throw new ArgumentException ("Model is not specified."); 

     if (string.IsNullOrEmpty (name)) 
      throw new ArgumentException ("Invalid name specified."); 

     var modification = new CarsModification 
     { 
      Model = model, 
      Name = name 
     }; 

     modification.Model.AddModification (modification); 

     return modification; 
    } 
} 

... 

using (var tx = session.BeginTransaction()) 
{ 
    var company = CarsCompany.Create ("Nissan"); 
    var model = CarsModel.Create (company, "Tiana"); 
    var modification = CarsModification.Create (model, "2011"); 

    session.Persist (company); 
    tx.Commit(); 
} 

Dieser Ansatz bekam alle Validierung/creation Logik in Einheiten entsprechen, und ich weiß nicht, ob es gut oder schlecht ist, sondern durch einfache Erstellung des Objekts mit Factory-Methode zu dem wir es implizit Kindern Sammlung übergeordneten Objekts Hinzufügen . Nach dem Transaktions-Commit und dem Session-Flush gibt es 3 Einfügungen in die Datenbank, obwohl ich noch nie einen "add" -Befehl in meinen Code geschrieben habe. Ich weiß nicht, vielleicht sind es nur ich und meine große Erfahrung außerhalb der DDD-Welt, aber es fühlt sich im Moment ein wenig unnatürlich an.

Also, was ist der korrekte Weg zum Hinzufügen von untergeordneten Entitäten mit DDD?

+0

Ich könnte alle Codebeispiele entfernen und diese ganze Frage viel schwieriger zu verstehen machen - aber ich denke nicht, dass es der Community helfen wird. Mir war nicht bewusst, dass SO nicht für komplexe Fragen steht. –

+0

Die ganze Frage (mit oder ohne Code) ist hier unpassend. Wie gesagt, es fragt nach Diskussion. Lies die Links, die ich gepostet habe. Ich kann einen anderen anbieten: [kein Diskussionsforum oder Forum] (http://meta.stackexchange.com/a/128550/172661). Wie ich bereits sagte, ist diese Seite für ** klare, knappe Fragen, die ohne Diskussion beantwortet werden können **. Da es in Ihrer gesamten Frage darum geht, den "richtigen Weg" zu diskutieren und "Meinungen [sp] zu hören" (was auch hier nicht angebracht ist), ist es hier nicht passend. –

+3

Ich stelle ziemlich konkrete Frage und warte auf eine ganz bestimmte Antwort darauf. Mit meinen Beispielen zeige ich nur die Forschungsanstrengungen, die ich in diese Angelegenheit gesteckt habe. Ich brauche keine Diskussion - ich brauche eine klare Antwort, wie man das richtig macht. Nur weil Sie nicht wissen, Antwort auf diese Frage bedeutet nicht, dass es Diskussion Natur hat. Die DDD-Welt benutzt viele Muster und ich möchte mich auf einen verweisen, den ich anwenden sollte, um dieses spezielle Problem zu lösen. –

Antwort

1

ich hier akzeptable Antwort habe: https://groups.yahoo.com/neo/groups/domaindrivendesign/conversations/messages/23187

Im Grunde ist es eine Kombination von Verfahren 2 und 3 - setzen AddModel Methode in die CarsCompany und geschützt machen rufen internen Konstruktor des CarsModel mit dem Namen Parameter, der überprüft wird im Konstruktor des CarsModels.

+0

Interessant, aber wo fügen Sie Objekte zu übergeordneten und untergeordneten Sammlungen hinzu? –

+0

In der übergeordneten .AddChild-Methode, die untergeordnete Elemente durch geschützten internen Konstruktor erstellt (Übergabe von self als Parameter für untergeordnete Elemente, um deren übergeordnete Elemente im Konstruktor festzulegen), fügt sie der untergeordneten Auflistung hinzu und gibt sie zurück. –

+0

Danke, das macht Sinn. Haben Sie dieses Muster in Verbindung mit Entity Framework verwendet? –

0

Hier ist eine sehr konkrete und bittlich ehrliche Antwort: alle Ihre Ansätze sind falsch, weil Sie die "erste Regel" von DDD gebrochen, das heißt, die DB existiert nicht.

Was Sie definieren, ist PERSISTENCE-Modell für ein ORM (Nhibernate). Um die Domänenobjekte zu entwerfen, müssen Sie zuerst das Bounded Context, sein Modell, die Entitäten und Wertobjekte für dieses Modell und die Aggregate Root (die sich intern mit den Regeln für Kinder und Unternehmen beschäftigt) identifizieren.

Nhibernate oder db-Schema haben hier keinen Platz, Sie brauchen nur reinen C# -Code und ein klares Verständnis der Domäne.

+4

Ja, das ist alles schön und klingt wie jedes DDD-Buch in, aber das Problem hier ist nicht über die Gestaltung der Domain. Das Problem bei der Implementierung. In der Entwicklung haben wir ein sehr spezifisches Muster, um ein sehr spezifisches Problem zu lösen - ein Problem, die Domäne in den Arbeitscode zu verwandeln. Und ich möchte darauf hinweisen, wie ich es richtig umsetzen kann. Ich könnte es mit reiner fiktionaler Sprache schreiben und es könnte in dieser fiktionalen Welt funktionieren (ich habe damit kein Problem). –

+2

Das Schwierige ist, die Domain zu gestalten. Tatsächlich sind das Erstellen der BC und der Aggreagates die schwierigsten Dinge. Sobald Sie sie kennen (in pseduo-Code), ist es nur eine Formalität, sie in C# zu schreiben. Das ist keine Theorie, der schwierigste Teil von DDD ist das VERSTEHEN der DOMAIN und eine sehr klare Unterscheidung, welche welche ist. Auch gibt es keine Silberkugel oder irgendein anderes anzuwendendes Muster. DDD handelt von Konzepten und Richtlinien. Sie möchten einige Beziehungen in Nhibernate implementieren. Das ist NICHT DDD. – MikeSW

+1

Sie benötigen den DOMAIN EXPERT, um das Problem und die Feinheiten zu verstehen. Weder ich noch andere Leute hier sind der Domänenexperte. Sie müssen mit dem Experten sprechen, um zu verstehen, was das Problem zu lösen ist. Und ehrlich gesagt, ohne die BC klar zu kennen, kann man keine Entität oder ein Aggregat entwerfen oder implementieren. – MikeSW

0

Also, was ist der korrekte Weg zum Hinzufügen von untergeordneten Entitäten mit DDD?

Der dritte Ansatz heißt Tight Coupling. Company, Car und Modification wissen fast alles über einander.

Der zweite Ansatz wird weithin in DDD vorgeschlagen. Ein Domänenobjekt ist dafür verantwortlich, ein verschachteltes Domänenobjekt zu erstellen und es darin zu registrieren.

Der erste Ansatz ist der klassische OOP-Stil. Die Erstellung eines Objekts wird vom Hinzufügen eines Objekts in eine Sammlung getrennt. Auf diese Weise kann der Code-Verbraucher ein Objekt einer konkreten Klasse (z. B. Auto) durch ein Objekt einer abgeleiteten Klasse (z. B. TrailerCar) ersetzen.

// var model = CarsModel.Create (company, "Tiana"); 

var model = TrailerCarsModel.Create (
    company, "Tiana", SimpleTrailer.Create(company)); 

company.AddModel (model); 

Versuchen Sie, diese Änderung der Geschäftslogik im 2./3.

0

Interessant. DDD vs Repository/ORM Navigationseigenschaften. Ich denke, die Antwort hängt davon ab, ob Sie mit einem Aggregat oder zwei arbeiten. Sollte CarsModel Teil des CarsCompany-Aggregats sein oder vielleicht sein eigenes Aggregat?

Ansatz eins ist, um das Problem verschwinden zu lassen. MikeSW deutete darauf hin. Wenn CarsCompany und CarsModel nicht Teil desselben Aggregats sein müssen, sollten sie sich gegenseitig nur durch Identität referenzieren, Navigationseigenschaften sollten in der Domain nicht sichtbar sein.

Ansatz 2 besteht darin, das Hinzufügen einer Beziehung auf die gleiche Weise wie das Abrufen eines Aggregats zu behandeln - die Anwendungsdienste rufen eine Methode aus dem Repository auf, die für Ihre ORM-spezifischen Probleme geeignet ist. Solch eine Methode könnte beide Enden der Beziehung füllen.