Axon Sagas在将事件重放到新数据库时复制事件存储中的事件

Axon Sagas在将事件重放到新数据库时复制事件存储中的事件,axon,Axon,我们有一个存储新订单的Axon应用程序。对于每个订单状态更改(OrderStateChangedEvent),它计划了两个任务。这些任务是由另一个传奇故事触发和进行的(TaskSaga-超出问题范围) 当我删除投影数据库,但离开事件存储区,然后再次运行应用程序时,事件将被重放(正确),但任务将被复制 我想这是因为每次OrderStateChangedEvent都会触发一组新的ScheduleTaskCommand 因为我是轴突方面的新手,我不知道如何避免这种重复 在AxonServer上运行的事

我们有一个存储新订单的Axon应用程序。对于每个订单状态更改(OrderStateChangedEvent),它计划了两个任务。这些任务是由另一个传奇故事触发和进行的(TaskSaga-超出问题范围)

当我删除投影数据库,但离开事件存储区,然后再次运行应用程序时,事件将被重放(正确),但任务将被复制

我想这是因为每次
OrderStateChangedEvent
都会触发一组新的
ScheduleTaskCommand

因为我是轴突方面的新手,我不知道如何避免这种重复

在AxonServer上运行的事件存储

Spring启动应用程序自动配置axon内容

投影数据库包含投影表和轴突表: 令牌输入 传奇故事 关联\u值\u条目

我假设所有事件都被重放,因为通过重新创建数据库,Axon表消失了(因此没有关于上次应用事件的记录)

我错过什么了吗

  • token_条目/saga_条目/association_value_条目表是否应该是每个应用程序节点上投影表的DB的一部分
  • 我认为事件存储可以随时重放到新应用程序节点的数据库中,而不必更改事件历史记录,这样我就可以运行任意数量的节点。或者,我可以随时删除投影数据库并运行应用程序,这会导致事件再次投影到新的数据库。或者这不是真的
  • 一般来说,我的问题是一个事件产生命令,导致产生新事件(重复)。我是否应该避免事件的“链接”以避免重复
谢谢

轴突结构:

@Configuration
public class AxonConfig {

    @Bean
    public EventSourcingRepository<ApplicationAggregate> applicationEventSourcingRepository(EventStore eventStore) {
        return EventSourcingRepository.builder(ApplicationAggregate.class)
                        .eventStore(eventStore)
                        .build();
    }

    @Bean
    public SagaStore sagaStore(EntityManager entityManager) {
        return JpaSagaStore.builder().entityManagerProvider(new SimpleEntityManagerProvider(entityManager)).build();
    }
}
  • Order aggregate设置属性
  • OrderStateChangeEvent由Saga监听,Saga根据特定状态的顺序安排两个任务
  • private Map tasks=new HashMap();
    私有OrderState-OrderState;
    @斯塔萨加
    @SagaEventHandler(associationProperty=“orderId”)
    (OrderStateChangeEvent事件)上的公共无效{
    orderState=event.getNewState();
    List tasksByState=tasksservice.getTasksByState(orderState);
    if(tasksByState.isEmpty()){
    finishSaga(event.getOrderId());
    }
    tasksByState.stream()
    .map(任务->ScheduleTaskCommand.builder()
    .orderId(事件.getOrderId())
    .taskId(IdentifierFactory.getInstance().generateIdentifier())
    .targetState(订单状态)
    .taskName(task.getTaskName())
    .build())
    .peek(command->tasks.put(command.getTaskId(),SCHEDULED))
    .forEach(命令->命令网关.send(命令));
    }
    
    在这种情况下,我想我可以帮你。 因此,发生这种情况是因为将所有事件提供给Saga实例的
    TrackingEventProcessor
    使用的
    TrackingToken
    被初始化到事件流的开头。因此,
    TrackingEventProcessor
    将从时间开始启动,从而第二次调度所有命令

    你可以做一些事情来解决这个问题

  • 您可以只擦除投影表并保持令牌表不变,而不是擦除整个数据库
  • 您可以将
    TrackingEventProcessor
    initialTrackingToken
    配置为从事件流的头部而不是尾部开始
  • 选项1将解决find问题,但需要从运营角度进行一些授权。选项2将其交给开发人员,可能比其他解决方案更安全

    要将令牌调整为从头部开始,可以使用
    TrackingEventProcessor配置来实例化
    TrackingEventProcessor

        EventProcessingConfigurer configurer;
    
        TrackingEventProcessorConfiguration trackingProcessorConfig =
                TrackingEventProcessorConfiguration.forSingleThreadedProcessing()
                                                   .andInitialTrackingToken(StreamableMessageSource::createHeadToken);
    
        configurer.registerTrackingEventProcessor("{class-name-of-saga}Processor", 
                                                  Configuration::eventStore, 
                                                  c -> trackingProcessorConfig);
    
    因此,您需要为您的传奇创建所需的配置,并调用
    和initialTrackingToken()
    函数,确保创建的头令牌不存在令牌


    我希望这能帮到你,汤姆

    史蒂文的解决方案很有魅力,但只适用于传奇故事。对于那些想要达到相同效果但在经典的
    @EventHandler
    (在重播时跳过执行)中的人,有一种方法。首先,您必须了解跟踪事件处理器的名称-我在AxonDashboard(运行AxonServer的8024端口)中找到了它-通常它是带有
    @EventHandler
    注释(确切地说是包名)的组件的位置。然后添加Steven在回答中指出的配置

        @Autowired
        public void customConfig(EventProcessingConfigurer configurer) {
            // This prevents from replaying some events in @EventHandler
            var trackingProcessorConfig = TrackingEventProcessorConfiguration
                    .forSingleThreadedProcessing()
                    .andInitialTrackingToken(StreamableMessageSource::createHeadToken);
            configurer.registerTrackingEventProcessor("com.domain.notreplayable",
                    org.axonframework.config.Configuration::eventStore,
                    c -> trackingProcessorConfig);
        }
    
    

    顺便说一句,我刚刚创建了一个问题来讨论将要切换的默认设置,特别是对于传奇。我已经多次遇到了这个问题,我觉得这应该在框架中进行调整。有没有任何答案解决了你的问题@TomášMika?如果你能分享你的行动方针,或者如果答案解决了你的问题,对其他读者会有好处。
        private Map<String, TaskStatus> tasks = new HashMap<>();
    
        private OrderState orderState;
    
        @StartSaga
        @SagaEventHandler(associationProperty = "orderId")
        public void on(OrderStateChangedEvent event) {
            orderState = event.getNewState();
            List<OrderStateAwareTaskDefinition> tasksByState = taskService.getTasksByState(orderState);
            if (tasksByState.isEmpty()) {
                finishSaga(event.getOrderId());
            }
    
            tasksByState.stream()
                    .map(task -> ScheduleTaskCommand.builder()
                            .orderId(event.getOrderId())
                            .taskId(IdentifierFactory.getInstance().generateIdentifier())
                            .targetState(orderState)
                            .taskName(task.getTaskName())
                            .build())
                    .peek(command -> tasks.put(command.getTaskId(), SCHEDULED))
                    .forEach(command -> commandGateway.send(command));
        }
    
        EventProcessingConfigurer configurer;
    
        TrackingEventProcessorConfiguration trackingProcessorConfig =
                TrackingEventProcessorConfiguration.forSingleThreadedProcessing()
                                                   .andInitialTrackingToken(StreamableMessageSource::createHeadToken);
    
        configurer.registerTrackingEventProcessor("{class-name-of-saga}Processor", 
                                                  Configuration::eventStore, 
                                                  c -> trackingProcessorConfig);
    
        @Autowired
        public void customConfig(EventProcessingConfigurer configurer) {
            // This prevents from replaying some events in @EventHandler
            var trackingProcessorConfig = TrackingEventProcessorConfiguration
                    .forSingleThreadedProcessing()
                    .andInitialTrackingToken(StreamableMessageSource::createHeadToken);
            configurer.registerTrackingEventProcessor("com.domain.notreplayable",
                    org.axonframework.config.Configuration::eventStore,
                    c -> trackingProcessorConfig);
        }