Axon Framework官方文档(十一)

来源:互联网 发布:淘宝导航条制作 编辑:程序博客网 时间:2024/06/18 08:55

11. Repositories and Event Stores

仓储是提供对聚合的访问的机制。仓储充当用于保存数据的实际存储机制的网关。在CQRS中,存储库只需要能够根据其惟一标识符找到聚合。而所有的复杂查询,需要放到查询模型上做。在Axon框架中,所有仓储必须实现Repository接口。这个接口规定了三个方法:load(identifier, version)、load(identifier)和newInstance(factoryMethod)。load 方法允许从仓储加载聚合。可选的version参数用于检测并发修改(参见高级冲突检测和解决)。newInstance用于在存储库中注册新创建的聚合。根据您的基础持久性存储和审计需求,有一些基础实现提供大了部分存储库所需的基本功能。Axon Framework对保存聚合当前状态的存储库(见Standard Repositories)和那些存储聚合事件的存储库(见 Event Sourcing Repositories)进行了区分。注意,Repository接口并没有规定delete(identifier)方法。删除聚合是通过在一个聚合内部调用AggregateLifecycle.markDeleted()方法完成的。删除一个聚合是一个像其他的状态迁移一样的状态变化,唯一的区别是它在许多情况下是不可逆的。你应该在聚合上创建自己的有意义的方法,来将聚合的状态设置为“已删除”。这也允许你注册你想要发布的任何事件。

11.1 Standard repositories(标准仓储)

标准仓储存储聚合的实际状态。每一次改变,新状态都会改写旧的状态。这使得应用程序的查询组件可以使用命令组件也使用的相同信息。这可能取决于您正在创建的应用程序的类型,是最简单的解决方案。如果确实是这样,那么Axon提供了一些构建块,帮助您实现这样的存储库。Axon为标准仓储提供了一个开箱即用的实现:GenericJpaRepository。它期望聚合成为一个有效的JPA实体。它配置了一个EntityManagerProvider,EntityManagerProvider提供EntityManager来管理实际的持久化,和一个指定了在实际的存储中保存的聚合的类型的类。当聚合调用静态AggregateLifecycle.apply()方法时,你也可以通过EventBus去发布事件。您还可以轻松地实现自己的仓储。在这种情况下,最好从抽象的LockingRepository来继承。作为聚合包装类型,建议使用AnnotatedAggregate。例如,请参阅GenericJpaRepository 的源代码。

11.2 Event Sourcing repositories(事件源仓储)

聚合根能够根据事件重建它们的状态,也可以配置为通过事件源存储库加载。这些存储库不存储聚合本身,但存储聚合生成的一系列事件。基于这些事件,可以随时恢复聚合的状态。EventSourcingRepository实现提供了AxonFramework中任何事件源仓储所需的基本功能。它依赖于EventStore(参见implementing-your-own-eventstore),它抽象了事件的实际存储机制。您还可以提供一个聚合工厂。AggregateFactory指定如何创建聚合实例。一旦创建了聚合,EventSourcingRepository就可以使用从事件存储中加载的事件来初始化它。Axon框架附带了一些您可以使用的AggregateFactory 实现。如果它们还不够,你可以自己实现。>GenericAggregateFactory:GenericAggregateFactory是一个特殊的AggregateFactory实现,可用于任何类型的事件源聚合根。GenericAggregateFactory可以创建仓储所管理的任何聚合类型的实例。聚合类必须是非抽象的,并声明一个没有初始化的默认的无参数构造函数。GenericAggregateFactory适合于大多数场景,在这些场景中,聚合体不需要特殊的非序列化资源注入。>SpringPrototypeAggregateFactory根据您的体系结构选择,使用Spring将依赖注入到聚合中可能是有用的。例如,你可以将查询库注入到你的聚合,以确保某些值的存在(或不存在)。注入依赖项到你的聚合,在定义了SpringPrototypeAggregateFactory的Spring上下文中,你需要配置一个聚合根的原型bean。不是使用构造函数创建的常规的实例,而是使用Spring应用程序上下文实例化你聚合。这也可以在你的聚合中注入任何依赖项。>实现你自己的AggregateFactory在某些情况下,GenericAggregateFactory并不提供您所需要的内容。例如,您可以有一个具有多个实现的抽象聚合类型(例如,PublicUserAccount和BackOfficeAccount都扩展了一个Account)。您可以使用单个存储库,而不是为每个聚合体创建不同的存储库,并配置一个能够感知不同实现的AggregateFactory。聚合工厂所做的大部分工作都是创建未初始化的聚合实例。它必须使用给定的聚合标识符和来自流的第一个事件。通常,这个事件是一个创建事件,它包含关于预期的聚合类型的提示。可以使用此信息选择实现并调用其构造函数。确保该构造函数没有应用任何事件;且聚合必须未初始化。相对于简单的存储库直接加载聚合的实现,基于事件初始化聚合可能是一项耗时的工作。CachingEventSourcingRepository提供一个可以从中加载聚合的缓存。

11.3 Event store implementations(实现事件存储)

事件源仓储需要一个事件存储来存储和加载来自聚集的事件。事件存储提供了事件总线的功能,它还保留已发布的事件,并能够根据聚合标识符检索事件。Axon提供了一个开箱机用的事件存储:EmbeddedEventStore。它将事件的实际存储和检索都委托给EventStorageEngine。

11.3.1 JpaEventStorageEngine

JpaEventStorageEngine在一个与jpa兼容的数据源中存储事件。JPA事件存储存将事件储在所谓的条目中。这些条目包含事件的序列化形式,以及存储元数据以便快速查找这些条目的一些字段。要使用JpaEventStorageEngine,您必须在类路径上有JPA(javax.persistence)注解。默认情况下,事件存储需要您配置持久化上下文(例如,在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"> (1)        <class>org...eventstore.jpa.DomainEventEntry</class> (2)        <class>org...eventstore.jpa.SnapshotEventEntry</class>    </persistence-unit></persistence>
(1).在这个示例中,事件存储有一个特定的持久化单元。但是,您可以选择将第三行添加到任何其他持久化单元配置中。(2).本行注册DomainEventEntry(由JpaEventStore使用的类)到持久化上下文。Note:Axon使用锁定来防止两个线程访问相同的聚合。但是,如果在同一个数据库上有多个jvm,这将对您没有帮助。在这种情况下,您必须依赖数据库来检测冲突。并发访问事件存储将导致违反主键约束(Key Constraint Violation),因为表允许聚合只能有一个任何序列号的事件,所以,用已有的序列号为现有聚合插入第二个事件将导致错误。JpaEventStorageEngine可以检测这个错误并把它转换成ConcurrencyException。然而,每个数据库系统以不同的方式报告此违规行为。如果你用JpaEventStore注册你的数据源,它将尝试检测数据库的类型,并找出错误代码是一个违反主键约束(Key Constraint Violation)。或者,你可能会提供一个PersistenceExceptionTranslator实例,如果一个给定的异常代表一个违反主键约束(Key Constraint Violation)它能分辨。如果没有提供数据源或PersistenceExceptionTranslator,从数据库驱动程序按原样抛出异常。默认情况下,JPA事件存储引擎需要一个EntityManagerProvider实现,该实现返回EventStorageEngine使用的EntityManager实例。这还允许应用程序管理所用的持久化上下文。EntityManagerProvider的责任是提供正确的EntityManager实例。这里有一些可用的EntityManagerProvider的实现,每个都满足不同的需求。SimpleEntityManagerProvider仅在构建时返回EntityManager实例给它。这使得实现对于容器管理的上下文来说是一个简单的选项。另外,还有ContainerManagedEntityManagerProvider,返回默认的持久化上下文,并且它的使用默认通过JPA事件存储。如果您有一个名为“myPersistenceUnit”的持久化单元,您希望在JpaEventStore中使用它,则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;    }
默认情况下,JPA事件存储在DomainEventEntry和SnapshotEventEntry实体中存储条目。虽然这在很多情况下是足够的,但您可能会遇到这样一种情况:这些实体提供的元数据是不够的。或者,您可能希望在不同的表中存储不同聚合类型的事件。如果是这样,您可以扩展JpaEventStorageEngine。它包含一些protected的方法,您可以重写以调整其行为。Warning:注意,持久性提供者(如Hibernate)在EntityManager实现上使用一级缓存。通常,这意味着在查询中使用或返回的所有实体都与EntityManager相连。它们只在事务被提交时被清除,或者在事务中执行明确的“清除”。尤其是当查询在事务上下文中执行时。。要解决这个问题,请确保只对非实体对象进行查询。您可以使用JPA的"SELECT new SomeClass(parameters) FROM..."风格查询来解决这个问题。或者,获取一批事件后调用entitymanager.flush()和entitymanager.clear()。如果未能这样做当加截大事件流时可能导致OutOfMemoryExceptions。

11.3.2 JDBC Event Storage Engine

JDBC事件存储引擎使用JDBC连接来将事件存储在JDBC兼容的数据存储中。通常,这些都是关系数据库。理论上,任何一个JDBC驱动程序都可以用来支持JDBC事件存储引擎。与JPA对等物类似,JDBC事件存储引擎将时间存储在条目中。默认情况下,每个事件都存储在一个条目中,该条目对应于表中的一行。一个表用于事件,另一个表用于快照。JdbcEventStorageEngine使用ConnectionProvider来获取连接。通常,这些连接可以直接从数据源获得。然而,Axon将把这些连接绑定到一个工作单元,以便在工作单元中使用单个连接。这确保使用单个事务来存储所有事件,即使在同一个线程中嵌套多个工作单元时也是如此。Note:Spring用户建议使用SpringDataSourceConnectionProvider连接从一个数据源的连接到现有的事务。

11.3.3 MongoDB Event Storage Engine(MongoDB 事件存储引用)

MongoDB是一个基于文档的NoSQL存储。它的可伸缩性特性使它适合用作事件存储。Axon提供了MongoDB作为后台数据库的MongoDB引擎。它包含在Axon Mongo模块中(Maven artifactId Axon - Mongo)。事件存储在两个单独的集合中:一个用于实际的事件流,一个用于快照。默认情况下,MongoEventStorageEngine将每个事件存储在一个单独的文档中。然而,它是可能改变StorageStrategy使用。Axon提供的选择是DocumentPerCommitStorageStrategy,为所有已存储在单个commit中的事件创建一个文档(即在同一DomainEventStream)。将整个提交存储在单个文档中有一个优点,即commit是原子存储的。此外,它只需要对任意数量的事件进行一次往返。缺点是在数据库中直接查询事件变得更加困难。例如,当重构领域模型时,如果他们被包含在“commit document”中,很难从一个聚合“transfer”事件到另一个聚合。MongoDB不需要很多配置。它所需要的只是对一个存储事件集合的引用,然后您就可以开始了。对于生产环境,您可能需要对集合上的索引进行双重检查。

11.3.4 Event Store Utilities(事件存储工具)

Axon提供了一些在某些情况下可能有用的事件存储引擎。SequenceEventStorageEngine是另外两个事件存储引擎的包装器。当读取时,它从两个事件存储引擎返回事件。附加事件只添加到第二个事件存储引擎。这在两个不同的事件存储实现用于性能的情况下是很有用的,例如。第一个将是一个更大的,但更慢的事件存储,而第二个则是为快速阅读和写入而优化的事件存储。还有一个常驻内存的存储事件EventStorageEngine实现:InMemoryEventStorageEngine。虽然它可能优于任何其他的事件存储,这并不意味着长期生产使用。然而,它在需要事件存储的short-lived工具或测试中非常有用。

11.3.5 Influencing the serialization process

事件存储需要一种方法来序列化事件。默认情况下,Axon使用XStreamSerializer,它使用XStream将事件序列化为XML。XStream的速度相当快,而且比Java序列化更加灵活。此外,XStream序列化的结果是人类可读的。对于日志记录和调试功能非常有用。XStreamSerializer可以配置。你可以定义它应该用于某些包、类甚至字段的别名。除了可以缩短潜在的长名称之外,还可以在事件的类定义更改时使用别名。有关别名的更多信息,访问XStream网站。另外,Axon还提供了JacksonSerializer,使用Jackson将事件序列化为JSON。当它生成一个更紧凑的序列化形式,它要求类遵守Jackson所要求的约定(或配置)。Note:使用Java代码(或其他JVM语言)配置序列化器很容易。但是,在Spring XML应用程序上下文中配置它并不是那么简单,因为它有调用方法的局限性。其中一个选项是创建一个FactoryBean,它创建一个XStreamSerializer的实例,并在代码中配置它。查看Spring参考以获取更多信息。您还可以实现自己的序列化器,只需创建一个实现序列化器的类,并配置事件存储来使用该实现而不是默认值。

11.4 Event Upcasting(事件向上转型)

由于软件应用程序的不断变化的性质,很可能事件定义也随着时间而变化。由于事件存储被认为是只读和只追加(没有修改和删除)的数据源,所以应用程序必须能够读取所有事件,而不管它们何时添加。这时upcasting 出现了。最初是面向对象编程的概念,即“当需要时,子类自动转换到它的超类”,upcasting的概念也可以应用于事件源。upcast一个事件的意思是把它从原来的结构转变成新的结构。与OOP的upcasting不同,事件升级不能完全自动化,因为新事件的结构不知道旧事件。必须提供手工编写的Upcasters,以指定如何将旧结构向上转换为新的结构。Upcasters是一个类,它接受一个version为x的输入事件,并且输出为零或更多版本x+1的新事件。此外,upcasters在一个链中被处理,这意味着一个upcaster的输出发送到下一个upcaster的输入。这允许你以增量的方式更新事件,为每一个新事件版次编写一个Upcaster ,使其小、隔离、并且容易理解。Note:也许upcasting最大的好处是它允许您进行非破坏性的重构,即完整的事件历史仍然完整。在本节中,我们将解释如何编写一个upcaster,描述随着Axon不同的的Upcaster实现,并解释事件的序列化形式如何影响写upcasters。为了允许upcaster查看他们正在接收的序列化对象的版本,事件存储将存储一个version以及事件的完全限定名称。这个version是由在序列化器中配置的RevisionResolver生成的。Axon提供了几个RevisionResolver的实现,比如AnnotationRevisionResolver,它检查在事件有效负载上的@Revision注解,SerialVersionUIDRevisionResolver 使用Java Serialization API和FixedValueRevisionResolver所定义的serialVersionUID,它总是返回一个预定义的值。后者在注入当前应用程序版本时是有用的。这将允许你看哪个版本的应用程序生成一个特定的事件。Maven用户可以使用MavenArtifactRevisionResolver自动使用项目的版本。它使用项目的groupId和artifactId初始化,以获得版本。由于这只能在Maven创建的JAR文件中工作,所以版本不能总是由IDE解决。如果一个版本不能被解析,则返回null。

11.4.1 Writing an upcaster

旧版本的事件:
@Revision("1.0")public class ComplaintEvent {    private String id;    private String companyName;    // Constructor, getter, setter...}
新版本的事件:
@Revision("2.0")public class ComplaintEvent {    private String id;    private String companyName;    private String complain; // New field    // Constructor, getter, setter...}
Upcaster: 
// Upcaster from 1.0 revision to 2.0 revisionpublic class ComplaintEventUpcaster extends SingleEventUpcaster {    private static SimpleSerializedType targetType = new SimpleSerializedType(ComplainEvent.class.getTypeName(), "1.0");    @Override    protected boolean canUpcast(IntermediateEventRepresentation intermediateRepresentation) {        return intermediateRepresentation.getType().equals(targetType);    }    @Override    protected IntermediateEventRepresentation doUpcast(IntermediateEventRepresentation intermediateRepresentation) {        return intermediateRepresentation.upcastPayload(                new SimpleSerializedType(targetType.getName(), "2.0"),                org.dom4j.Document.class,                document -> {                    document.getRootElement().addElement("complaint");                    document.getRootElement().element("complaint").setText("no complaint description"); // Default value                    return document;                }        );    }}
spring boot配置:
@Configurationpublic class AxonConfiguration {    @Bean    public SingleEventUpcaster myUpcaster() {        return new ComplaintEventUpcaster();    }    @Bean    public JpaEventStorageEngine eventStorageEngine(Serializer serializer,                                                    DataSource dataSource,                                                    SingleEventUpcaster myUpcaster,                                                    EntityManagerProvider entityManagerProvider,                                                    TransactionManager transactionManager) throws SQLException {        return new JpaEventStorageEngine(serializer,                myUpcaster::upcast,                dataSource,                entityManagerProvider,                transactionManager);    }}
TODO - Describe>Upcasters致力于中间表示>它们更新流到流>抽象一对一的upcasting实现>代码示例

11.4.2 Content type conversion

一个upcaster工作在给定内容类型上(如dom4j文档)。upcasters之间提供额外的灵活性,内容类型在链接的upcasters之间可能会有所不同。Axon将尝试使用ContentTypeConverters在内容类型之间自动地转换。它将寻找从类型x到类型y最短的路径,执行转换并交值转换成请求的upcaster。考虑到性能因素 ,如果receiving upcaster上的canUpcast方法产生true,转换才会被执行。ContentTypeConverters可能依赖于使用的序列化器类型。试图把一个byte[]转换成dom4j文档,这没有任何意义,除非使用序列化器把事件作为XML来写。确保UpcasterChain有权访问serializer-specific ContentTypeConverters,你可以通过UpcasterChain的构造函数引用序列化器。Note:为了达到最佳性能,确保所有upcasters在同一链上(其中一个的输出是另一个的输入)处理相同的内容类型。如果您所需要的内容类型转换不是由Axon提供的,您可以使用ContentTypeConverter接口编写自己的内容。XStreamSerializer支持Dom4J以及XOM作为XML文档表示。JacksonSerializer支持Jackson的JsonNode。

11.6 Snapshotting(快照)

当聚合体存活很长时间,并且它们的状态不断变化时,它们将产生大量的事件。必须加载所有这些事件来重建一个聚集的状态可能会有很大的性能影响。快照事件是具有特殊用途的领域事件:它将任意数量的事件总结为单个事件。通过定期创建和存储快照事件,事件存储并不需要返回事件的长列表。仅仅返回最后一个快照事件以及在快照之后发生的所有事件。例如,库存中的物品往往会经常发生变化。每当一个项目被出售时,一个事件就会减少一个项目的库存。每当有一批新产品出现时,库存就会增加一些。如果你每天销售100件商品,你每天至少会产生100个事件。几天后,你的系统将花费大量的时间阅读这些事件,只是为了弄清楚它是否应该raise一个“ItemOutOfStockEvent”。单个快照事件仅仅通过存储当前的库存数量就可以取代很多这些事件。

11.6.1 Creating a snapshot (创建一个快照)

快照创建可以由许多因素触发,例如自上一个快照以来创建的事件的数量,初始化聚合的时间超过了某个阈值,基于时间的等等。目前,Axon提供了一种机制,允许您根据事件计数阈值触发快照。当要创建快照时的定义,由SnapshotTriggerDefinition接口提供。当加载聚合所需的事件数量超过一定的阈值时,EventCountSnapshotTriggerDefinition提供触发快照创建的机制。如果加载一个聚合需要的事件的数量超过某个可配置的阈值,触发器告诉Snapshotter为聚合创建一个快照。快照触发器配置在事件源存储库中,并有一些属性允许您调整触发:>Snapshotter :设置实际的snapshotter实例,负责创建和存储实际快照事件;>Trigger:设置触发快照创建的阈值;Snapshotter负责实际创建快照。通常,快照是一个应该尽可能少地干扰操作过程的过程。因此,建议在不同的线程中运行snapshotter。Snapshotter接口声明了一个单一的方法:scheduleSnapshot(),它将聚合的类型和标识符作为参数。Axon提供了AggregateSnapshotter,它创建和存储AggregateSnapshot实例。这是一种特殊类型的快照,因为它包含实际的聚合实例。Axon提供的存储库能够识别这种类型的快照,并从它提取聚合,而不是实例化一个新的。在快照事件之后加载的所有事件都流向从中提取的聚合实例中。Note:一定要确保您所使用的序列化器实例(默认为XStreamSerializer)能够序列化您的聚合。XStreamSerializer要求您使用Hotspot JVM,或者您的聚合必须具有可访问的默认构造函数或实现Serializable接口。AbstractSnapshotter提供了一套基本属性,允许您调整创建快照的方式:>EventStore :设置用于加载过去事件并存储快照的事件存储。这个事件存储必须实现SnapshotEventStore接口。>Executor :设置executor,比如ThreadPoolExecutor提供了线程来处理实际快照的创建。默认情况下,快照的创建是在线程中调用scheduleSnapshot()方法,一般不建议用于生产。AggregateSnapshotter提供了一个更多的属性:>AggregateFactories:是允许你设置创建聚合实例工厂的属性。配置多个聚合工厂允许你使用一个单独的Snapshotter为各种聚合类型创建快照。EventSourcingRepository实现提供了访问他们使用的AggregateFactory。这可以用于配置相同的聚合工厂像在存储库中使用的Snapshotter一样。Note:如果您在另一个线程中使用执行快照创建的执行器,请确保在必要时为基础事件存储配置正确的事务管理。Spring用户可以使用SpringAggregateSnapshotter,当需要创建一个快照时,它将从应用程序上下文自动查找合适的AggregateFactory。

11.6.2 Storing Snapshot Events(存储快照事件)

当快照存储在事件存储中,它将自动使用该快照来总结所有先前的事件并将其返回到它们的位置。所有事件存储实现都允许并发创建快照。这意味着它们允许存储快照,而另一个进程为相同的聚合添加事件。这使得快照程序可以完全独立地运行。Note:通常,一旦它们是快照事件的一部分,您可以将所有事件存档。快照事件将永远不会在常规操作场景中再次读取事件存储。但是,如果您希望能够在创建快照之前重建聚合状态,那么您必须保持事件为最新的。Axon提供了一种特殊类型的快照事件:AggregateSnapshot,它将整个聚合作为快照存储。动机很简单:您的聚合应该只包含与业务决策相关的状态。这正是您希望在快照中捕获的信息。由Axon提供的所有事件源仓储都可以识别出AggregateSnapshot,并从中提取聚合。要注意,使用此快照事件需要事件序列化机制能够序列化聚合。

11.6.3 Initializing an Aggregate based on a Snapshot Event(初始化基于快照事件的聚合)

快照事件是像其他事件一样的事件。这意味着一个快照事件就像任何其他领域事件一样被处理。当使用注解来划分事件处理程序(@EventHandler)时,你可以注解一个方法,基于快照事件初始化全部的聚合状态。下面的代码示例演示了,如何像对待任何其他聚合中的领域事件一样对待快照事件。
public class MyAggregate extends AbstractAnnotatedAggregateRoot {    // ... code omitted for brevity    @EventHandler    protected void handleSomeStateChangeEvent(MyDomainEvent event) {        // ...    }    @EventHandler    protected void applySnapshot(MySnapshotEvent event) {        // the snapshot event should contain all relevant state        this.someState = event.someState;        this.otherState = event.otherState;    }}
有一种类型的快照事件处理方式不同:AggregateSnapshot。这种类型的快照事件包含实际的聚合。聚合工厂识别这种类型的事件并从快照中提取聚合。然后,将所有其他事件重新应用到提取的快照。这意味着聚合从不需要能够处理AggregateSnapshot实例自身。

11.6.4 Advanced conflict detection and resolution(高级冲突检测与解决)

明确变更含义的主要优点之一是,您可以更精确地检测冲突变化。通常,当两个用户同时执行相同的数据时,就会发生这些冲突的变化。假设有两个用户,他们都在查看特定版本的数据。他们俩都决定更改那个数据。他们将发送类似于“在这个聚合的版本X上,做那个”的命令,其中X是聚合的预期版本。其中一个将实际应用于预期的版本。其他用户不会。当聚合已经被另一个进程修改时,你可以检查用户的意图与任何看不见的修改是否冲突,而不是简单地拒绝所有传入命令。为了检测冲突,将ConflictResolver参数传递给您聚合的@ commandhandler方法。这个接口提供了detectconflict方法,允许您定义在执行特定类型的命令时被视为冲突的事件类型。Note:注意ConflictResolver只会包含任何潜在的冲突事件,如果聚合用一个预期的版本加载。使用@TargetAggregateVersion在一个命令的字段上标示聚合的预期的版本.如果找到匹配谓词的事件,则抛出异常(detectConflicts可选的第二个参数允许你定义抛出的异常)。如果没有找到,处理将继续正常进行。如果没有对detectConflicts进行调用,并且存在潜在的冲突事件,那么@ commandhandler将会失败。当提供了预期版本时,可能会出现这种情况,但@ commandhandler方法的参数中不存在冲突解决程序。
原创粉丝点击