0

TLDR version: I'm having trouble getting my DDD domain model to work with NHibernate. If my value object itself contains a collection of value objects, I can't assign a new value without getting an NHibernate exception, and want to know what the best practice is in this situation.

Longer version:

Say I have an entity which contains a value object as a property, ValueObjectA, which itself contains a set of a different value objects of type ValueObjectB.

ValueObjectB only exists meaningfully as a property of ValueObjectA, i.e. if myEntity.ValueObjectA == null, it doesn't make sense for ValueObjectB to exist either.

I've written some example code to illustrate what I mean, with simplifications for brevity.

public class Entity
{
    public int Id { get; private set; }
    public ValueObjectA ValueObjectA { get; set; }

    // Constructor: public Entity(ValueObjectA valueObjectA)
}

public class ValueObjectA : IEquatable<ValueObjectA>
{
    public string X { get; private set; }
    public ISet<ValueObjectB> ValueObjectBs { get; private set; }

    // Constructor: public ValueObjectA(string x, ISet<ValueObjectB> valueObjectBs)
    // Implementation of Equals/GetHahcode
}

public class ValueObjectB : IEquatable<ValueObjectB>
{
    public int Y { get; private set; }
    public int Z { get; private set; }

    // Constructor: public ValueObjectB(int y, int z)
    // Implementation of Equals/GetHahcode
}

I have a corresponding mapping class using mapping by code:

public class EntityMap : ClassMapping<Entity>
{
    public EntityMap()
    {
        Table("Entity");
        Id(x => x.Id, map => map.Generator(Generators.Identity));

        Component(x => x.ValueObjectA, c =>
        {
            c.Property(x => x.X);

            // Component relation is equilavent to <composite-element> in xml mappings
            c.Set(x => x.ValueObjectBs, map =>
            {
                map.Table("ValueObjectB");
                map.Inverse(true);
                map.Cascade(Cascade.All | Cascade.DeleteOrphans);
                map.Key(k => k.Column("Id"));
            }, r => r.Component(ce =>
            {
                ce.Property(x => x.Y);
                ce.Property(x => x.Z);
            }));
        });
    }
}

The properties of ValueObjectA are mapped to the Entity table, but the properties of ValueObjectA.ValueObjectB are mapped to another table, since it is a one to many relationship. When a ValueObjectB is removed, I want that row to be deleted in the ValueObjectB table.

Since value objects are immutable, when I change the properties of entity.ValueObjectA, I should create a new instance of ValueObjectA. The problem is that the set of ValueObjectBs is a reference type, so when I try to save the entity with a different ValueObjectA, NHibernate will throw an exception because the original set that NHibernate is tracking is no longer referenced:

A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance.

Consider the following code:

        var valueObjectBs_1 = new HashSet<ValueObjectB>
        {
            new ValueObjectB(1, 2),
            new ValueObjectB(3, 4)
        };

        var valueObjectA_1 = new ValueObjectA("first", valueObjectBs_1);

        var entity = new Entity(valueObjectA_1);

        // Save entity, reload entity

        var valueObjectBs_2 = new HashSet<ValueObjectB>
        {
            new ValueObjectB(1, 2)
        };

        var valueObjectA_2 = new ValueObjectA("second", valueObjectBs_2);

        entity.ValueObjectA = valueObjectA_2;

        // Save entity again
        // NHIBERNATE EXCEPTION

I've managed to get around this by creating another ValueObjectA in order to preserve the reference to the set, e.g.

        valueObjectA_1.ValueObjectBs.Remove(new ValueObjectB(3, 4));
        entity.ValueObjectA = new ValueObjectA(valueObjectA_2.X, valueObjectA_1.ValueObjectBs);

However... that feels like a code smell - even if I wrote a custom setter for Entity.ValueObjectA, the implementation is starting to get complicated where the design is supposed to be simple.

public class Entity
{
    // ...
    private ValueObjectA valueObjectA;
    public ValueObjectA ValueObjectA
    {
        // get
        set
        {
            // Add/Remove relevant values from ValueObjectA.ValueObjectBs
            valueObjectA = new ValueObjectA(value.X, ValueObjectA.ValueObjectBs);
        }
    }
}

What is the best practice in this type of situation? Or is this a sign that I'm trying to do something which violates the principles of DDD?

RiddleMeThis
  • 851
  • 1
  • 8
  • 18
  • You got lucky that `NHibernate` screamed at you, otherwise you may have gotten away with that design for some time before it would have smelled again – Constantin Galbenu Apr 23 '17 at 11:20

1 Answers1

0

What you have is an anemic domain model.

You should replace public setters of the entity with methods that have meaningful names from the Ubiquitous language, that check the invariants and that do all the necessary cleanup in case of value objects replacements.

Although it may seem that things are more complicated this is payed back by the fact the now the entity is in full control about what happens with its internals. You now have full encapsulation.

Constantin Galbenu
  • 16,951
  • 3
  • 38
  • 54
  • Thanks. I'm aware of the anemic domain model. My actual domain model uses ubiquitous languages, and maintains the business logic within the actual objects. (I appreciate the code supplied is anemic, but I wanted to simplify it to to simplify it to illustrate the NHibernate exception - otherwise, it'd be TLDR). – RiddleMeThis Apr 24 '17 at 20:03
  • My concern is it's difficult to keep it simple when you have reference types within value objects. Say I had a value object, Address, and entities Person and Order, which had a billing address and a delivery address respectively. If I have bespoke logic to update an address, e.g. because it contains a collection of value objects, as in the original example, I would have to put the same logic in both entities or introduce an extra mapping/builder class to keep it DRY. It feels like the implementation is becoming complex even though the conceptual design might be simple. Is that a code smell? – RiddleMeThis Apr 24 '17 at 20:19