Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why should I avoid having public setters on domain entity properties

I am trying to apply DDD in one of the applications I am working on now and I cannot say I have grasped it 100% yet.

In most of the samples I am looking at, it seems to be the case that we are trying to avoid having public setters on domain entity properties. For example, I see domain entities implemented like below:

public class Product
{
    public Product(string name)
    {
        if (name == null)
        {
            throw new ArgumentNullException("name");
        }

        Name = name;
    }

    public string Name { get; private set; }

    public void UpdateName(string newName)
    {
        if (newName == null)
        {
            throw new ArgumentNullException("newName");
        }

        Name = newName;
        DomainEvents.Raise(new ProductNameUpdatedEvent(this));
    }
}

The usage would look something like:

// have a Product object instance here somehow
product.UpdateName("foobar");

However, I can achieve the same behavior by implementing the update logic on Name property's setter as below:

public class Product
{
    private string _name;

    public Product(string name)
    {
        if (name == null)
        {
            throw new ArgumentNullException("name");
        }

        _name = name;
    }

    public string Name
    {
        get
        {
            return _name;
        }

        set
        {
            if (value == null)
            {
                throw new ArgumentNullException("value");
            }

            _name = value;
            DomainEvents.Raise(new ProductNameUpdatedEvent(this));
        }
    }
}

The usage would look something like below in this case:

// have a Product object instance here somehow
product.Name = "foobar";

I can see that the first one actually expresses the intent better but I cannot see that much difference here. Other than the better expressiveness, is there any other reason why you want to favor the first option? In DDD, is it a big no-no to go with the second option?

like image 986
tugberk Avatar asked Mar 01 '15 11:03

tugberk


1 Answers

DDD is all about the ubiquitous language. CRUD(Create/Read/Update/Delete) terms usually do not find their way in the ubiquitous language. Would you say that a product name can be updated or a product can be renamed from a business's perspective?

I can't tell for sure since I do not know your domain, but chances are that rename is more appropriate.

Now, let's say you see this line of code product.name = 'some name', how would you what that really means? You have to rely on the knowledge that a Product cannot be constructed without a name to know for sure that we are actually renaming it. Otherwise, it could also mean that we are naming a product that wasn't initially named.

With product.rename(...), it clearly shows the intention and there's no place for interpretation. It's also much more aligned with the ubiquitous language which is what really matters.

Also, perhaps the business want to capture why the name of a product has been changed? Was it changed because there was a typo? Was it changed for marketing reasons? How would you capture this with a simple setter?

I do not know C# really well so this might not be idiomatic, but here's what seems to make more sense:

public class Product {

    //Note: Using ProductName rather than string would be helpful
    //to protect further invariants (e.g. name cannot be empty string)
    private string _name;

    public Product(string name) {
        Name = name;
    }

    public string Name {
        get {
            return _name;
        }

        private set {
            if (value == null) {
                throw new ArgumentNullException("A product must be named");
            }

            _name = value;
        }
    }

    public void rename(string newName) {
        string oldName = Name;

        Name = newName;

        //Assume the entity has the supporting code for a  unique identifier
        //productId
        DomainEvents.Raise(new ProductRenamed(productId, oldName, newName));
    }
}

Also note that I enhanced the domain event with additional relevant information. Now something else that is important when handling events is to write them to disk within the same transaction in which the aggregate get's modified. Once the transaction commits, they can be published through a messaging infrastructure for further processing. You want to avoid side-effects from occurring before the transaction actually committed in general.

like image 196
plalx Avatar answered Nov 18 '22 05:11

plalx