gpt4 book ai didi

validation - 领域驱动设计 - 跨不同聚合的命令的复杂验证

转载 作者:行者123 更新时间:2023-12-03 19:21:04 25 4
gpt4 key购买 nike

我刚开始使用 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);
}
}

下面是我对实现的看法:

  1. 基本上,订单可以处于三种状态:“DRAFT”、“VALIDATED”和“INVALID”。 “DRAFT”状态可以包含任何类型的无效数据,“VALIDATED”状态应该只包含有效数据,“INVALID”应该包含无效数据。
  2. 因此,应该有一个尝试切换订单状态的方法,我们称它为order.validate(...)。该方法将执行状态转换所需的验证(DRAFT -> VALIDATED 或 DRAFT -> INVALID),如果成功 - 更改状态并传输 OrderValidated 或 OrderInvalidated 事件。

现在,我正在努力解决的是上述 order.validate(...) 方法的签名。为了验证订单,它需要其他几个聚合,即 BillCycleClient。我可以看到这些解决方案:

  • 将这些聚合直接放入 validate 方法中,比如order.validateWith(client, cycles)order.validate(new
    OrderValidationData(client, cycles))
    。不过,这似乎有点骇人听闻。
  • 从客户那里提取所需信息并进行循环进入某种中间验证数据对象。就像是order.validate(new OrderValidationData(client.getDiscountInfos(),
    getListOfPeriods(周期))
  • 在单独的服务中进行验证可以随心所欲地聚合它的方法想要(基本上类似于上面的 CRUD 示例)。然而,这似乎远离 DDD,因为方法 order.validate() 将成为虚拟状态setter,调用这个方法可以带来一个命令不直观地进入损坏状态(状态=“有效”但包含无效数据,因为没有人费心调用验证服务)。

正确的做法是什么,难道我的整个思维过程都是错误的吗?

提前致谢。

最佳答案

引入委托(delegate)对象来操作Order、Client、BillCycle怎么样?

class OrderingService {
@Injected private ClientRepository clientRepository;

@Injected private BillingRepository billRepository;

Specification<Order> validSpec() {
return new ValidOrderSpec(clientRepository, billRepository);
}

}

class ValidOrderSpec implements Specification<Order> {
@Override public boolean isSatisfied(Order order) {
Client client = clientRepository.findBy(order.getClientId());
BillCycle[] billCycles = billRepository.findAll();
// validate here
}
}

class Order {
void validate(ValidOrderSpecification<Order> spec) {
if (spec.isSatisfiedBy(this) {
validated();
} else {
invalidated();
}
}
}

从我的角度来看,您的三种解决方案的优缺点:

  1. order.validateWith(client, cycles)

很容易用订单测试验证。

#file: OrderUnitTest
@Test public void should_change_to_valid_when_xxxx() {
Client client = new ClientFixture()...build()
BillCycle[] cycles = new BillCycleFixture()...build()
Order order = new OrderFixture()...build();

subject.validateWith(client, cycles);

assertThat(order.getStatus(), is(VALID));
}

到目前为止一切顺利,但似乎有一些重复的 DiscountOrderProcess 测试代码。

#file: DiscountProcessor
@Test public void should_change_to_valid_when_xxxx() {
Client client = new ClientFixture()...build()
BillCycle[] cycles = new BillCycleFixture()...build()
Order order = new OrderFixture()...build()
DiscountProcessor subject = ...

given(clientRepository).findBy(client.getId()).thenReturn(client);
given(cycleRepository).findAll().thenReturn(cycles);
given(orderRepository).findBy(order.getId()).thenReturn(order);

subject.processOrder(order.getId());

assertThat(order.getStatus(), is(VALID));
}

#or in mock style
@Test public void should_change_to_valid_when_xxxx() {
Client client = mock(Client.class)
BillCycle[] cycles = array(mock(BillCycle.class))
Order order = mock(Order.class)
DiscountProcessor subject = ...

given(clientRepository).findBy(client.getId()).thenReturn(client);
given(cycleRepository).findAll().thenReturn(cycles);
given(orderRepository).findBy(order.getId()).thenReturn(order);

given(client).....
given(cycle1)....

subject.processOrder(order.getId());

verify(order).validated();
}
  1. order.validate(new OrderValidationData(client.getDiscountInfos(), getListOfPeriods(cycles))

同上,仍然需要为OrderUnitTest和discountOrderProcessUnitTest准备数据。但我认为这个更好,因为订单与 Client 和 BillCycle 没有紧密耦合。

  1. order.validate()

如果您在域层中保持验证,则与我的想法类似。有时这不是任何实体的责任,请考虑领域服务或规范对象。

#file: OrderUnitTest
@Test public void should_change_to_valid_when_xxxx() {
Client client = new ClientFixture()...build()
BillCycle[] cycles = new BillCycleFixture()...build()
Order order = new OrderFixture()...build();
Specification<Order> spec = new ValidOrderSpec(clientRepository, cycleRepository);

given(clientRepository).findBy(client.getId()).thenReturn(client);
given(cycleRepository).findAll().thenReturn(cycles);


subject.validate(spec);

assertThat(order.getStatus(), is(VALID));
}

#file: DiscountProcessor
@Test public void should_change_to_valid_when_xxxx() {
Order order = new OrderFixture()...build()
Specification<Order> spec = mock(ValidOrderSpec.class);
DiscountProcessor subject = ...

given(orderingService).validSpec().thenReturn(spec);
given(spec).isSatisfiedBy(order).thenReturn(true);
given(orderRepository).findBy(order.getId()).thenReturn(order);

subject.processOrder(order.getId());

assertThat(order.getStatus(), is(VALID));
}

关于validation - 领域驱动设计 - 跨不同聚合的命令的复杂验证,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26729462/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com