Richtige Methode zum Erstellen von untergeordneten Entitäten mit DDD

Ich bin ziemlich neu in der DDD-Welt und nachdem ich ein paar Bücher darüber gelesen hatte (darunter Evans DDD), konnte ich im Internet keine Antwort auf meine Frage finden: Was ist die richtige Art, mit DDD untergeordnete Entitäten zu erstellen? Sie sehen, viele Informationen im Internet funktionieren auf einer einfachen Ebene. Aber die Details stimmen nicht, und der Einfachheit halber werden sie in Dutzenden von DDD-Beispielen immer weggelassen.

Ich komme ausmeine eigene Antwort auf similair frage hier auf stackoverflow. Ich bin mit meiner eigenen Vision zu diesem Problem nicht ganz zufrieden, daher dachte ich, ich muss auf diese Angelegenheit näher eingehen.

Zum Beispiel muss ich ein einfaches Modell erstellen, das die Benennung der Autos darstellt: Firma, Modell und Modifikation (zum Beispiel Nissan Teana 2012 - das wird "Nissan" Firma, "Teana" Modell und "2012" Modifikation sein).

Die Skizze des Modells, das ich erstellen möchte, sieht folgendermaßen aus:

CarsCompany
{
    Name
    (child entities) Models
}

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


CarsModification
{
    (parent entity) Model
    Name
}

Also muss ich jetzt 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 einem einfachen Ansatz mit der 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 Schlechte an diesem Ansatz ist, dass beim Erstellen des Modells dieses nicht zur übergeordneten Modellsammlung hinzugefügt wird:

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 festgeschrieben und die Sitzung geleert wurde, schreibt der ORM alle Daten korrekt in die Datenbank und wenn wir das nächste Mal das Unternehmen laden, wird die Modellsammlung unser Modell korrekt enthalten. Gleiches gilt für die Modifikation. Bei diesem Ansatz bleibt die übergeordnete Entität im inkonsistenten Zustand, bis sie erneut aus der Datenbank geladen wurde. No Go.

Der zweite Ansatz.

Dieses Mal werden wir die sprachspezifische Option verwenden, um das Problem des Einstellens von geschützten Eigenschaften anderer Klassen zu lösen. Wir werden nämlich den Modifikator "protected internal" sowohl für Setter als auch für Konstruktoren verwenden.

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 CarsModel AddModel (string name)
    {
        if (string.IsNullOrEmpty (name))
            throw new ArgumentException ("Invalid name specified.");

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

        this._models.Add (model);

        return model;
    }
}


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


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


    protected internal CarsModel ()
    {
    }


    public CarsModification AddModification (string name)
    {
        if (string.IsNullOrEmpty (name))
            throw new ArgumentException ("Invalid name specified.");

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

        this._modifications.Add (modification);

        return modification;
    }
}


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


    protected internal CarsModification ()
    {
    }
}

...

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

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

Dieses Mal bleibt bei jeder Entitätserstellung sowohl die übergeordnete als auch die untergeordnete Entität im konsistenten Zustand. Die Validierung des Zustands der untergeordneten Entität ist jedoch in die übergeordnete Entität eingedrungen (AddModel undAddModification Methoden). Da ich kein DDD-Experte bin, bin ich mir nicht sicher, ob es in Ordnung ist oder nicht. Es könnte in Zukunft zu weiteren Problemen führen, wenn untergeordnete Entitäteneigenschaften nicht einfach über Eigenschaften festgelegt werden können und das Einrichten eines Status auf der Grundlage übergebener Parameter komplexere Arbeiten erfordert als das Zuweisen von Parameterwerten zu Eigenschaften. Ich hatte den Eindruck, dass wir die Logik auf die Entität innerhalb dieser Entität konzentrieren sollten, wo immer dies möglich ist. Für mich verwandelt dieser Ansatz das übergeordnete Objekt in eine Art Entity & Factory-Hybrid.

Der dritte Ansatz.

Ok, wir werden die Verantwortung für die Aufrechterhaltung der Eltern-Kind-Beziehungen umkehren.

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 hat die gesamte Validierungs- / Erstellungslogik in entsprechenden Entitäten und ich weiß nicht, ob es gut oder schlecht ist, aber durch einfaches Erstellen des Objekts mit der Factory-Methode fügen wir es implizit der übergeordneten Objekt-Children-Auflistung hinzu. Nach dem Festschreiben der Transaktion und dem Leeren der Sitzung werden 3 Einfügungen in die Datenbank vorgenommen, obwohl ich in meinem Code nie einen "add" -Befehl geschrieben habe. Ich weiß nicht, ob es nur ich und meine große Erfahrung außerhalb der DDD-Welt sind, aber im Moment fühlt es sich ein bisschen unnatürlich an.

Wie fügt man untergeordnete Entitäten mit DDD am besten hinzu?

Antworten auf die Frage(4)

Ihre Antwort auf die Frage