首先,我很长的帖子很抱歉。有很多代码可以显示出对该问题的详细了解,因此丢失了一些东西……请非常善于阅读所有内容:-)
我正在尝试使用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)
我不了解此设置中与此错误消息有关的一些内容:
请检查以下不同的类定义以获取更多详细信息。首先,我具有根聚合约会,该约会将同时用作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);
}
}
很少,您成功到了帖子结尾。首先谢谢你!您能告诉我哪里出了问题或我在做什么错吗?
致以诚挚的问候,库尔特
问题3从第三个问题中,我注意到您do not要使用Axon的状态存储聚合方法,而要使用事件源。另一方面,您也通过将其作为实体,<将聚合存储为状态对象。您的意图是什么?如果要使用Appointment
退还给感兴趣的各方,则应该知道您没有就此事遵守CQRS。
[Axon中带@Aggregate
注释的类通常指向命令模型。因此,它仅用于摄取命令,确定是否可以执行该命令的意图表达并因此而发布事件。
已添加,您声明将其放入Spring Boot应用程序中。从那里,我假设您也在使用axon-spring-boot-starter
依赖项。使用Axon的Spring自动配置时,@Aggregate
用作“ Spring原型”。最重要的是,如果@Aggregate
注释的对象用@Entity
注释,则自动配置将假定您要按原样存储Aggregate。因此,它将默认为具有状态存储聚合;您声明的内容不是您想要的。
问题1和2create命令可能是有效的,因为这是聚合的起始点。因此,它尚未基于标识符检索现有格式。
[其次,您收到的异常虽然包裹在 问题4和5CommandExecutionException
中,但最初可能来自您的数据库。在Axon的代码库中快速搜索文本Provided id of the wrong type for class
不会产生任何结果。请注意,Axon将始终假定ID可以转换为String
。因此,专用的toString()
方法可能有益于不将不需要的信息附加到String
。 这是Allard询问的更多信息,这可能与Aggregate本质上现在是国家存储的事实有关。因此,对于给定的Aggregate,异常是从GenericJpaRepository
使用的JPA实现中冒出来的(这是此存储库。Axon会在给定当前设置的情况下自动为您配置)。@EventSourcingHandler
带注释的方法更新汇总并在您的应用程序中具有一个独特的Spring Component来处理事件以更新投影,这确实是完全可以的。通过Axon执行CQRS时,我会将其视为“要走的路”。
您最近遇到的问题需要我做一个假设。我猜您尚未对正在使用的Event Processor进行任何特定配置。这意味着Axon将为您自动配置TrackingEventProcessor
。此实现的功能之一是将其进度(“处理事件流中的事件有多远”)存储在Token中。这些标记又应与您的预测一起存储,因为它们定义了整个事件流中您的预测的最新状态。
token_entry
表不存在或在每次启动时都已清除。结论
在这里满口,绝对希望这对您有所帮助!如果不清楚,请评论我的答案;我将相应地更新我的回复。