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

时间:2015-03-01 11:56:14

标签: 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));
    }
}

用法看起来像:

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

但是,我可以通过在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));
        }
    }
}

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

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

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

2 个答案:

答案 0 :(得分:6)

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

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

答案 1 :(得分:0)

由于您正在使用域名事件,因此您的问题会更加有趣。

我在某个时候发现了以下帖子:http://www.jayway.com/2013/06/20/dont-publish-domain-events-return-them/

所以这里的想法是不使用一些任意的全局可访问的基础设施来发布域事件,而是简单地返回事件作为操作的结果。

我觉得这个选项很有趣有几个原因:

  • 不依赖于所有客户端都需要使用的某些DomainEvents静态实现。
  • 更容易测试,因为我们不需要连接静态单例DomainEvents

但是,使用您的示例添加了另一个维度,即在这种情况下甚至无法使用setter,因为它无法返回域事件结果。因此,当使用域事件时,可能会更好地改变一个人的设计,我希望:)