-
19-09-2019 - |
题
我们需要在用户在活动页面上输入电子邮件地址时添加活动提醒。事件是另一个领域对象。我们最初的想法是创建一个 Customer 域对象和相关的 CustomerService:
public class CustomerService {
public void AddEventReminder(string emailAddress, int eventId) {
var customer = new Customer(emailAddress);
customer.AddEmailReminder(eventId);
}
}
我们如何在单元测试中验证确实对新客户调用了 AddEmailReminder 方法?
我的想法:
- 使用工厂来创建客户。这闻起来很臭,因为我认为你只应该在对象创建有一定复杂性的地方使用工厂。
- 糟糕的代码。也许有更好的方法来做到这一点?
- 起订量魔法。
另请注意(也许是相关的),我们如何确定这里的聚合根?我们任意决定客户是,但它同样可以是事件。我已阅读并理解有关聚合根的文章,但在这种情况下尚不清楚。
解决方案
在这种情况下,我会在创建客户的服务中创建一个受保护的方法,在测试中使用匿名内部类覆盖该方法,并使其返回一个模拟 Customer 对象。然后,您可以在模拟 Customer 对象上验证是否调用了 AddEmailReminder。就像是:
public class CustomerService {
public void AddEventReminder(string emailAddress, int eventId) {
var customer = createCustomer(emailAddress);
customer.AddEmailReminder(eventId);
}
protected Customer createCustomer(string emailAddress) {
return new Customer(emailAddress);
}
}
在测试中(假设 C# 知识有限,但它应该说明这一点):
void testCustomerCreation() {
/* final? */ Customer mockCustomer = new Customer("email");
CustomerService customerService = new CustomerService() {
protected Customer createCustomer(string emailAddress) {
return mockCustomer;
}
};
customerService.AddEventReminder("email", 14);
assertEquals(mockCustomer.EventReminder() /* ? */, 14);
}
其他提示
关于 CustomerService API 的思考
您决定将此操作封装在 CustomerService 中是否有任何特殊原因?这个看起来有点 贫血 大部头书。是否可以直接封装在客户上?
也许您遗漏了 CustomerService 代码示例中的一些内容来简化事情......
但是,如果必须,更改签名以采用 Customer 实例可以解决问题:
public void AddEventReminder(Customer customer, int eventId)
但话又说回来,Int32 几乎不符合域对象的资格,因此签名实际上应该是
public void AddEventReminder(Customer customer, Event event)
现在的问题是这种方法是否有任何价值?
哪个是聚合根?
我想他们都没有。聚合根表明您管理子项 仅有的 通过根,在这种情况下这两种方式都没有意义。
考虑以下选项:
如果您将事件作为根,则意味着您可能没有 CustomerRepository,并且检索、编辑和保留客户的唯一方法是通过事件。这对我来说听起来非常错误。
如果将 Customer 作为根,则可以没有 EventRepository,并且检索、编辑和保留事件的唯一方法是通过特定的 Customer。这对我来说同样是错误的。
唯一剩下的可能性是它们是单独的根。这也意味着它们彼此之间只是松散连接,并且您将需要某种领域服务来查找客户的事件或事件的客户。