簡體   English   中英

如何配置 Spring Boot App 連接到 Axon 服務器並注冊事件處理程序

[英]How to configure Spring Boot App to connect to Axon Server and register event handler

首先,我有一個 spring 啟動應用程序,它使用連接到服務器 4.2.4 的 axon 4.5.5 庫。 這受支持嗎?

這個 spring 啟動應用程序應該監聽來自(發送到)軸突服務器的(主要應用程序)的幾個事件,這是我在下面的 application.yml 中的 sprint 啟動軸突客戶端配置。

其次,此應用程序連接到 axon 服務器但無法處理任何導致所有上述事件被列入黑名單的事件。 我已經追蹤到事件處理程序注冊可能是導致問題的原因。 我們在調用 registerTrackingEventProcessor() 時使用 EventStore 作為 StreamableMessageSource。

你們知道為什么注冊的事件處理程序沒有觸發嗎? 我可以看到 spring 啟動應用程序已連接到儀表板上的 axon 服務器以及觸發事件的主應用程序。 我還可以在儀表板中搜索時看到觸發的事件,並在 axon 服務器日志中看到它們的黑名單。 所以我猜這是導致問題的配置。

這是我的庫版本(來自 pom.xml):

Version 4.5.5
axon-test
axon-spring-boot-autoconfigure
axon-spring-boot-starter
axon-modelling
axon-metrics
axon-messaging
axon-eventsourcing

Version 2.6.0
spring-boot-starter-web

這是我的 application.yml 軸突片段:

axon:
  axonserver:
    client-id: reporting-etl-client
    component-name: reporting-etl
    query-threads: ${AXON_QUERY_THREADS_MAX:50}
    servers: ${AXON_AXONSERVER_SERVERS:axonserver}
  serializer:
    events: jackson

和 AxonConfig.java:

package com.fedeee.reporting.config;

import com.fedeee.reporting.axon.events.EventHandlerProjector;
import com.thoughtworks.xstream.XStream;
import org.axonframework.axonserver.connector.query.AxonServerQueryBus;
import org.axonframework.config.EventProcessingConfigurer;
import org.axonframework.config.ProcessingGroup;
import org.axonframework.eventhandling.EventBus;
import org.axonframework.eventhandling.TrackingEventProcessorConfiguration;
import org.axonframework.eventhandling.gateway.DefaultEventGateway;
import org.axonframework.eventhandling.gateway.EventGateway;
import org.axonframework.queryhandling.DefaultQueryGateway;
import org.axonframework.queryhandling.QueryBus;
import org.axonframework.queryhandling.QueryGateway;
import org.axonframework.serialization.AbstractXStreamSerializer;
import org.axonframework.serialization.Serializer;
import org.axonframework.serialization.xml.XStreamSerializer;
import org.reflections.Reflections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.Assert;

import java.util.Set;

@Configuration
public class AxonConfig {

  private static final Logger LOG = LoggerFactory.getLogger(AxonConfig.class);

  /**
   * Correctly configuring the XStream serializer to avoid security warnings.
   */
  @Autowired
  public void configureXStream(Serializer serializer) {
    if (serializer instanceof AbstractXStreamSerializer) {
      XStream xStream = ((XStreamSerializer) serializer).getXStream();
      XStream.setupDefaultSecurity(xStream);
      xStream.allowTypesByWildcard(new String[] {"com.fedeee.pkg.api.events.**", "org.axonframework.**"});
    }
  }

  /**
   *
   * @param configurer
   * @param context
   */
  @Autowired
  public void configure(EventProcessingConfigurer configurer, ApplicationContext context) {
    LOG.info("Setting up TrackingEventProcessors for threads, batch size and other configurations..."
        + " annotated with @ProcessingGroup...");
    // find classes in the com.fedeee.* package that has methods annotated with @ProcessingGroup to configure
    Reflections reflections = new Reflections("com.fedeee.reporting.axon.events");
    Set<Class<?>> annotatedClasses = reflections.getTypesAnnotatedWith(ProcessingGroup.class);

    // Configure each identified class
    annotatedClasses.stream().forEach(annotatedClass -> {
      // Locate the appropriate spring bean to get appropriate values from each one.
      String beanName = annotatedClass.getName().substring(annotatedClass.getName().lastIndexOf(".") + 1);
      beanName = beanName.substring(0,1).toLowerCase() + beanName.substring(1);
      Object projObj = context.getBean(beanName);
      if (projObj instanceof EventHandlerProjector) {
        EventHandlerProjector projector = (EventHandlerProjector) projObj;
            LOG.info("Configuring EventHandlerProjector Bean '{}' with maxThreads: {} and batchSize: {}.",
            beanName, projector.getMaxThreads(), projector.getBatchSize());
        ProcessingGroup pgAnnotation = annotatedClass.getAnnotation(ProcessingGroup.class);
        String processingGroup = pgAnnotation.value();

        configurer.registerTrackingEventProcessor(
                processingGroup,
                org.axonframework.config.Configuration::eventStore,
                conf -> TrackingEventProcessorConfiguration.forParallelProcessing(projector.getMaxThreads())
                        .andBatchSize(projector.getBatchSize())
        ).registerHandlerInterceptor(processingGroup, configuration -> new EventHandlerLoggingInterceptor());
        // Enable logging for EventHandlers

        LOG.info(".. '{}' successfully configured with processing group '{}'.", beanName, processingGroup);
      } else {
        LOG.info(".. '{}' failed to configure with any processing group.", beanName);
      }
    });

    // TODO: handle tracking event processor initialization. See the axon mailing list thread:
    // *****************************************************************************************************************
    // https://groups.google.com/forum/#!topic/axonframework/eyw0rRiSzUw
    // In that thread there is a discussion about properly initializing the token store to avoid recreating query models.
    // I still need to understand more about this...
    // *****************************************************************************************************************
  }

//  @Autowired
//  public void configureErrorHandling(
//      EventProcessingConfigurer configurer, ErrorHandler errorHandler
//  ) {
//    configurer.registerDefaultListenerInvocationErrorHandler(c -> errorHandler);
//  }

  @Autowired
  public void registerInterceptors(QueryBus queryBus) {
    Assert.notNull(queryBus, "Invalid configuration, queryBus is null!");
    if (AxonServerQueryBus.class.isAssignableFrom(queryBus.getClass())) {
      queryBus.registerHandlerInterceptor(InterceptorSupport.authorizationHandlerInterceptor());
    }

  }

  @Bean
  public QueryGateway queryGateway(QueryBus queryBus) {
    return DefaultQueryGateway.builder().queryBus(queryBus).build();
  }

  @Bean
  public EventGateway eventGateway(EventBus eventBus) {
    return DefaultEventGateway.builder().eventBus(eventBus).build();
  }

}

這是 EventHandlerProjector.java:

package com.fedeee.reporting.axon.events;

/**
 * Defines a contract to ensure we specify the number of threads and batch size to be allowed by the event
 */
public interface EventHandlerProjector {

  /**
   * Specifies the number of records per batch to be handled by the tracking processor.
   * @return
   */
  public default Integer getBatchSize() {
    return 1;
  }

  /**
   * Specifies the maximumn number of threads the tracking processor can be specified to use.
   * @return
   */
  public default Integer getMaxThreads() {
    return 1;
  }
}

最后是事件處理程序 class:

package com.fedeee.reporting.axon.events.pkg;

import com.fedeee.api.UserInfo;
import com.fedeee.pkg.api.events.*;
import com.fedeee.reporting._shared.utils.MdcAutoClosable;
import com.fedeee.reporting.axon.events.EventHandlerProjector;
import com.fedeee.reporting.recover.packageevents.PackageCreatedDto;
import com.fedeee.reporting.recover.packageevents.RecoverPackageCreatedRequest;
import com.fedeee.reporting.recover.packageevents.RecoverPackageEditedDto;
import com.fedeee.reporting.recover.packageevents.RecoverPackageEventsService;
import com.fedeee.reporting.recover.translators.PackageEventTranslator;
import com.fedeee.reporting.recover.translators.UserInfoTranslator;
import com.fedeee.reporting.recover.user.RecoverUserDto;
import org.axonframework.config.ProcessingGroup;
import org.axonframework.eventhandling.EventHandler;
import org.axonframework.eventhandling.Timestamp;
import org.axonframework.messaging.annotation.MetaDataValue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.time.Instant;

import static com.fedeee.api.AxonMessageMetadataKeys.USER_INFO;

/**
 * Event TrackingProcessor for PackageRecord-based Events handled by Axon.
 *
 * Configurations can be provided with the given batchSize and maxThreads options via .env or docker-compose.
 *
 * IMPORTANT! <code>AxonConfig</code> looks for the <em>@ProcessingGroup</em> annotation to set everything up properly.
 */
@ProcessingGroup(value = "Package-Record")
@Component
public class PackageRecordProjector implements EventHandlerProjector {

  @Value("${reporting-etl.tp-batch-size.package-record:1}")
  private Integer batchSize;

  @Value("${reporting-etl.tp-max-threads.package-record:5}")
  private Integer maxThreads;

  private RecoverPackageEventsService recoverPackageEventsService;

  @Autowired
  public PackageRecordProjector(RecoverPackageEventsService recoverPackageEventsService) {
    super();
    this.recoverPackageEventsService = recoverPackageEventsService;
  }

  private final Logger LOG = LoggerFactory.getLogger(this.getClass());

  /**
   * Event handler to handle packages created in Recover.
   *
   * This replaces the REST endpoint exposed and used by Recover in RecoverPackageEventsController.created().
   *
   * @param event
   * @param occurrenceInstant
   * @param userInfo
   */
  @EventHandler
  void on(PackageCreated event, @Timestamp Instant occurrenceInstant, @MetaDataValue(USER_INFO) UserInfo userInfo) {
    try (MdcAutoClosable mdc = new MdcAutoClosable()) {
      mdcInit(event, userInfo, mdc);

      LOG.info("Handling PackageCreated event...");

      PackageCreatedDto createdDto = PackageEventTranslator.from(event, occurrenceInstant, userInfo);
      RecoverUserDto recoverUserDto = UserInfoTranslator.from(userInfo);
      RecoverPackageCreatedRequest request = new RecoverPackageCreatedRequest(createdDto, recoverUserDto);

      /* Once we are ready, comment this in and make appropriate changes to RecoverPackageEventsController to
        disallow duplication via the REST endpoint. (There are comments in there already.  :)  )
       */
      recoverPackageEventsService.save(request);

      LOG.info("Finished handling PackageCreated event.");
    } catch (Exception e) {
        LOG.info("An Exception has been thrown : ", e);
        throw e;
    }
  }

  @EventHandler
  void on(PackageTypeCorrected event, @Timestamp Instant occurrenceInstant, @MetaDataValue(USER_INFO) UserInfo userInfo) {
    try (MdcAutoClosable mdc = new MdcAutoClosable()) {
      mdcInit(event, userInfo, mdc);

      // TODO: not implemented (Recover and Reporting-ETL)
      LOG.info("Finished handling PackageTypeCorrected event.");
    } catch (Exception e) {
      LOG.info("An Exception has been thrown : ", e);
      throw e;
    }
  }

  @EventHandler
  void on(PackageDeleted event, @Timestamp Instant occurrenceInstant, @MetaDataValue(USER_INFO) UserInfo userInfo) {
    try (MdcAutoClosable mdc = new MdcAutoClosable()) {
      mdcInit(event, userInfo, mdc);

      // TODO: not implemented (Reporting-ETL)
      LOG.info("Finished handling PackageDeleted event.");
    } catch (Exception e) {
      LOG.info("An Exception has been thrown : ", e);
      throw e;
    }
  }

// TODO: not implemented (Recover and Reporting-ETL)
//  @EventHandler
//  void on(PackageIntegrated event, @Timestamp Instant occurrenceInstant, @MetaDataValue(USER_INFO) UserInfo userInfo) {
//    try (MdcAutoClosable mdc = new MdcAutoClosable()) {
//      mdcInit(event, userInfo, mdc);
//    } catch (Exception e) {
//      LOG.info("An Exception has been thrown : ", e);
//      throw e;
//    }
//  }

  @EventHandler
  void on(DataCaptureStarted event, @Timestamp Instant occurrenceInstant, @MetaDataValue(USER_INFO) UserInfo userInfo) {
    try (MdcAutoClosable mdc = new MdcAutoClosable()) {
      mdcInit(event, userInfo, mdc);

      RecoverPackageEditedDto editedDto = PackageEventTranslator.from(event, occurrenceInstant, userInfo);

      /* Once we are ready, comment this in and make appropriate changes to RecoverPackageEventsController to
        disallow duplication via the REST endpoint. (There are comments in there already.  :)  )
       */
      recoverPackageEventsService.save(editedDto);

      LOG.info("Finished handling DataCaptureStarted event.");
     } catch (Exception e) {
      LOG.info("An Exception has been thrown : ", e);
      throw e;
    }
  }

  @EventHandler
  void on(DataCaptureEnded event, @Timestamp Instant occurrenceInstant, @MetaDataValue(USER_INFO) UserInfo userInfo) {
    try (MdcAutoClosable mdc = new MdcAutoClosable()) {
      mdcInit(event, userInfo, mdc);

      RecoverPackageEditedDto editedDto = PackageEventTranslator.from(event, occurrenceInstant, userInfo);

      /* Once we are ready, comment this in and make appropriate changes to RecoverPackageEventsController to
        disallow duplication via the REST endpoint. (There are comments in there already.  :)  )
       */
      recoverPackageEventsService.update(event.getPackageId(), editedDto);

      LOG.info("Finished handling DataCaptureEnded event.");
    } catch (Exception e) {
      LOG.info("An Exception has been thrown : ", e);
      throw e;
    }
  }

  @Override public Integer getBatchSize() {
    return batchSize;
  }

  @Override public Integer getMaxThreads() {
    return maxThreads;
  }

  private void mdcInit(PackageEvent event, UserInfo userInfo, MdcAutoClosable mdc) {
    mdc.put("PackageId", event.getPackageId());
    mdc.put("UserId", userInfo.getUserId());
    LOG.info("Handling package record event: {}", event);
  }
}

這是今天 2023/01/27 的日志...

.
.
.
2023-01-27 17:19:32.924 DEBUG 8 --- [MessageBroker-4] i.a.a.message.command.CommandCache       : Checking timed out commands
2023-01-27 17:19:37.924 DEBUG 8 --- [MessageBroker-4] i.a.axonserver.message.query.QueryCache  : Checking timed out queries
2023-01-27 17:19:37.924 DEBUG 8 --- [MessageBroker-3] i.a.a.message.command.CommandCache       : Checking timed out commands
2023-01-27 17:19:40.299 DEBUG 8 --- [ool-5-thread-16] i.a.axonserver.grpc.PlatformService      : Registered client : ClientComponent{client='reporting-etl-client', component='reporting-etl', context='default'}
2023-01-27 17:19:40.299  INFO 8 --- [ool-5-thread-16] i.a.a.logging.TopologyEventsLogger       : Application connected: reporting-etl, clientId = reporting-etl-client, context = default
2023-01-27 17:19:40.299 DEBUG 8 --- [ool-5-thread-16] i.a.a.c.version.ClientVersionsCache      : Version update received from client reporting-etl-client.default to version 4.4.
2023-01-27 17:19:40.332  INFO 8 --- [ool-5-thread-15] i.a.a.message.event.EventDispatcher      : Starting tracking event processor for : - 209301
2023-01-27 17:19:42.925 DEBUG 8 --- [MessageBroker-7] i.a.axonserver.message.query.QueryCache  : Checking timed out queries
2023-01-27 17:19:42.925 DEBUG 8 --- [MessageBroker-3] i.a.a.message.command.CommandCache       : Checking timed out commands
.
.
.
2023-01-27 18:56:08.163 DEBUG 8 --- [MessageBroker-1] i.a.a.message.command.CommandCache       : Checking timed out commands
2023-01-27 18:56:08.163 DEBUG 8 --- [MessageBroker-7] i.a.axonserver.message.query.QueryCache  : Checking timed out queries
2023-01-27 18:56:09.242 DEBUG 8 --- [pool-5-thread-9] i.a.a.message.command.CommandDispatcher  : Dispatch com.fedeee.pkg.api.commands.StartDataCapture to: recover-api-client.default
2023-01-27 18:56:09.257 DEBUG 8 --- [ool-5-thread-10] i.a.a.message.command.CommandDispatcher  : Sending response to: io.axoniq.axonserver.message.command.CommandInformation@17b317ff
2023-01-27 18:56:09.294 DEBUG 8 --- [st-dispatcher-2] i.a.a.grpc.GrpcQueryDispatcherListener   : Send request io.axoniq.axonserver.grpc.SerializedQuery@158b1b9a, with priority: 0
2023-01-27 18:56:09.294 DEBUG 8 --- [st-dispatcher-2] i.a.a.grpc.GrpcQueryDispatcherListener   : Remaining time for message: 72152808-ca89-4565-82dd-2675e52686e2 - 3600000ms
2023-01-27 18:56:09.300 DEBUG 8 --- [pool-5-thread-5] i.a.axonserver.message.query.QueryCache  : Remove messageId 72152808-ca89-4565-82dd-2675e52686e2
2023-01-27 18:56:09.300 DEBUG 8 --- [pool-5-thread-5] i.a.a.message.query.QueryDispatcher      : No (more) information for 72152808-ca89-4565-82dd-2675e52686e2 on completed
2023-01-27 18:56:09.300 DEBUG 8 --- [st-dispatcher-2] i.a.a.grpc.GrpcQueryDispatcherListener   : Send request io.axoniq.axonserver.grpc.SerializedQuery@6e93a34b, with priority: 0
2023-01-27 18:56:09.301 DEBUG 8 --- [st-dispatcher-2] i.a.a.grpc.GrpcQueryDispatcherListener   : Remaining time for message: 53aa1974-4012-452a-b451-2957003e4b9f - 3599999ms
2023-01-27 18:56:09.306 DEBUG 8 --- [pool-5-thread-4] i.a.axonserver.message.query.QueryCache  : Remove messageId 53aa1974-4012-452a-b451-2957003e4b9f
2023-01-27 18:56:09.306 DEBUG 8 --- [pool-5-thread-4] i.a.a.message.query.QueryDispatcher      : No (more) information for 53aa1974-4012-452a-b451-2957003e4b9f on completed
2023-01-27 18:56:09.319 DEBUG 8 --- [ool-5-thread-13] i.a.a.m.q.s.handler.DirectUpdateHandler  : SubscriptionQueryResponse for subscription Id 55748e24-e26f-4863-a5d0-a4eeff43da69 send to client.
2023-01-27 18:56:10.509 DEBUG 8 --- [st-dispatcher-2] i.a.a.grpc.GrpcQueryDispatcherListener   : Send request io.axoniq.axonserver.grpc.SerializedQuery@fd8d154, with priority: 0
2023-01-27 18:56:10.510 DEBUG 8 --- [st-dispatcher-2] i.a.a.grpc.GrpcQueryDispatcherListener   : Remaining time for message: d2d91224-557f-4735-933b-e4195e7e42f9 - 3599999ms
2023-01-27 18:56:10.514 DEBUG 8 --- [ool-5-thread-15] i.a.axonserver.message.query.QueryCache  : Remove messageId d2d91224-557f-4735-933b-e4195e7e42f9
2023-01-27 18:56:10.514 DEBUG 8 --- [ool-5-thread-15] i.a.a.message.query.QueryDispatcher      : No (more) information for d2d91224-557f-4735-933b-e4195e7e42f9 on completed
2023-01-27 18:56:13.163 DEBUG 8 --- [MessageBroker-2] i.a.a.message.command.CommandCache       : Checking timed out commands
2023-01-27 18:56:13.163 DEBUG 8 --- [MessageBroker-5] i.a.axonserver.message.query.QueryCache  : Checking timed out queries
.
.
.

由於您可能會遇到已知錯誤,因此最好使用 4.6 的服務器和框架,也就是說,它應該可以工作,因為服務器 API 非常穩定。

您還可以分享com.fedeee.reporting.axon.events.EventHandlerProjector class 嗎? 有 class @EventHandler注釋的方法嗎? 是否有更多帶有@EventHandler注釋方法的類?

我假設您使用的是 Spring Boot 和 Axon Framework starter,確實如此嗎?

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM