Aggregate does not continue with the sequence number in the database but attempts to start with 0

Dear all,

I’m developing an application, which keeps dealers in PostgreSQL. My axon aggregate class uses an identifier, which is composed of dealers dispatch type and account number, which is a unique identifier defined by the business logic.

My app supports file import of dealers with the following logic:

  • New dealers are inserted into db (CreateDealerCommand)
  • Existing dealers are updated (UpdateDealerCommand)
  • Dealers missing in the file are removed from the database (DeleteDealerCommand)

Unfortunately, I have an issue with the scenario, where the dealer is firstly created using CreateDealerCommand, then removed using DeleteDealerCommand and lastly created again using CreateDealerCommand.
The first two commands (CreateDealerCommand and DeleteDealerCommand) are successfully executed and two events are stored in db, both with identifier 03013712 and increasing sequence (0 and 1).Výstřižek.PNG

However, when I try to create the dealer with identifier 03013712 again, I’m not able to continue in the sequence and the app starts a new sequence.

`

org.axonframework.modelling.command.ConcurrencyException: An event for aggregate [03013712] at sequence [0] was already inserted
at org.axonframework.eventsourcing.eventstore.AbstractEventStorageEngine.handlePersistenceException(AbstractEventStorageEngine.java:123)
at org.axonframework.eventsourcing.eventstore.jpa.JpaEventStorageEngine.lambda$appendEvents$6(JpaEventStorageEngine.java:286)
at org.axonframework.common.transaction.TransactionManager.executeInTransaction(TransactionManager.java:47)
at org.axonframework.eventsourcing.eventstore.jpa.JpaEventStorageEngine.appendEvents(JpaEventStorageEngine.java:279)
at org.axonframework.eventsourcing.eventstore.AbstractEventStorageEngine.appendEvents(AbstractEventStorageEngine.java:98)
at org.axonframework.eventsourcing.eventstore.AbstractEventStore.prepareCommit(AbstractEventStore.java:63)
at org.axonframework.eventhandling.AbstractEventBus.doWithEvents(AbstractEventBus.java:218)
at org.axonframework.eventhandling.AbstractEventBus.lambda$null$8(AbstractEventBus.java:152)
at org.axonframework.messaging.unitofwork.MessageProcessingContext.notifyHandlers(MessageProcessingContext.java:71)
at org.axonframework.messaging.unitofwork.DefaultUnitOfWork.notifyHandlers(DefaultUnitOfWork.java:106)
at org.axonframework.messaging.unitofwork.AbstractUnitOfWork.changePhase(AbstractUnitOfWork.java:222)
at org.axonframework.messaging.unitofwork.AbstractUnitOfWork.commitAsRoot(AbstractUnitOfWork.java:83)
at org.axonframework.messaging.unitofwork.AbstractUnitOfWork.commit(AbstractUnitOfWork.java:71)
at org.axonframework.messaging.unitofwork.DefaultUnitOfWork.executeWithResult(DefaultUnitOfWork.java:92)
at org.axonframework.commandhandling.SimpleCommandBus.handle(SimpleCommandBus.java:176)
at org.axonframework.commandhandling.AsynchronousCommandBus.lambda$handle$0(AsynchronousCommandBus.java:89)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: could not execute statement
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:154)
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:181)
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:188)
at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1460)
at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1440)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:308)
at com.sun.proxy.$Proxy158.flush(Unknown Source)
at org.axonframework.eventsourcing.eventstore.jpa.JpaEventStorageEngine.lambda$appendEvents$6(JpaEventStorageEngine.java:283)
… 17 common frames omitted
Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement
at org.hibernate.exception.internal.SQLStateConversionDelegate.convert(SQLStateConversionDelegate.java:112)
at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:42)
at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:113)
at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:99)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:178)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3176)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3690)
at org.hibernate.action.internal.EntityInsertAction.execute(EntityInsertAction.java:90)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:604)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:478)
at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:356)
at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39)
at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1454)
… 25 common frames omitted
Caused by: org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint “domain_event_entry_uq_aggregate_sequence”
Detail: Key (aggregate_identifier, sequence_number)=(03013712, 0) already exists.
at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2440)
at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2183)
at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:308)
at org.postgresql.jdbc.PgStatement.executeInternal(PgStatement.java:441)
at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:365)
at org.postgresql.jdbc.PgPreparedStatement.executeWithFlags(PgPreparedStatement.java:143)
at org.postgresql.jdbc.PgPreparedStatement.executeUpdate(PgPreparedStatement.java:120)
at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeUpdate(ProxyPreparedStatement.java:61)
at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeUpdate(HikariProxyPreparedStatement.java)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:175)
… 33 common frames omitted

`

I have the following aggregate to handle my commands

@Aggregate
@NoArgsConstructor
public class DealerAggregate {

    //dispatch type + account number
    @AggregateIdentifier
    private String composedCode;

    @CommandHandler
    public DealerAggregate(CreateDealerCommand cmd) {
        apply(new DealerCreatedEvent(cmd.getMetadata(), cmd.getDealer()));
    }

    @CommandHandler
    public void on(UpdateDealerCommand cmd){
        apply(new DealerUpdatedEvent(cmd.getMetadata(), cmd.getDealer()));
    }

    @CommandHandler
    public void on(DeleteDealerCommand cmd){
        apply(new DealerDeletedEvent(cmd.getMetadata(), cmd.getDealer()));
    }

    @EventSourcingHandler
    public void on(DealerCreatedEvent event) {
        composedCode = event.getDealer().getDispatchTypeCode() + event.getDealer().getAccountNumber();
    }
}

I tried to create a method like below, however, it’s never called.

`

    @CommandHandler
    public void on(CreateDealerCommand cmd){
        apply(new DealerCreatedEvent(cmd.getMetadata(), cmd.getDealer()));
    }

`

Do you please have any ideas on how to deal with this issue? I tried to create a new void method on(CreateDealerCommand) however, this method is never called.

Thank you in advance!
Best regards,
Jaroslav Schnaubert

Forgot to mention - I use axon version 4.1.1

Dne čtvrtek 5. března 2020 15:48:49 UTC+1 Jaroslav Schnaubert napsal(a):

The first event from the created command is treated special. It should always be the first event and can happen only once.
I think you have to check in the CreateDealerCommand if the dealer already exists (technically) and then apply a special event ( ‘DealerRecreated’ ? ) instead of the DealerCreatedEvent.

Ow, i think you even have to create a specialised command for it , because it can not be a constructor command again, so you have to do the check in the client.

Axon 4.3 has a creationPolicy Annotation. Maybe you can do something with that :
https://docs.axoniq.io/reference-guide/v/4.3/implementing-domain-logic/command-handling/aggregate#aggregate-command-handler-creation-policy

Hi,

Can we use this feature with the axon server 4.2.4? Means can we upgrade to axon framework 4.3 with any axon 4.x version?

Best, Michael

Hi Michael,

You can use creationPolicy feature with this version of the Axon Server as well.

Hi all,

Jaroslav, you are expecting to be able to reuse the aggregate identifier 03013712 because you did a “domain specific” delete of the aggregate.
However, as you are doing event sourcing, this is only a conceptual delete, but the fact it existed still remains.
Hence, the uniqueness constraint kicks in as soon as you try to create an aggregate with the same identifier.

We typically suggest to use a UUID as the aggregate identifier.
You are obviously free to use other formats than a UUID, but know that once used, you cannot delete the fact.

Hope this sheds some light on the situation.

Cheers,

Steven van Beelen

Axon Framework Lead Developer

AxonIQ

twitter-icon_128x128.png

Dear Gerlo, thanks, the creationPolicy works perfectly for my case!

Dne neděle 8. března 2020 13:43:22 UTC+1 Gerlo Hesselink napsal(a):