域模型中的setter

时间:2010-09-15 10:32:06

标签: c# domain-driven-design domain-model

在DDD上下文中,域模型上的setter是代码气味。 应该避免使用它们,原因很简单,它们实际上并不是域的一部分。域专家可能无法理解其中的名词,而数据的更改应该通过特定的方法来代替。

示例:

customer.StreetName = ...
customer.City = ...

虽然正确的方法是拥有一个customer.ChangeAddress方法然后可以发布一个事件等等。至少从我的理解这是所有合理的理论,我完全可以理解为什么在一个域模型并不是真正需要的。

BUT: 如果您的域模型没有setter,那么这些方法很难测试。

如果我没有构建一个接受所有参数的big-ass构造函数,或者做一些反射魔法的话,我怎么能得到一个Customer实例来运行我的测试呢?我在后端使用NHibernate,所以NHibernate已经做了一些反射魔法来填充这些字段。

但是拥有10个参数的ctor感觉真的很糟糕。(对于工厂方法也是如此)。

对此有何建议?

问候丹尼尔

2 个答案:

答案 0 :(得分:3)

在经典(非CQRS)DDD中,最好将所有数据分解为值对象,以便将您的实体简化为其主要功能:维护身份。

在您的示例中,Customer应引用Address ValueObject并使用ChengeAddress方法,该方法应该如下所示:

public void ChangeAddress(Address address)
{
   //Consistency rules are here
   _address = address;
}

尝试尽可能多地将逻辑从实体移动到值对象。它们本质上更容易测试,因为良好的价值对象是小而不可变的。您使用构造函数在给定状态下实例化VO并对其进行练习(通常通过调用返回另一个转换后的VO实例的方法)。

最后但并非最不重要的是,根据我的经验,我可以说,如果测试您的域模型需要额外的基础架构(如反射或任何其他工具),那么您做错了(通过引入不必要的耦合)。

答案 1 :(得分:1)

您可能想尝试AutoFixture

混合一点反思爱情和领域变得非常可测试:

namespace Unit{
  using System;
  using System.Linq.Expressions;
  public static class ObjectExtensions{
    public static T Set<T,TProp>(this T o,
      Expression<Func<T,TProp>> field,TProp value){

      var fn=((MemberExpression)field.Body).Member.Name;
      o.GetType().GetProperty(fn).SetValue(o,value,null);
      return o;
    }
  }
}

用法:

myUberComplexObject.Set(x=>x.PropertyOfIt, newValueOfIt);

你应该至少尝试将那些“大屁股”对象划分为较小的对象。尝试建立一个层次结构(只要确保它符合无处不在的语言)。