Instantâneos de eventos no Axonframework 3, melhorando o desempenho

Visão geral da estrutura Axonframework


Axonframework é uma estrutura que implementa vários princípios e padrões de design, como:

CQRS - Separa o processamento de solicitações de leitura e gravação de dados
Event Sourcing é quando o estado de um aplicativo é armazenado como uma cadeia de eventos.
Agregado DDD - um objeto de domínio que armazena estado

Uma das desvantagens de armazenar o estado final de um aplicativo na forma de uma cadeia de eventos é o número de eventos armazenados e processados. Felizmente, o Axonframework permite criar um evento de instantâneo que contém o resultado de vários eventos (evento de domínio).

Instantâneos de eventos


Evento de instantâneo - esses são os valores resultantes de vários eventos (evento de domínio). Isso permite recriar rapidamente o estado do agregado. É importante entender que um instantâneo é criado a partir de eventos que foram usados ​​para uma unidade específica com um identificador exclusivo.

Por exemplo ( Fig. 1 ), definimos a configuração para criar um instantâneo para cada dois eventos (limite = 2 - para fins ilustrativos). Nesse caso, quando dois eventos alteram o estado da unidade, uma imagem será criada com os valores resultantes dos dois eventos anteriores.


Figura 1. Uma captura instantânea de dois eventos. (limite = 2)

Considere um exemplo mais complicado ( Fig. 2 ), a configuração também especifica um limite de 2 para que um instantâneo seja criado a cada dois eventos. Quando 2 eventos mudarem o estado da unidade, uma imagem será criada. Além disso, os outros 2 eventos alteram o estado da Unidade e uma nova imagem não é criada, mas a existente é atualizada.


Fig. 2 O resultado da cadeia de eventos em uma imagem (limiar = 2)

Desempenho


Por um lado, quando uma longa cadeia de eventos se acumula em um aplicativo, leva tempo para ler e processar um grande número de eventos para recriar o estado da Unidade. Por outro lado, se você criar um instantâneo, o estado da Unidade será recriado rapidamente, mas levará tempo para criar um instantâneo. É preciso encontrar um equilíbrio entre essas duas situações.


Fig. 3 Desempenho sem tirar um instantâneo


Fig. 4 Desempenho com instantâneo (limite = 3)

Por padrão, um instantâneo é criado no encadeamento que chamou o método scheduleSnapshot (). Essa configuração não é recomendada para um ambiente de combate (consulte a Fig. 4 / entrada).

Abaixo está um exemplo de código usando ThreadPoolExecutor (...) que fornecerá um thread separado para a criação de um instantâneo. Nesse caso, nosso cliente não notará uma desaceleração no aplicativo e o tempo alocado para criar um instantâneo.

Código


Para ativar a criação de instantâneos, você precisa fazer pequenas alterações no código do aplicativo. A anotação agregada indica o nome do repositório usado no código da classe de configuração. Na classe de configuração, são indicados o limite para a criação de capturas instantâneas, o método para a criação de capturas instantâneas, repositórios etc.

AxonConfig.java

@Autowired private EventStore eventStore; @Bean public SpringAggregateSnapshotterFactoryBean springAggregateSnapshotterFactoryBean() { return new SpringAggregateSnapshotterFactoryBean(); } @Bean public SpringAggregateSnapshotter snapshotter(ParameterResolverFactory parameterResolverFactory, EventStore eventStore, TransactionManager transactionManager) { Executor executor = Executors.newFixedThreadPool(10); return new SpringAggregateSnapshotter(eventStore, parameterResolverFactory, executor, transactionManager); } @Bean("reservationRepository") public EventSourcingRepository<Reservation> reservationRepository(Snapshotter snapshotter, ParameterResolverFactory parameterResolverFactory) { return new EventSourcingRepository<Reservation>(reservationAggregateFactory(), eventStore, parameterResolverFactory, new EventCountSnapshotTriggerDefinition(snapshotter, 50)); } @Bean(name = "reservationAggregateFactory") public AggregateFactory<Reservation> reservationAggregateFactory() { SpringPrototypeAggregateFactory<Reservation> aggregateFactory = new SpringPrototypeAggregateFactory<>(); aggregateFactory.setPrototypeBeanName("reservation"); return aggregateFactory; } 

Reservation.java

 @Aggregate(repository = "reservationRepository") public class Reservation { //… } 

Vale ressaltar que o tópico de discussão dos Grupos do Google contém exemplos e discussões úteis de código.

Selecionar limite para capturas instantâneas



5.1 Maneira teórica

Vamos calcular o número de eventos que podem ser aplicados à unidade na classe EventListener. Em seguida, estimamos teoricamente o número médio de eventos aplicados à Unidade em uma situação típica e definimos um valor um pouco menor que esse como o limite para a criação de imagens. Isso pode ser feito se o aplicativo acabou de ser criado e não houver dados reais para análise.

5.2 Maneira prática

Analisamos os dados do banco de dados e assumimos que o banco de dados é usado pelo MongoDB e funciona dentro do contêiner do docker.

 > docker exec -it <container-id> mongo > show dbs admin 0.000GB axonframework 0.000GB local 0.000GB > use axonframework switched to db axonframework > show collections domainevents sagas snapshotevents > db.domainevents.findOne() { “_id” : ObjectId(“5bb1dc8d4446d63bcc765feb”), “aggregateIdentifier” : “b1e320d5–58aa-4b9b-a667-aa724900592f”, “type” : “Reservation”, “sequenceNumber” : NumberLong(0), “serializedPayload” : “<com.example.ReservationStarted><reservationIdentifier>b1e320d5–58aa-4b9b-a667-aa724900592f</reservationIdentifier><duration resolves-to=\”java.time.Ser\”><byte>1</byte><long>2400</long><int>0</int></duration></com.example.ReservationStarted>”, “timestamp” : “2018–10–01T08:36:29.434Z”, “payloadType” : “com.example.ReservationStarted”, “payloadRevision” : null, “serializedMetaData” : “<meta-data><entry><string>traceId</string><string>b090b86a-ec89–484b-ae9f-e4fa0f9bcd39</string></entry><entry><string>correlationId</string><string>b090b86a-ec89–484b-ae9f-e4fa0f9bcd39</string></entry></meta-data>”, “eventIdentifier” : “f324f021–50b4–4e91–84d0-f8c4425f3eb9” } 

Cada evento armazenado contém um campo agregateIdentifier, pelo qual calculamos o número de eventos aplicados a cada agregado com uma consulta simples:

 db.domainevents.aggregate([ {$group: {_id: "$aggregateIdentifier", count: {$sum: 1} } }, {$sort : {count : -1} } ]); { "_id" : "0d84afd1-f199-45c8-b50e-7d9ebfa4c8fb", "count" : 136 } { "_id" : "49de7c32-38ea-435a-b837-ccdb61ec0baa", "count" : 136 } { "_id" : "12957b0b-af05-47c4-a3d8-968b75cf9ffb", "count" : 136 } { "_id" : "97a24559-ee3a-43e7-a6be-1eb6840b662a", "count" : 132 } { "_id" : "b6aeb1af-0620-4b02-8de3-c2446c2f7d83", "count" : 132 } { "_id" : "b385aaf4-3338-489f-8d1b-4600d5e088b9", "count" : 132 } { "_id" : "5970327f-9551-4945-94e9-3844c0cd3543", "count" : 132 } ... { "_id" : "0182239h-3948-3334-98t5-9643j4ld8346", "count" : 1 } 

O limite para a criação de capturas instantâneas pode ser selecionado abaixo da média para que as capturas instantâneas sejam criadas com eficiência. Nesse caso, um valor de 50 é bom.

Verificando a ativação da captura instantânea


 > mongo > show dbs admin 0.000GB axonframework 0.000GB local 0.000GB > use axonframework > show collections domainevents sagas snapshotevents > db.domainevents.count() 515 > db.snapshotevents.count() 7 

Se a coleção de eventos de captura instantânea não estiver vazia e contiver capturas instantâneas, a criação da captura instantânea foi ativada com sucesso.

Outras opções de captura instantânea


A documentação menciona outras variações sobre como ativar a criação de instantâneo, por exemplo:

  • o número de eventos gerados desde a última captura instantânea excedeu o limite
  • Tempo limite de inicialização da unidade expirado
  • atraso de tempo, etc. etc.

Source: https://habr.com/ru/post/pt435522/


All Articles