Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Proper way of creating child entities with DDD

I'm fairly new to DDD world and after reading couple of books about it (Evans DDD among them) I was unable to find the answer to my question on internet: what the proper way of creating child entities with DDD? You see, a lot of information on internet operates on some simple level. But devils in the details and they're always omitted in dozens of DDD samples for the sake of simplicity.

I'm coming from my own answer on similair question here on stackoverflow. I'm not completely satisfied with my own vision on this problem so I thought I need to elaborate on this matter.

For example, I need to create simple model that represent cars naming: company, model and modification (for example, Nissan Teana 2012 - that will be "Nissan" company, "Teana" model and "2012" modification).

The sketch of the model I want to create looks like this:

CarsCompany
{
    Name
    (child entities) Models
}

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


CarsModification
{
    (parent entity) Model
    Name
}

So, now I need to create code. I'll use C# as language and NHibernate as ORM. This is important and what typically does not shown in vast DDD samples on the internet.

The first approach.

I'll start with simple approach with typical object creation via factory methods.

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
        };
    }
}

The bad thing about this approach is that creation of the model does not adding it to the parent models collection:

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 ();
}

After the transaction is committed and the session is flushed, the ORM will correctly write all into the database and next time we load that company it's models collection will correctly holds our model. The same goes to modification. So this approach leaves our parent entity in inconsistent state until it's been reload from database. No go.

The second approach.

This time we'll gonna use language specific option to solve the problem of setting protected properties of other classes - namely we'll gonna use "protected internal" modifier on both setters and constructor.

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 ();
}

This time each entity creation leaves both parent and child entity in consistent state. But validation of the child entity state leaked into the parent entity (AddModel and AddModification methods). Since I'm nowhere expert in DDD I'm not sure if it's ok or not. It could create more problems in future when child entities properties could not be simply set via properties and setting up some state based on passed parameters would require more complex work that assigning parameter value to property. I was under impression that we should concentrate logic about entity inside that entity wherever it's possible. For me this approach turns parent object into some kind of Entity&Factory hybrid.

The third approach.

Ok, we'll gonna invert the responsibilities of maintaining parent-child relations.

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 ();
}

This approach got all validation/creation logic inside corresponding entities and I don't know if it's good or bad, but by simple creation of the object with factory method we implicitly adding it to the parent object children collection. After transaction commit and session flush there will be 3 inserts into the database even tho I never written some "add" command in my code. I don't know maybe it's just me and my vast experience outside of DDD world but it feels a bit unnatural for now.

So, what's most correct way of adding child entities with DDD?

like image 789
Michael Logutov Avatar asked Jun 13 '12 19:06

Michael Logutov


People also ask

What is a child entity?

A child entity is a type of entity that is defined in relation to another, specific entity called the parent. Child entities can be thought of as subordinates or dependents of their parents. In some cases, child entities may have their own independent existence, but they cannot exist without their parents.

What is an entity in DDD?

In domain-driven design, an entity is a representation of an object in the domain. It is defined by its identity, rather than its attributes. It encapsulates the state of that object through its attributes, including the aggregation of other entities, and it defines any operations that might be performed on the entity.


1 Answers

So, what's most correct way of adding child entities with DDD?

The third approach is called Tight Coupling. Company, Car and Modification know almost everything about each other.

The second approach is widely proposed in DDD. An domain object is responsible for creating a nested domain object AND registering it inside.

The first approach is classic OOP style. Creation of an object is separated from adding an object into some collection. This way code consumer can substitute an object of a concrete class (e.g. Car) with an object of any derived class (e.g. TrailerCar).

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

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

company.AddModel (model);

Try adopting this business logic change in the 2nd / 3rd approach.

like image 78
Lightman Avatar answered Oct 25 '22 12:10

Lightman