【问题标题】:How are aggregates instantiated to test other aggregates with?如何实例化聚合以测试其他聚合?
【发布时间】:2018-11-15 15:02:00
【问题描述】:

假设我有一个聚合,对于某些操作,需要存在另一个聚合。假设我有一个car 和一个garage。可能有一个名为ParkInGarage 的命令如下所示:

public class ParkInGarage {

    @TargetAggregateIdentifier
    public final UUID carId;

    public final Garage garage;

    //... constructor omitted
}

read 表示要验证聚合的存在,最好在命令中使用加载的聚合,因为这已经暗示了它的存在(而不是传递 garageId)。

现在,当使用Axon's fixturesCar 进行单元测试时,我不能简单地通过说new Garage(buildGarageCmd) 来实例化我的Garage。它会说:

java.lang.IllegalStateException:如果没有活动范围,则无法请求当前范围

因为没有设置基础设施。 我将如何测试这种情况,还是应该以不同的方式设计聚合?

抽象的真实示例

我正在使用的聚合根可能具有对自身的引用,以形成所述聚合根的树结构。我们就叫它Node吧。

@Aggregate
public class Node {
    private Node parentNode;
}

创建后,我可以将Optional<Node> 作为父级传递,或者稍后使用单独的命令设置父级。是否应该将父级定义为实例或 ID 是问题的一部分。

public class AttachNodeCmd {
    @TargetAggregateIdentifier
    public final UUID nodeId;
    public final Optional<Node> parentNode;
}

在命令处理程序中,我需要检查将节点附加到给定父节点是否会引入循环(结构应该是树,而不是公共图)。

@CommandHandler
public Node(AttachNodeCmd command) {
    if (command.parentNode.isPresent()) {
        Node currentNode = command.parentNode.get();
        while (currentNode != null) {
            if (currentNode.equals(this)) throw new RecursionException();
            currentNode = currentNode.parentNode.orElse(null);
        }
    }

    //Accept the command by applying() an Event
}

在某些时候,需要实例化父级以执行这些检查。这可以通过在命令中提供聚合实例来完成(不鼓励),或者通过向命令处理程序提供Repository&lt;Node&gt;nodeId 来完成,这是聚合本身,也是不鼓励的。目前我还没有找到正确的方法来做这件事,并且还没有进一步测试的方法。

【问题讨论】:

    标签: unit-testing domain-driven-design aggregateroot axon


    【解决方案1】:

    我不会将 AR 实例放在命令中。命令模式应该是稳定的并且易于序列化/重新序列化,因为它们是消息契约。

    您可以做的是解决命令处理程序中的依赖关系。

    //ParkInGarage command handler
    Garage garage = garageRepository.garageOfId(command.garageId);
    Car car = carRepository.carOfId(command.carId);
    
    car.parkIn(garage);
    

    我根本不了解 Axon 框架,但现在应该比较容易测试。

    【讨论】:

    • 这是一个很好的解决方案,但是 Axon 不允许您在命令处理程序中注入存储库,并且还会阻止您在聚合中使用存储库,因为加载需要时间并且增加了聚合时并发修改的机会锁定。你的提议很有道理,但我担心在这种情况下它更像是一个框架限制。
    • 似乎是一个奇怪的框架限制,因为解决命令处理程序/应用程序服务中的依赖关系是 DDD 从业者的行业标准。
    【解决方案2】:

    我认为@plalx 让您走上正轨。命令是您的 API/消息契约的一部分,在其中公开聚合并不是一个好主意。 另外我想指出,Axon 中的AggregateFixtures 用于测试单个聚合,而不是聚合之间的操作协调。

    聚合/有界上下文之间的协调通常是您看到 saga 发挥作用的地方。现在说实话,我有点怀疑这个用例是否证明了 Saga,但我可以想象如果 ParkCarInGarageCommand 失败,因为 Garage 聚合已满(例如),你需要指示 @ 987654324@ 通过另一个命令聚合,告诉它不可行。在 Axon 中设置的 Saga 可能会帮助您解决此问题,因为您可以轻松捕获 (1) 处理命令的异常或 (2) 处理通知操作不成功的事件。

    【讨论】:

    • ParkCarInGarageCommand 将被定向到什么聚合?据我了解,传奇只是由事件开始的。因此,最初必须有其他东西来处理命令。 “其他东西”如何获得所需聚合的实例?你能举个例子吗?
    • 我已经编辑了我的帖子,以提供一个更具体的例子来说明我正在尝试做什么和测试。如果 Axon 可以为此提供一个示例解决方案,那就太好了,因为目前我在文档中找不到任何提示。我确定这是一个常见问题。
    • 另外,如果我在命令或事件中不能有任何聚合(成员),我如何重建一个聚合根,其对象指针指向只有 ID 的聚合成员?所有这些规则都非常严格。
    • 任何组件/单例/bean 都可以有一个@CommandHandler 注释。因此,您可以添加一个单独的单例来处理ParkCarInGarageCommand,例如,它依次发送命令。如果您想使用 Saga,请使用,它应该从某些事件开始,然后可以将命令发布到与其关联的聚合。这意味着您将有一个不同的起点,因为我不熟悉您的域,所以我很难告诉您是哪一个起点。
    • 谢谢,我想我已经掌握了窍门。树“一致性边界”的解决方案是引入另一个实体作为聚合根并保护树结构不变量。所有的操作都是通过它来执行的,例如添加节点和链接它们。在用于添加节点的事件(采购)处理程序中,正在那里实例化新节点。这消除了对存储库或存在检查的需要。
    猜你喜欢
    • 1970-01-01
    • 2011-01-16
    • 1970-01-01
    • 2019-10-07
    • 2016-05-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-27
    相关资源
    最近更新 更多