为什么我应该避免在域实体属性上使用公共setter

tug*_*erk 7 c# domain-driven-design

我正在尝试在我现在正在处理的一个应用程序中应用DDD,但我不能说我已经100%掌握了它.

在我看到的大多数示例中,似乎是我们试​​图避免在域实体属性上使用公共setter.例如,我看到如下实现的域实体:

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));
    }
}
Run Code Online (Sandbox Code Playgroud)

用法看起来像:

// have a Product object instance here somehow
product.UpdateName("foobar");
Run Code Online (Sandbox Code Playgroud)

但是,我可以通过在Name属性的setter 上实现更新逻辑来实现相同的行为,如下所示:

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));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,用法看起来如下所示:

// have a Product object instance here somehow
product.Name = "foobar";
Run Code Online (Sandbox Code Playgroud)

我可以看到第一个实际上更好地表达了意图但我在这里看不到那么多差异.除了更好的表现力,还有其他任何理由为什么你喜欢第一个选项?在DDD中,使用第二种选择是否是一个很大的禁忌?

pla*_*alx 7

DDD就是无处不在的语言.CRUD(创建/读取/更新/删除)术语通常无法在无处不在的语言中找到它们的方式.您是说可以更新产品名称还是可以从业务角度重命名产品?

我无法确定,因为我不知道你的域名,但重命名的可能性更大.

现在,让我们说你看到这行代码product.name = 'some name',你觉得这究竟意味着什么?您必须依赖于Product无法在没有名称的情况下构建的知识,以确定我们实际上是在重命名它.否则,它也可能意味着我们正在命名一个最初未命名的产品.

有了product.rename(...)它,它清楚地表明了意图,没有解释的地方.它也与普遍存在的语言更加一致,这才是真正重要的.

此外,企业可能希望了解为什么产品名称已被更改?是因为有拼写错误而改变了吗?是出于营销原因而改变了吗?你会如何用一个简单的二传手捕获它?

我不太了解C#,所以这可能不是惯用语,但这里似乎更有意义:

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));
    }
}
Run Code Online (Sandbox Code Playgroud)

另请注意,我使用其他相关信息增强了域事件.现在处理事件时其他重要的事情就是在修改聚合get的同一事务中将它们写入磁盘.事务提交后,可以通过消息传递基础结构发布它们以进行进一步处理.您希望避免在事务实际提交之前发生副作用.