在DDD上下文中,域模型上的setter是代码气味。 应该避免使用它们,原因很简单,它们实际上并不是域的一部分。域专家可能无法理解其中的名词,而数据的更改应该通过特定的方法来代替。
示例:
customer.StreetName = ...
customer.City = ...
虽然正确的方法是拥有一个customer.ChangeAddress
方法然后可以发布一个事件等等。至少从我的理解这是所有合理的理论,我完全可以理解为什么在一个域模型并不是真正需要的。
BUT: 如果您的域模型没有setter,那么这些方法很难测试。
如果我没有构建一个接受所有参数的big-ass构造函数,或者做一些反射魔法的话,我怎么能得到一个Customer实例来运行我的测试呢?我在后端使用NHibernate,所以NHibernate已经做了一些反射魔法来填充这些字段。
但是拥有10个参数的ctor感觉真的很糟糕。(对于工厂方法也是如此)。
对此有何建议?
问候丹尼尔
答案 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);
你应该至少尝试将那些“大屁股”对象划分为较小的对象。尝试建立一个层次结构(只要确保它符合无处不在的语言)。