【问题标题】:EventSourcing, Aggregates – working with invariants without doubling themEventSourcing, Aggregates – 使用不变量而不加倍它们
【发布时间】:2021-12-22 04:00:41
【问题描述】:

我在我的应用程序中使用非常基本的事件溯源技术,但是我遇到了一个概念问题。让我们直接进入示例。

ProductAggregateRoot.php

public function changePrice(ChangeProductPrice $command): self
{
    if ($this->availability->equals(Availability::UNAVAILABLE())) {
        throw CannotChangePriceException::unavailableProduct();
    }

    if ($this->price->equals($command->newPrice)) {
        throw CannotChangePriceException::priceHasntChanged();
    }

    $this->recordThat(
        new ProductPriceChanged($this->price, $command->newPrice)
    );

    return $this;
}

现在,我想创建一些其他方法(甚至域服务,并不重要),这很简单:

  1. 从外部来源获取当前价格和可用性
  2. 尝试更新汇总价格。
  3. 尝试更新总体可用性。

如您所见,在更新价格时,我正在检查一些业务不变性,例如,如果产品不可用,您无法更改价格,如果其价值未发生变化,您无法更改价格。如果任何不变量被破坏,则抛出异常。

现在,在我的域服务中,我想根据外部数据源更改价格和可用性。现在,我只是做了一些 try catch 块,如下所示:

try {
    $aggregate->changePrice(new ChangeProductPrice(
        $productId,
        $state->getPrice()
    ));
} catch (CannotChangePriceException $ex) {
}

一个用于价格的 try-catch 块,另一个用于可用性等。它有效,但我觉得它完全是错误的和hacky。我会更喜欢简单的 if 块和方法/规范,如CanChangePrice()。但是,我需要双重使用它——无论是在服务中还是在具体方法本身,这听起来也不好。

问题是:你们如何处理这些事情?这似乎是一个非常微不足道的问题,但我仍然没有找到让我感觉良好的解决方案。

【问题讨论】:

    标签: php domain-driven-design event-sourcing


    【解决方案1】:

    一些想法:

    • 为什么没有“更改价格和可用性”命令?这更好地捕捉了更新两者的意图,并允许不变检查具有更好的上下文知识(例如,如果我们将可用性更新为可用,那么当前的可用性是没有意义的,因为它是以前的遗留物)。

    • 我有点怀疑将价格更改为实际当前价格的尝试是错误的。在我看来,这个命令是表达一种将价格变成期望价格的愿望,所以如果这个愿望已经得到满足,为什么还要犯这个错误呢?返回$this(实际上是耸耸肩说“确定”)似乎更好,并且不会强迫请求者知道当前价格(严格来说,他们无法知道当前价格)。

    【讨论】:

    • 1.想了想。但是,我希望能够自行修改价格和可用性,而无需在每种情况下都传递这两个参数。即使假设它会起作用,我也会在另一个程序流程中遇到同样的问题,同时尝试更新产品名称和价格。这就是为什么我想保持它的原子性,这样我就可以组合不同的流程,只要给定的参数没有紧密耦合。
    • 2.那是个很好的观点。确实,在这种情况下,我可以简单地跳过发射事件。就它是真实且非常有用的见解而言,我的问题有点笼统-假设还有其他一些不变量-在这种情况下,我应该说“确定”以及何时我应该停止它并实际抛出异常?如果业务规则是“价格不能一次下跌超过 30%”,那是否已经是一个例外?如果是这样,我们回到主要问题。
    • 虽然有两个命令而不是一个命令并不是原子的,因为每个命令都是原子的,所以如果有的话,你就是“分子”。使聚合之外的东西将多个命令串在一起(如果第二个命令失败,则必须处理回滚第一个命令)。在聚合中,您的“更改价格和可用性”的实现可以委托给聚合原语(如果您的语言支持,某种流畅或单子 API 可能会有所帮助)。
    • 如示例,我直接以命令作为参数调用聚合方法。当所有方法都已经在聚合上调用时,才会发生持久化——毕竟,它无论如何都是一个事务。我可以采用三种方法——价格、可用性和两者结合。但是,在那种情况下,我需要将很多东西加倍(不变量、发射事件、改变状态等)。在组合方法中,我可以在内部调用其他方法,但话又说回来——我需要处理异常。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-24
    • 2013-12-11
    • 1970-01-01
    相关资源
    最近更新 更多