值对象作为@AggregateIdentifier和@TargetAggregateIdentifier

问题描述 投票:0回答:1

首先,我很长的帖子很抱歉。有很多代码可以显示出对该问题的详细了解,因此丢失了一些东西……请非常善于阅读所有内容:-)

我正在尝试使用Axon框架和Spring Boot应用程序来开发基于事件源的应用程序。我提供了以下几个类定义,它们构成了集合,命令和事件的实现。

我编写了一个非常简单的测试应用程序(Spring),只不过向Axon发送了CreateAppointmentCommand。此create命令使用手动分配的AppointmentId(它是AbstractId的子类),并返回此ID供以后使用。没错,Appointment中的构造函数命令处理程序将按预期方式调用,并且相应的ApppointmentCreatedEvent将被触发,并且也将由Appoitment类按预期进行处理。到目前为止,一切都很好。当我发送带有create命令返回的ID的ConfirmAppointmentCommand时,会出现问题。在这种情况下,我会收到一条错误消息:

Command'ConfirmAppointmentCommand'导致org.axonframework.commandhandling.CommandExecutionException(为类Appointment提供了错误类型的ID。期望:类AppointmentId,获得类java.lang.String)

我不了解此设置中与此错误消息有关的一些内容:

  1. 为什么与确认命令/事件相比,当创建命令和事件使用相同的方法(至少到目前为止,到目前为止我仍然理解)时,它们按预期方式工作?
  2. 为什么Axon为什么抱怨AppointmentId作为(大概是聚合)的标识符,而相应的代码(请参见下文)却为@AggregateIdentier和@TargetAggregateIdentier的两种字符串类型都注解了?
  3. 我被Axon使用时,我允许对聚合和实体使用相同的代码将聚合直接存储到持久性存储中(在本例中是由Spring管理并链接到关系数据库的JPA存储库)(我不'我认为我不应该使用参考指南中描述的状态存储聚合方法,因为我仍然希望我的解决方案受事件驱动以创建和更新约会?]
  4. 这是使用事件机制使聚合状态保持最新的正确方法,并且可以使用不同的Spring @Component类来实现这一点,该类正在实现一系列@EventHandler方法以对关系进行CRUD操作数据库。在后者中,将按预期处理创建事件,并将约会存储在数据库中。由于先前的错误消息,未触发确认事件。
  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 [...]";
    }
}

AbstractEntity类是所有JPA实体和聚合的基类。此类具有以下定义。

@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;
    }
}

EntityId(将用作JPA实体的主键)是具有以下基类定义的'复杂'值对象。

@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,它实际上不过是基本Command类的具体实现。因此,实现非常简单。

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.

}

对于项目中使用的不同事件,使用类似的方法。所有事件都继承了DomainEvent基类,如下所示。

    @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;
        }   
    }

AppointmentCreatedEvent非常简单。

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.
}

最后,为了完整起见,AppointmentConfirmedEvent。

public final class AppointmentConfirmedEvent extends DomainEvent<AppointmentId> {
    private static final long serialVersionUID = 5415394808454635999L;

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

很少,您成功到了帖子结尾。首先谢谢你!您能告诉我哪里出了问题或我在做什么错吗?

致以诚挚的问候,库尔特

spring domain-driven-design cqrs event-sourcing axon
1个回答
0
投票

问题3从第三个问题中,我注意到您do not要使用Axon的状态存储聚合方法,而要使用事件源。另一方面,您也通过将其作为实体,<将聚合存储为状态对象。您的意图是什么?如果要使用Appointment退还给感兴趣的各方,则应该知道您没有就此事遵守CQRS。

[Axon中带@Aggregate注释的类通常指向命令模型。因此,它仅用于摄取命令,确定是否可以执行该命令的意图表达并因此而发布事件。

已添加,您声明将其放入Spring Boot应用程序中。从那里,我假设您也在使用axon-spring-boot-starter依赖项。使用Axon的Spring自动配置时,@Aggregate用作“ Spring原型”。最重要的是,如果@Aggregate注释的对象用@Entity注释,则自动配置将假定您要按原样存储Aggregate。因此,它将默认为具有状态存储聚合;您声明的内容不是您想要的。

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

[其次,您收到的异常虽然包裹在CommandExecutionException中,但最初可能来自您的数据库。在Axon的代码库中快速搜索文本Provided id of the wrong type for class不会产生任何结果。请注意,Axon将始终假定ID可以转换为String。因此,专用的toString()方法可能有益于不将不需要的信息附加到String这是Allard询问的更多信息,这可能与Aggregate本质上现在是国家存储的事实有关。因此,对于给定的Aggregate,异常是从GenericJpaRepository使用的JPA实现中冒出来的(这是此存储库。Axon会在给定当前设置的情况下自动为您配置)。

问题4和5

通过@EventSourcingHandler带注释的方法更新汇总并在您的应用程序中具有一个独特的Spring Component来处理事件以更新投影,这确实是完全可以的。通过Axon执行CQRS时,我会将其视为“要走的路”。

您最近遇到的问题需要我做一个假设。我猜您尚未对正在使用的Event Processor进行任何特定配置。这意味着Axon将为您自动配置TrackingEventProcessor。此实现的功能之一是将其进度(“处理事件流中的事件有多远”)存储在Token中。这些标记又应与您的预测一起存储,因为它们定义了整个事件流中您的预测的最新状态。

[如果您注意到每次启动时都会从事件处理组件中调用事件处理程序,那么对我来说,这表明token_entry表不存在或在每次启动时都已清除。

结论

在这里满口,绝对希望这对您有所帮助!如果不清楚,请评论我的答案;我将相应地更新我的回复。
© www.soinside.com 2019 - 2024. All rights reserved.