Axon(七)

七、saga

7.1 实现

Saga是一种特殊类型的事件侦听器:管理业务事务的侦听器。有些事务可以运行几天甚至几周,而另一些事务则在几毫秒内完成。在Axon中,Saga的每个实例都负责管理单个业务事务。这意味着Saga应该保持着管理该事务所必需的状态,或者采取补偿措施来回滚已经采取的任何操作。通常,与常规事件监听者相反,saga有起点和终点,两者都由事件触发。虽然一个saga的起点通常是非常清楚的,但是一个saga可以有很多种方式来结束。

在Axon中,saga是定义一个或多个@SagaEventHandler方法的类。与常规事件处理程序不同,saga的多个实例可能随时存在。saga由一个事件处理器(跟踪或订阅)管理,它专门处理特定saga类型的事件。

7.1.1 生命周期

单个Saga实例负责管理单个事务。这意味着你需要能够指出一个saga的生命周期的开始和结束。             

在saga中,事件处理程序用@SagaEventHandler注释。如果一个特定的事件表示一个事务的开始,那么在同一个方法中添加另一个注释:@StartSaga。此注释将创建一个新的saga,并在发布匹配事件时调用其事件处理程序方法。              默认情况下,只有在找不到合适的现有saga(相同类型)时,才会启动新的saga。还可以通过将@StartSaga注释上的forceNew属性设置为true来强制创建新的saga实例。             

结束一个传奇可以有两种方式。如果某个事件总是指示saga生命周期的结束,那么在saga上用@EndSaga注释该事件处理程序。在调用处理程序之后,它的生命周期将结束。或者,你可以打电话SagaLifecycle.结束()从传奇里结束生命周期。这允许你有条件地结束这段传奇。

7.1.2 事件处理

saga中的事件处理与普通事件侦听器相当。方法和参数解析的相同规则在这里有效。不过,有一个主要区别。虽然事件侦听器有一个处理所有传入事件的实例,但一个saga可能存在多个实例,每个实例对不同的事件感兴趣。例如,围绕Id为“1”的订单管理事务的saga不会对订单“2”的事件感兴趣,反之亦然。

Axon不会将所有事件发布到所有saga实例(这将完全浪费资源),而是只发布包含与saga关联的属性的事件。这是使用AssociationValues完成的。AssociationValue由键和值组成。键表示所使用的标识符的类型,例如“orderId”或“order”。该值表示上一示例中相应的值“1”或“2”。

@SagaEventHandler注释方法的求值顺序与@EventHandler方法的顺序相同(请参见带注释的事件处理程序)。如果处理程序方法的参数与传入事件匹配,并且saga与在处理程序方法上定义的属性有关联,则方法匹配。

@SagaEventHandler注释有两个属性,其中associationProperty是最重要的一个。这是应用于查找关联saga的传入事件的属性的名称。关联值的键是属性的名称。值是属性的getter方法返回的值。

例如,考虑一个方法字符串getOrderId()的传入事件,它返回“123”。如果接受此事件的方法用@SagaEventHandler(associationProperty=“orderId”)批注,则此事件将路由到与键为“orderId”且值为“123”的AssociationValue关联的所有saga。这可能是一个,多个,甚至一个都没有。

有时,要关联的属性的名称不是要使用的关联的名称。例如,您有一个将“Sell orders”与“Buy orders”匹配的Saga,您可以有一个包含“buyOrderId”和“sellOrderId”的事务对象。如果希望saga将“sellOrderId”值关联为“orderId”,可以在@SagaEventHandler注释中定义一个不同的keyName。然后它将变成@SagaEventHandler(associationProperty=“sellOrderId”,keyName=“orderId”)。

7.1.3注入资源

传奇故事通常不仅仅是根据事件来维持状态。它们与外部组件交互。为此,他们需要访问处理组件所需的资源。通常,这些资源并不是传奇故事和它的状态的一部分,这些资源不应该作为这样的持久性存在。然而,一旦一个saga被重建,这些资源必须在事件被路由到该实例之前被注入。

为此,有一个资源注入器。它被SagaRepository用来为saga注入资源。Axon提供了一个SpringResourceInjector,它用来自应用程序上下文的资源注入带注释的字段和方法。Axon还提供了一个SimpleResourceInjector,它将已经注册的资源注入到@Inject注释的方法和字段中。

小贴士

因为资源不应该与saga一起持久化,所以一定要在这些字段中添加transient关键字。这将阻止序列化机制尝试将这些字段的内容写入存储库。saga反序列化后,存储库将自动重新注入所需的资源。

SimpleResourceInjector允许注入预先指定的资源集合。它扫描Saga的(setter)方法和字段,以找到用@Inject注释的方法和字段。

使用配置API时,Axon将默认为ConfigurationResourceInjector。它将注入配置中的任何可用资源。默认情况下,EventBus、EventStore、CommandBus和CommandGateway等组件可用。也可以使用注册自己的组件configurer.registerComponent().

SpringResourceInjector使用Spring的依赖注入机制向Saga注入资源。这意味着您可以使用setter注入或直接现场注入,如果您需要的话。需要对要注入的方法或字段进行注释,以便Spring将其识别为依赖项,例如使用@Autowired。

7.1.4 saga基本结构

事件需要重定向到适当的saga实例。为此,需要一些基础结构类。最重要的组成部分是sagamanger和SagaRepository。

7.1.4.1 Saga管理器

与处理事件的任何组件一样,处理由事件处理器完成。然而,saga不是处理事件的单例实例。它们有需要管理的个体生命周期。

Axon通过annotatedsaganager支持生命周期管理,它提供给事件处理器来执行处理程序的实际调用。它是使用saga的类型来管理的,以及一个SagaRepository,其中可以存储和检索该类型的saga。一个annotatedsaganager只能管理一个saga类型。

使用配置API时,Axon将对大多数组件使用合理的默认值。但是,强烈建议定义要使用的SagaStore实现。SagaStore是一种“物理”存saga实例的机制。AnnotatedSagaRepository(默认)使用SagaStore存储和检索Saga实例。

Axon部分:

Configurer configurer = DefaultConfigurer.defaultConfiguration();

        configurer.eventProcessing(eventProcessingConfigurer -> eventProcessingConfigurer

                .registerSaga(MySaga.class,

                              // Axon defaults to an in-memory SagaStore,

                              // defining another is recommended

                              sagaConfigurer -> sagaConfigurer.configureSagaStore(c -> new JpaSagaStore(...))));

 

// alternatively, it is possible to register a single SagaStore for all Saga types:

configurer.registerComponent(SagaStore.class, c -> new JpaSagaStore(...));

springboot部分:

@Saga(sagaStore = "mySagaStore")

public class MySaga {...}

...

// somewhere in configuration

@Bean

public SagaStore mySagaStore() {

    return new MongoSagaStore(...); // default is JpaSagaStore

}

7.1.4.2 saga持久化和saga存储

SagaRepository负责存储和检索sagas,供SagaManager使用。它能够通过标识符和关联值检索特定的saga实例。

不过,也有一些特殊要求。由于sagas中的并发处理是一个非常微妙的过程,存储库必须确保对于每个概念saga实例(具有相同的标识符),JVM中只存在一个实例。

Axon提供了AnnotatedSagaRepository实现,它允许查找saga实例,同时保证只能同时访问saga的单个实例。它使用SagaStore来执行saga实例的实际持久性。

要使用的实现的选择主要取决于应用程序使用的存储引擎。Axon提供JdbcSagaStore、InMemorySagaStore、JpaSagaStore和MongoSagaStore。

在某些情况下,应用程序可以从缓存saga实例中获益。在这种情况下,有一个cachingsagstore,它包装了另一个实现来添加缓存行为。请注意,cachingsagstore是一个直写缓存,这意味着保存操作总是立即转发到备份存储,以确保数据安全。

  1. JpaSagaStore

JpaSagaStore使用JPA存储saga的状态和关联值。saga本身不需要任何JPA注释;Axon将使用序列化器序列化saga(类似于事件序列化,您可以在XStreamSerializer、JacksonSerializer或JavaSerializer之间进行选择,这可以通过在应用程序中配置默认序列化程序来设置。有关详细信息,请参阅序列化程序。

JpaSagaStore配置了EntityManagerProvider,它提供了对要使用的EntityManager实例的访问。这种抽象允许同时使用应用程序管理的和容器管理的EntityManager。或者,您可以定义序列化程序来序列化Saga实例。Axon默认为XStreamSerializer。

  1. JdbcSagaStore

JdbcSagaStore使用普通JDBC来存储阶段实例及其关联值。与JpaSagaStore类似,saga实例不需要知道它们是如何存储的。它们是使用序列化程序序列化的。

JdbcSagaStore是用DataSource或ConnectionProvider初始化的。虽然不是必需的,但在使用ConnectionProvider初始化时,建议将实现包装在UnitOfWorkAwareConnectionProviderWrapper中。它将检查当前工作单元中已打开的数据库连接,以确保工作单元中的所有活动都在单个连接上完成。

与JPA不同,JdbcSagaRepository使用纯SQL语句来存储和检索信息。这可能意味着某些操作依赖于特定于数据库的SQL方言。也可能是某些数据库供应商提供了您想要使用的非标准特性。为此,您可以提供自己的SagaSqlSchema。SagaSqlSchema是一个接口,它定义了存储库需要在底层数据库上执行的所有操作。它允许您自定义为每个操作执行的SQL语句。默认值是GenericSagaSqlSchema。其他可用的实现有postgresgasqlschema、Oracle11SagaSqlSchema和HsqlSagaSchema。

  1. MongoSagaStore

MongoSagaStore将saga实例及其关联存储在MongoDB数据库中。MongoSagaStore将所有saga存储在MongoDB数据库的单个集合中。对于每个saga实例,都会创建一个文档。

MongoSagaStore还确保在任何时候,在一个JVM中,对于任何惟一的Saga,只存在一个Saga实例。这确保不会因为并发问题而丢失状态更改。

mongosgastore使用MongoTemplate和可选的序列化程序初始化。MongoTemplate提供了对存储saga的集合的引用。Axon提供了DefaultMongoTemplate,它接受一个MongoClient实例以及存储saga的数据库名和集合名。数据库名和集合名可以省略。在这种情况下,它们分别默认为“axonframework”和“sagas”。

  1. 缓存

如果使用数据库支持的saga存储,保存和加载saga实例可能是一个相对昂贵的操作。在短时间内多次调用同一个saga实例的情况下,缓存对应用程序的性能尤其有利。

Axon提供了cachingsagstore实现。这是一个saga存储,包装了另一个,它做实际的存储。当加载saga或关联值时,cachingsagstore将首先查询其缓存,然后再委托给包装的存储库。在存储信息时,总是委派所有调用,以确保备份存储始终对saga的状态具有一致的视图。

要配置缓存,只需将任何SagaStore包装在CachingSagaStore中。cachingsagstore的构造函数接受三个参数:1要包装的saga存储器2要用于关联值的缓存3用于saga实例的缓存。后两个参数可以引用同一个缓存,也可以引用不同的缓存。这取决于特定应用程序的要求。

7.2关联

当saga跨多个域概念(如订单、装运、发票等)管理事务时,saga需要与这些概念的实例相关联。关联需要两个参数:标识关联类型(订单、装运等)的键和表示该概念标识符的值。

将一个saga与一个概念联系起来有几种方法。首先,当调用@StartSaga带注释的事件处理程序时,Saga会自动与@SagaEventHandler方法中标识的属性相关联。任何其他关联都可以使用SagaLifecycle.关联(字符串键,字符串/数字值)方法。使用SagaLifecycle.RemoveAssociation与(字符串键,字符串/数字值)删除特定关联的方法。

注意

在Saga中关联域概念的API特意只允许字符串或数字作为标识值,因为存储的关联值条目需要标识符的字符串表示。在API中使用简单的标识符值和简单的字符串表示是按设计的,因为数据库中的字符串列条目可以简化数据库引擎之间的比较。例如,由于Object#toString()调用的结果可能会提供难以处理的标识符,因此没有与(String,Object)关联。

想象一下,有一个saga是为一个关于订单的交易而创建的。saga会自动与订单相关联,因为方法是用@StartSaga注释的。saga负责为该订单创建费用清单,并为发货站创建了一次发货业务。一旦货物到达并且买家支付了费用,交易就完成了,saga也就结束了。

这是这段saga的代码:

public class OrderManagementSaga {

 

    private boolean paid = false;

    private boolean delivered = false;

    @Inject

    private transient CommandGateway commandGateway;

 

    @StartSaga

    @SagaEventHandler(associationProperty = "orderId")

    public void handle(OrderCreatedEvent event) {

        // client generated identifiers

        ShippingId shipmentId = createShipmentId();

        InvoiceId invoiceId = createInvoiceId();

        // associate the Saga with these values, before sending the commands

        SagaLifecycle.associateWith("shipmentId", shipmentId);

        SagaLifecycle.associateWith("invoiceId", invoiceId);

        // send the commands

        commandGateway.send(new PrepareShippingCommand(...));

        commandGateway.send(new CreateInvoiceCommand(...));

    }

 

    @SagaEventHandler(associationProperty = "shipmentId")

    public void handle(ShippingArrivedEvent event) {

        delivered = true;

        if (paid) { SagaLifecycle.end(); }

    }

 

    @SagaEventHandler(associationProperty = "invoiceId")

    public void handle(InvoicePaidEvent event) {

        paid = true;

        if (delivered) { SagaLifecycle.end(); }

    }

    // ...

}

通过允许客户端生成标识符,saga可以轻松地与概念相关联,而不需要请求-响应类型的命令。在发布命令之前,我们将事件与这些概念相关联。这样,我们就可以保证捕获响应命令而生成的事件。一旦订单支付,货物到达,saga就将结束。

7.3 基本结构

事件需要重定向到适当的saga实例。为此,需要一些基础结构类。最重要的组成部分是sagamanager和SagaRepository。

7.3.1sagamanager

与处理事件的任何组件一样,处理由事件处理器完成。然而,saga不是处理事件的单例实例。它们有需要管理的个体生命周期。

Axon通过annotatedsagamanager支持生命周期管理,它提供给事件处理器来执行处理程序的实际调用。它是使用saga的类型来管理的,以及一个SagaRepository,其中可以存储和检索该类型的saga。一个annotatedsagamanager只能管理一个saga类型。

使用配置API时,Axon将对大多数组件使用合理的默认值。但是,强烈建议定义要使用的SagaStore实现。SagaStore是一种“物理”存储saga实例的机制。AnnotatedSagaRepository(默认)使用SagaStore存储和检索Saga实例。

Axon部分

Configurer configurer = DefaultConfigurer.defaultConfiguration();

        configurer.eventProcessing(eventProcessingConfigurer -> eventProcessingConfigurer

                .registerSaga(MySaga.class,

                              // Axon defaults to an in-memory SagaStore,

                              // defining another is recommended

                              sagaConfigurer -> sagaConfigurer.configureSagaStore(c -> new JpaSagaStore(...))));

 

// alternatively, it is possible to register a single SagaStore for all Saga types:

configurer.registerComponent(SagaStore.class, c -> new JpaSagaStore(...));

springboot部分

@Saga(sagaStore = "mySagaStore")

public class MySaga {...}

...

// somewhere in configuration

@Bean

public SagaStore mySagaStore() {

    return new MongoSagaStore(...); // default is JpaSagaStore

}

7.3.2saga持久化与saga存储

上面已经论述过了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值