25.Axon框架-事件(三)

37 阅读8分钟

EventStroe

1.EventBus与EventStore的关系

asdasdjioasdjoa.png

asdsadaccccccccccccawio.png

2.什么是EventStore

EventSourcingRepository,需要通过EventStore来存储聚合产生的事件,并从其中加载事件。EventStore继承了EventBus接口,也就是说具备EventBus的路由功能,除此之外还具备:

  1. 持久化已发布的事件
  2. 支持根据指定的聚合ID,查询该聚合的历史事件

3.所有EventStore总览

1768104394250.png

4.Axon启动器

依赖

<!-- 在 POM 文件中添加依赖 -->
<dependency>
    <groupId>org.axonframework</groupId>
    <artifactId>axon-spring-boot-starter</artifactId>
    <version>${axon.version}</version>
</dependency>

EventStroe自动配置规则

  1. 只需在依赖中声明axon-spring-boot-starter,Axon就会自动配置事件存储
  2. 若从axon-spring-boot-starter中排除axon-server-connector依赖,Axon会根据以下规则自动配置EventStore

asdasdccccx.png

5.EmbeddedEventStore

介绍

作为非Axon Server选项,Axon提供了EmbeddedEventStore。它本身不直接处理事件的存储和检索,而是将这一职责委托给EventStorageEngine

Axon提供了多种EventStorageEngine实现,适用于不同的存储场景

JpaEventStorageEngine

介绍

JpaEventStorageEngine将事件存储在兼容JPA的数据源中,事件以entry形式存储,每个entry包含事件的序列化数据,以及用于快速查询的元数据字段

要使用JpaEventStorageEngine,类路径中必须包含JPA注解(jakarta.persistence);同时也兼容旧版的javax.persistence注解(需通过legacyjpa命名空间使用旧版JpaEventStorageEngine)

核心配置

默认情况下,事件存储需要在持久化上下文(通常定义在META-INF/persistence.xml文件中)中注册DomainEventEntry和SnapshotEventEntry两个类(均位于org.axonframework.eventsourcing.eventstore.jpa包下),示例配置如下:

<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">
    <persistence-unit name="eventStore" transaction-type="RESOURCE_LOCAL">
        <class>org.axonframework.eventsourcing.eventstore.jpa.DomainEventEntry</class>
        <class>org.axonframework.eventsourcing.eventstore.jpa.SnapshotEventEntry</class>
    </persistence-unit>
</persistence>
  1. 示例中为EventStroe单独配置了持久化单元,你也可以将上述两个类添加到其他已有的持久化单元配置中
  2. 这一步的核心是将JpaEventStorageEngine依赖的DomainEventEntry类注册到持久化上下文,确保JPA能识别并映射该实体
唯一约束

Axon通过锁机制防止两个线程同时访问同一个聚合,但在多JVM共享同一数据库的场景下,该机制无法生效,此时需依赖数据库自身的冲突检测能力

当多个进程并发访问EventStore时,会触发键约束冲突,因为事件表要求同一聚合+同一序列号只能对应一个事件,若为已存在的聚合+序列号插入第二个事件,会直接报错

JpaEventStorageEngine能检测此类错误并将其转换为ConcurrencyException,但不同数据库的错误码格式不同:

  1. 若向JpaEventStorageEngine注册了DataSource,它会自动检测数据库类型,并识别对应的键约束冲突错误码
  2. 也可手动提供PersistenceExceptionResolver实例,自定义判断异常是否为键约束冲突
  3. 若未提供DataSource或PersistenceExceptionTranslator,数据库驱动抛出的异常会直接向上传递,不做转换
EntityManagerProvider

默认情况下,JpaEventStorageEngine需要EntityManagerProvider实现来提供其所需的EntityManager实例(支持应用托管的持久化上下文)。EntityManagerProvider的核心职责是确保返回正确的EntityManager实例,Axon提供了多种实现以适配不同场景:

  1. SimpleEntityManagerProvider:在构造时接收EntityManager实例并直接返回,适用于容器托管的上下文
  2. ContainerManagedEntityManagerProvider:返回默认的持久化上下文,是JPA操作EventStore的默认实现
从javax.persistence迁移到jakarta.persistence

自Axon4.6.0版本起,默认使用jakarta命名空间。为适配这一变更,JpaTokenStore等类提供了新旧两个版本(分别对应javax和jakarta)

若需为JpaEventStorageEngine指定名为myPersistenceUnit的持久化单元,可自定义EntityManagerProvider实现:

public class MyEntityManagerProvider implements EntityManagerProvider {

    private EntityManager entityManager;

    @Override
    public EntityManager getEntityManager() {
        return entityManager;
    }

    @PersistenceContext(unitName = "myPersistenceUnit")
    public void setEntityManager(EntityManager entityManager) {
        this.entityManager = entityManager;
    }
}
扩展与优化

DomainEventEntry和SnapshotEventEntry能满足大多数场景,但如果需要自定义元数据或为不同聚合类型分配不同表,可通过继承JpaEventStorageEngine并覆盖其来调整行为

此外,Hibernate等持久化provider会在EntityManager中使用一级缓存,查询中使用或返回的实体都会附加到EntityManager,仅在事务提交或显式调用clear()时才会清除(尤其在事务上下文内执行查询时)。若加载大量事件流,可能导致OutOfMemoryException,解决方案如下:

  1. 仅查询非实体对象(如使用JPA的SELECT new SomeClass(parameters) FROM … 语法)
  2. 拉取一批事件后,调用EntityManager.flush()和EntityManager.clear()清理缓存
配置

原生API:

public class AxonConfig {
    // 省略其他配置方法...
    public Configurer jpaEventStorageConfigurer(
            EntityManagerProvider entityManagerProvider,
            TransactionManager transactionManager
    ) {
        return DefaultConfigurer.jpaConfiguration(entityManagerProvider, transactionManager);
    }
}

SpringBoot:

@Configuration
public class AxonConfig {
    // 省略其他配置方法...

    // EmbeddedEventStore 将事件的存储/检索委托给 EventStorageEngine
    @Bean
    public EventStore eventStore(
        EventStorageEngine storageEngine,
        GlobalMetricRegistry metricRegistry
    ) {
        return EmbeddedEventStore.builder()
                                 .storageEngine(storageEngine)
                                 .messageMonitor(metricRegistry.registerEventBus("eventStore"))
                                 .spanFactory(spanFactory)
                                 // ...(其他配置)
                                 .build();
    }

    // JpaEventStorageEngine 将事件存储在兼容 JPA 的数据源中
    @Bean
    public EventStorageEngine eventStorageEngine(
            Serializer serializer,
            PersistenceExceptionResolver persistenceExceptionResolver,
            @Qualifier("eventSerializer") Serializer eventSerializer,
            EntityManagerProvider entityManagerProvider,
            TransactionManager transactionManager
    ) {
         return JpaEventStorageEngine
             .builder()
             .snapshotSerializer(serializer)
             .persistenceExceptionResolver(persistenceExceptionResolver)
             .eventSerializer(eventSerializer)
             .entityManagerProvider(entityManagerProvider)
             .transactionManager(transactionManager)
             // ...(其他配置)
             .build();
    }
}

JdbcEventStorageEngine

介绍

JdbcEventStorageEngine使用JDBC Connection将事件存储在兼容JDBC的数据源中(通常是关系型数据库),理论上所有提供JDBC驱动的存储都可作为其底层实现

与JpaEventStorageEngine类似,JdbcEventStorageEngine也以entry形式存储事件(默认每个事件对应表中的一行),并使用两个独立的表分别存储事件和快照

核心依赖

JdbcEventStorageEngine通过ConnectionProvider获取数据库连接,通常直接从DataSource中获取。Axon会将连接与工作单元绑定,确保同一工作单元内使用同一个连接,即使同一线程中嵌套多个工作单元,也能保证所有EventStore操作在同一个事务中完成

配置

原生API:

public class AxonConfig {
    // 省略其他配置方法...
    public void configureJdbcEventStorage(
            Configurer configurer,
            ConnectionProvider connectionProvider,
            EventTableFactory eventTableFactory
    ) {
        configurer.configureEmbeddedEventStore(config -> {
            JdbcEventStorageEngine storageEngine = JdbcEventStorageEngine
                .builder()
                .snapshotSerializer(config.serializer())
                .connectionProvider(connectionProvider)
                .transactionManager(config.getComponent(TransactionManager.class))
                .eventSerializer(config.eventSerializer())
                // ...(其他配置)
                .build();
            // 若尚未创建数据库表结构,可调用 createSchema 方法
            storageEngine.createSchema(eventTableFactory);
            return storageEngine;
        });
    }
}

SpringBoot:

若类路径中存在JDBC,Axon的JdbcAutoConfiguration会自动生成JdbcEventStorageEngine。如需创建表结构,可参考以下配置:


@Configuration
public class AxonConfig {
    // 省略其他配置方法...

    // EmbeddedEventStore 将事件的存储/检索委托给 EventStorageEngine
    @Bean
    public EventStore eventStore(
            EventStorageEngine storageEngine,
            GlobalMetricRegistry metricRegistry
    ) {
        return EmbeddedEventStore
            .builder()
            .storageEngine(storageEngine)
            .messageMonitor(metricRegistry.registerEventBus("eventStore"))
            .spanFactory(spanFactory)
            // ...(其他配置)
            .build();
    }

    // JdbcEventStorageEngine 将事件存储在兼容 JDBC 的数据源中
    @Bean
    public EventStorageEngine storageEngine(
            Serializer serializer,
            ConnectionProvider connectionProvider,
            @Qualifier("eventSerializer") Serializer eventSerializer,
            TransactionManager transactionManager,
            EventTableFactory tableFactory
    ) {
        JdbcEventStorageEngine storageEngine = JdbcEventStorageEngine
            .builder()
            .snapshotSerializer(serializer)
            .connectionProvider(connectionProvider)
            .eventSerializer(eventSerializer)
            .transactionManager(transactionManager)
            // ...(其他配置)
            .build();
        // 若尚未创建数据库表结构,可调用 createSchema 方法
        storageEngine.createSchema(tableFactory);
        return storageEngine;
    }
}
Spring环境下的数据源推荐

对于Spring用户,建议使用SpringDataSourceConnectionProvider,它能将DataSource中的连接绑定到已有的事务中,确保事务一致性

SQL语句自定义

不同数据库的最优SQL语句存在差异,Axon无法覆盖所有场景,因此允许自定义JdbcEventStorageEngine使用的SQL语句:

  1. 可通过JdbcEventStorageEngineStatements工具类查看默认语句
  2. 所有可调整的语句均位于org.axonframework.eventsourcing.eventstore.jdbc.statements包下
  3. 可通过JdbcEventStorageEngine.Builder自定义这些语句的构建逻辑

MongoEventStorageEngine

介绍

MongoDB是基于文档的NoSQL数据库,其可扩展性使其适合作为EventStore。Axon提供MongoEventStorageEngine(位于axon-mongo模块),将MongoDB作为底层存储

有两个独立的集合:一个用于事件流,一个用于快照

存储策略

默认情况下,MongoEventStorageEngine为每个事件创建一个独立文档;也可通过调整StorageStrategy改变存储方式,Axon提供的DocumentPerCommitStorageStrategy会将同一提交中的所有事件(即同一DomainEventStream中的事件)存储为单个文档

两种策略的对比:

  1. 单文档存储:提交操作原子性强,无论事件数量多少都只需一次数据库往返;但缺点是难以直接查询单个事件(如领域模型重构时,难以将事件从一个聚合迁移到另一个)
  2. 多文档存储(默认):查询灵活,但需多次数据库往返
配置

原生API:

public class AxonConfig {
    // 省略其他配置方法...
    public void configureMongoEventStorage(Configurer configurer, MongoTemplate mongoTemplate) {
        configurer.configureEmbeddedEventStore(config -> MongoEventStorageEngine
            .builder()
            .mongoTemplate(mongoTemplate)
            // ...(其他配置)
            .build()
        );
    }
}

SpringBoot:

@Configuration
public class AxonConfig {
    // 省略其他配置方法...

    // EmbeddedEventStore 将事件的存储/检索委托给 EventStorageEngine
    @Bean
    public EventStore eventStore(EventStorageEngine storageEngine,
                               GlobalMetricRegistry metricRegistry) {
       return EmbeddedEventStore
           .builder()
           .storageEngine(storageEngine)
           .messageMonitor(metricRegistry.registerEventBus("eventStore"))
           .spanFactory(spanFactory)
           // ...(其他配置)
           .build();
    }

    // MongoEventStorageEngine 将每个事件存储为 MongoDB 中的独立文档
    @Bean
    public EventStorageEngine storageEngine(MongoClient client) {
        return MongoEventStorageEngine
            .builder()
            .mongoTemplate(DefaultMongoTemplate.builder()
                                             .mongoDatabase(client)
                                             .build())
            // ...(其他配置)
            .build();
    }
}

6.EventStore工具类

  • InMemoryEventStorageEngine:基于内存的EventStore,往往用于短期工具或测试场景
  • SequenceEventStorageEngine:是一个包装类,可同时关联两个EventStorageEngine,读取用两个,追加用第二个
  • FilteringEventStorageEngine:允许根据谓词过滤事件,仅符合条件的事件会被存储

7.事件序列化

介绍

EventStore需要通过序列化将事件转换为可存储的格式。Axon默认使用XStreamSerializer(基于XStream框架),将事件序列化为XML格式

推荐

Axon还提供JacksonSerializer(基于Jackson框架),将事件序列化为JSON格式,序列化结果更紧凑,但要求事件类符合Jackson的序列化约定(或通过配置适配)

自定义

只需实现Serializer接口,并将EventStore配置为使用该实现

配置

原生API
// 返回已配置默认组件的 Configurer 实例
// 显式将 JacksonSerializer 设为事件序列化器
Configurer configurer = DefaultConfigurer.defaultConfiguration()
      .configureEventSerializer(c -> JacksonSerializer.builder().build());
SpringBoot Properties
# 在 application.properties 中配置
axon.serializer.events=jackson
# 可选值:java(原生序列化)、xstream、jackson
SpringBoot自定义Bean
// 在 @Configuration 类中定义
@Qualifier("eventSerializer")
@Bean
public Serializer eventSerializer() {
    return JacksonSerializer.builder().build();
}

与其他对象序列化的区别

Axon允许为EventStore和其他对象(如命令、快照、Saga等) 配置不同的序列化器:

  • XStreamSerializer能序列化几乎所有对象,适合作为默认序列化器,但输出格式不便于跨应用共享
  • JacksonSerializer输出的JSON格式更通用,且事件类通常符合其序列化约定,因此更适合作为事件序列化器

若未显式配置eventSerializer,事件会使用全局默认序列化器(默认是XStreamSerializer)进行序列化