当前位置: 首页 > 知识库问答 >
问题:

以@AggregateIdentifier和@TargetAggregateIdentifier的

易琛
2023-03-14

首先,我为很长的帖子道歉。有相当多的代码,以显示有一个问题的详细理解,因此失去了张贴的东西...请你阅读所有的内容:-)

我不明白此设置中与此错误消息相关的一些事情:

  1. 为什么create命令和事件与confirm命令/event使用相同的方法(至少就我目前的理解而言)时会按预期工作?
  2. 为什么Axon抱怨一个appoinmentId作为(当然是聚合)的标识符,而相应的代码(见下文)为@aggregateIdentier和@targetaggregateIdentier注释了两种字符串类型?
  3. 在Axon使用聚合和实体(在本例中是由Spring管理并链接到关系数据库的JPA存储库)时,是否允许使用相同的代码将聚合直接存储到持久存储中(我认为我不应该使用参考指南中描述的状态存储聚合方法,因为我仍然希望我的解决方案是事件驱动的,用于创建和更新约会)?
  4. 这是使用事件机制保持聚合状态最新的正确方法吗?是否可以使用一个不同的Spring@Component类来实现一系列@EventHandler方法来对关系数据库执行CRUD操作。在后者中,create事件将按预期处理,约会将存储在数据库中。由于前面的错误消息,没有触发确认事件。
  5. 参考第4项,如果重新启动Axon并开始向第4中的事件处理程序发出不同的事件,我对会发生什么有点困惑。这会不会导致很多数据库错误,因为约会仍然在数据库中,或者在最坏的情况下,相同约会的无穷无尽的重复?换句话说,我在这个项目中使用的方法和我对事件驱动的应用程序/服务的理解似乎有问题。

请查看下面的不同类定义以获得更多的删除信息。首先,我有根聚合约会,它将同时用作JPA实体。

@Aggregate
@Entity
@Table(name = "t_appointment")
public final class Appointment extends AbstractEntity<AppointmentId> {

    //JPA annotated class members left out for brevity

    @PersistenceConstructor
    private Appointment() {
        super(null);
        //Sets all remaining class members to null.
    }

    @CommandHandler
    private Appointment(CreateAppointmentCommand command) {
        super(command.getAggregateId());
        validateFields(getEntityId(), ...);
        AggregateLifecycle.apply(new AppointmentCreatedEvent(getEntityId(), ...);
    }

    @EventSourcingHandler
    private void on(AppointmentCreatedEvent event) {
        validateFields(event.getAggregateId(), ...);
        initFields(event.getAggregateId(), ...);
    }

    private void validateFields(AppointmentId appointmentId, ...) {
        //Check if all arguments are within the required boundaries.
    }

    private void initFields(AppointmentId appointmentId, ...) {
        //Set all class level variables to passed in value.
    }

    @CommandHandler
    private void handle(ConfirmAppointmentCommand command) {
        AggregateLifecycle.apply(new AppointmentConfirmedEvent(command.getAggregateId()));
    }

    @EventSourcingHandler
    private void on(AppointmentConfirmedEvent event) {
        confirm();
    }

    public void confirm() {
        changeState(State.CONFIRMED);
    }   

    //Similar state changing command/event handlers left out for brevity.

    private void changeState(State newState) {
        switch (state) {
        ...
        }
    }

    //All getter methods left out for brevity. The aggregate does NOT provide any setters.

    @Override
    public String toString() {
        return "Appointment [...]";
    }
}
@MappedSuperclass
@SuppressWarnings("serial")
public abstract class AbstractEntity<ENTITY_ID extends AbstractId> implements Serializable{

    @EmbeddedId
    private ENTITY_ID entityId;

    @AggregateIdentifier
    private String targetId;


    protected AbstractEntity(ENTITY_ID id) {
        this.LOG = LogManager.getLogger(getClass());
        this.entityId = id;
        this.targetId = id != null ? id.getId() : null;
    }

    public final ENTITY_ID getEntityId() {
        return entityId;
    }

    @Override
    public final int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((entityId == null) ? 0 : entityId.hashCode());
        return result;
    }

    @Override
    public final boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        AbstractEntity<?> other = (AbstractEntity<?>) obj;
        if (entityId == null) {
            if (other.entityId != null)
                return false;
        } else if (!entityId.equals(other.entityId))
            return false;
        return true;
    }
}
@MappedSuperclass
@SuppressWarnings("serial")
public abstract class AbstractId implements Serializable{

    @Column(name = "id")
    private String id;


    protected AbstractId() {
        this.id = UUID.randomUUID().toString();
    }

    public final String getId() {
        return id;
    }

    @Override
    public final int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((id == null) ? 0 : id.hashCode());
        return result;
    }

    @Override
    public final boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        AbstractId other = (AbstractId) obj;
        if (id == null) {
            if (other.id != null)
                return false;
        } else if (!id.equals(other.id))
            return false;
        return true;
    }

    public final String toString() {
        return id;
    }
}

在聚合中,使用了许多命令和事件。每个命令都是command的子类。

@SuppressWarnings("serial")
public abstract class Command<AGGREGATE_ID extends AbstractId> implements Serializable{

    private AGGREGATE_ID aggregateId;

    @TargetAggregateIdentifier
    private String targetId;

    protected Command(AGGREGATE_ID aggregateId) {
        if(aggregateId == null) {
            throw new InvalidArgumentException(...);
        }   
        this.aggregateId = aggregateId;
        this.targetId = aggregateId != null ? aggregateId.getId() : null;
    }

    public final AGGREGATE_ID getAggregateId() {
        return aggregateId;
    }   
}

一个指定的命令类(这在我的方法中造成了困难)是ConfirmAppointmentCommand,它实际上只不过是基命令类的具体实现。因此,实现非常直接。

public final class ConfirmAppointmentCommand extends Command<AppointmentId> {
    private static final long serialVersionUID = 6618106729289153342L;

    public ConfirmAppointmentCommand(AppointmentId appointmentId) {
        super(appointmentId);       
    }   
}

CreateAppointmentCommand与ConfirmAppointmentCommand非常相似,定义如下。

public final class CreateAppointmentCommand extends Command<AppointmentId> {
    private static final long serialVersionUID = -5445719522854349344L;

    //Some additional class members left out for brevity.

    public CreateAppointmentCommand(AppointmentId appointmentId, ...) {
        super(appointmentId);

        //Check to verify the provided method arguments are left out.

        //Set all verified class members to the corresponding values.
    }

    //Getters for all class members, no setters are being implemented.

}
    @SuppressWarnings("serial")
    public abstract class DomainEvent<T extends AbstractId> implements Serializable{

        private T aggregateId;


        protected DomainEvent(T aggregateId) {
            if(aggregateId == null) {
                throw new InvalidArgumentException(ErrorCodes.AGGREGATE_ID_MISSING);
            }           
            this.aggregateId = aggregateId;
        }

        public final T getAggregateId() {
            return aggregateId;
        }   
    }
public final class AppointmentCreatedEvent extends DomainEvent<AppointmentId> {
    private static final long serialVersionUID = -5265970306200850734L;

    //Class members left out for brevity

    public AppointmentCreatedEvent(AppointmentId appointmentId, ...) {
        super(appointmentId);

        //Check to verify the provided method arguments are left out.

        //Set all verified class members to the corresponding values.
    }

    //Getters for all class members, no setters are being implemented.
}
public final class AppointmentConfirmedEvent extends DomainEvent<AppointmentId> {
    private static final long serialVersionUID = 5415394808454635999L;

    public AppointmentConfirmedEvent(AppointmentId appointmentId) {
        super(appointmentId);       
    }
}

很少,你做到了直到帖子结束。谢谢你在第一次请!你能告诉我哪里出了问题或者我做错了什么吗?

向你致意,库尔特

共有1个答案

商焕
2023-03-14

问题3在您的第三个问题中,我注意到您不想使用Axon的状态存储聚合方法,而是使用事件源。另一方面,通过使聚合成为一个实体,您也将其存储为一个状态对象。

你这么做的目的是什么?如果这是使用约会返回给感兴趣的各方,那么您应该知道您在这件事上没有遵循CQRS。

Axon中的@aggrege注释类通常指向命令模型。因此,它纯粹用于摄取命令,决定是否可以执行该命令的意图表达,并为此发布事件。

添加后,您声明您将把它放在一个Spring Boot应用程序中。从这里,我假设您也在使用axon-spring-boot-starter依赖项。当使用Axon的Spring auto配置时,@aggrege作为“Spring原型”工作。此外,如果@aggrege注释对象也使用@entity注释,则自动配置假定您希望按原样存储聚合。因此,它将默认拥有一个状态存储的聚合;你陈述的东西不是你想要的。

问题1和2create命令可能会起作用,因为这是聚合的起始点。因此,它还没有基于标识符检索现有格式。

如果您注意到每次启动时都会调用事件处理组件中的事件处理程序,那么对我来说,token_entry表要么不在那里,要么在每次启动时被清除。

结束了相当一口在这里,绝对希望这有助于你的库尔特!如果有不清楚的地方,请评论我的回答;我会相应地更新我的回应。

 类似资料:
  • 一般来说,这似乎是有效的,因为当我在第二个服务中发送命令时,它会被接收并更新状态。由于我可以使用EventSourcingHandler还可以使用在另一个服务中创建的事件来操作它的状态,所以我从第一个服务聚合应用的源中获取状态信息。 我担心快照机制会对我不利,但显然它足够聪明,只要我确保聚合的“类型”名称不相同,就可以单独存储快照。 到目前为止,一切都很好,唯一让我感觉到的是,第二个聚合没有(需要

  • 本文向大家介绍==和===、以及Object.is的区别?相关面试题,主要包含被问及==和===、以及Object.is的区别?时的应答技巧和注意事项,需要的朋友参考一下 参考回答: (1) == 主要存在:强制转换成number,null==undefined " "==0 //true "0"==0 //true " " !="0" //true 123=="123" //true null=

  • 问题内容: 好的,我正在做Euler项目的挑战,我简直不敢相信我会遇到第一个挑战。尽管我的代码看起来很正常,但我真的看不到为什么我得到错误的答案: 我得到的答案是266333,它说错了… 问题答案: 您应该对两者都使用相同的for循环,以避免重复计算两者的倍数。例如15,30 …

  • 我们的项目需要同时支持Oracle和Postgres DBS。并且可能会有更多的数据库添加到此列表中。因此需要为BLOB和CLOB数据类型提供一个与DB无关的Hibernate配置。 而Oracle正在与以下方面进行良好的合作: Postgres也开始抱怨。 研究发现,人们建议使用和进行注释。这两个都是Hibernate注释。但是,我们打算使用JPA注释,这需要在运行时使用Hibernate,在我

  • 数值N的范围为。函数定义为一个数x的所有数字之和。我们必须求特殊对(x,y)的个数的计数,使得: 1。 2。本质上是素数 我们只能对和计数一次。打印输出的模 我的方法: 因为给定范围内的数字和的最大值可以是450(如果长度为50的数字中所有字符都是9,则)。因此,我们可以创建一个大小为451*451的二维数组,并且对于所有的对,我们可以存储它是否为素数。 现在,我面临的问题是在线性时间内找到给定数

  • 我开始使用Intellij Idea为Android开发应用程序,因为它是去年的首选IDE。两个IDE:Intellij Idea 1的致命错误让我大吃一惊。我创建了自定义视图: 然后,我试着在Idea的UI设计器中看到结果: null 更新的答案:因为我的名声很小,所以我不能回答我自己的问题,所以回答问题:我的自定义视图在com.example.customview.ImageCustomVie