【发布时间】:2014-12-31 00:11:04
【问题描述】:
我刚开始使用 DDD,目前正试图掌握用它做不同事情的方法。我正在尝试使用带有 CQRS 的异步事件(还没有事件源)来设计它。目前我坚持验证命令。我读过这个问题:Validation in a Domain Driven Design,但是,似乎没有一个答案涵盖跨不同聚合根的复杂验证。
假设我有这些聚合根:
- Client - 包含已启用服务的列表,每个服务都可以有一个价值对象的折扣列表及其有效性。
- DiscountOrder - 为给定客户的某些服务启用更多折扣的订单,包含带有折扣配置的订单项目。
- BillCycle - 每个产生账单的时期都由自己的账单周期描述。
这是用例:
可以提交折扣订单。折扣订单中的每个新折扣期不应与任何 BillCycles 重叠。一项服务不能同时激活两种相同类型的折扣。
基本上,在 CRUD 样式中使用 Hibernate,这看起来类似于(java 代码,但问题与语言无关):
public class DiscountProcessor {
...
@Transactional
public void processOrder(long orderId) {
DiscOrder order = orderDao.get(orderId);
BillCycle[] cycles = billCycleDao.getAll();
for (OrderItem item : order.getItems()) {
//Validate billcycle overlapping
for (BillCycle cycle : cycles) {
if (periodsOverlap(cycle.getPeriod(), item.getPeriod())) {
throw new PeriodsOverlapWithBillCycle(...);
}
}
//Validate discount overlapping
for (Discount d : item.getForService().getDiscounts()) {
if (d.getType() == item.getType() && periodsOverlap(d.getPeriod(), item.getPeriod())) {
throw new PeriodsOverlapWithOtherItems(...);
}
}
//Maybe some other validations in future or stuff
...
}
createDiscountsForOrder(order);
}
}
下面是我对实施的想法:
- 基本上,订单可以处于三种状态:“DRAFT”、“VALIDATED”和“INVALID”。 “DRAFT”状态可以包含任何类型的无效数据,“VALIDATED”状态应该只包含有效数据,“INVALID”应该包含无效数据。
- 因此,应该有一个尝试切换订单状态的方法,我们称之为
order.validate(...)。该方法将执行状态转换所需的验证(DRAFT -> VALIDATED 或 DRAFT -> INVALID),如果成功 - 更改状态并传输 OrderValidated 或 OrderInvalidated 事件。
现在,我正在努力解决的是上述order.validate(...) 方法的签名。要验证订单,它需要几个其他聚合,即BillCycle 和Client。我可以看到这些解决方案:
- 将这些聚合直接放入
validate方法中,例如order.validateWith(client, cycles)或order.validate(new OrderValidationData(client, cycles))。不过,这似乎有点 骇人听闻。 - 从客户端和循环中提取所需信息
进入某种中间验证数据对象。就像是
order.validate(new OrderValidationData(client.getDiscountInfos(), getListOfPeriods(cycles))。 - 在单独的服务中进行验证
可以用任何聚合来做任何事情的方法
想要(基本上类似于上面的 CRUD 示例)。然而,这似乎
远离 DDD,因为方法
order.validate()将成为虚拟状态 setter,调用此方法将可以带来一个 命令不直观地进入损坏状态(状态=“有效”但 包含无效数据,因为没有人打扰调用验证 服务)。
正确的做法是什么,难道我的整个思考过程都是错误的?
提前致谢。
【问题讨论】:
-
当多个聚合在一个进程中起作用时,通常可以使用域服务来封装这个逻辑。但是,由于
validate状态更改行为似乎在Order中找到了它的自然归宿,我可能会选择像您想要的那样将执行验证所需的依赖项传递给order.validate,或者传递IOrderValidatingSpecification到order.validate并让订单双重发送给它。
标签: validation language-agnostic domain-driven-design cqrs