Some questions that keep popping up…

I'm getting an exception: Deadlock found when trying to get lock; try restarting transaction

This exception sounds scary, but fortunately, the solution is simple. Database engines use locks when modifying rows in a table. If there is no index the database can use to pinpoint the rows to modify, it needs a table scan, and thus a lock on the entire table. If data is read from that same table from multiple connection, it is possible that the database is not able to get a write lock at all, and will time out.

When a Saga is deleted in Axon, or an old snapshot event needs to be purged, one or more rows are deleted. If there is a proper index on the table, the database only needs to lock the rows to be deleted. They're probably not read anyway, since it's old data.

The solution: make sure the proper indices are on your tables. You can find more information about the required indices in the Reference Guide, Chapter "Performance Tuning".

Below is a sample stacktrace (with a MySQL database):

javax.persistence.PersistenceException: org.hibernate.exception.LockAcquisitionException: Deadlock found when trying to get lock; try restarting transaction
	at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1360)
	at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1288)
	at org.hibernate.ejb.AbstractEntityManagerImpl.throwPersistenceException(AbstractEntityManagerImpl.java:1370)
	at org.hibernate.ejb.AbstractQueryImpl.executeUpdate(AbstractQueryImpl.java:108)
	at org.axonframework.saga.repository.jpa.JpaSagaRepository.deleteSaga(JpaSagaRepository.java:147)
	at org.axonframework.saga.repository.AbstractSagaRepository.commit(AbstractSagaRepository.java:123)
	at org.axonframework.saga.repository.jpa.JpaSagaRepository.commit(JpaSagaRepository.java:88)
	at org.axonframework.saga.AbstractSagaManager.commit(AbstractSagaManager.java:140)
	at org.axonframework.saga.AbstractSagaManager.invokeSagaHandler(AbstractSagaManager.java:121)
	at org.axonframework.saga.AbstractSagaManager.access$200(AbstractSagaManager.java:43)
	at org.axonframework.saga.AbstractSagaManager$SagaInvocationTask.run(AbstractSagaManager.java:215)
	at org.axonframework.saga.SynchronousSagaExecutionWrapper.scheduleEventProcessingTask(SynchronousSagaExecutionWrapper.java:34)
	at org.axonframework.saga.AbstractSagaManager$SagaLookupAndInvocationTask.run(AbstractSagaManager.java:245)
	at org.axonframework.saga.SynchronousSagaExecutionWrapper.scheduleLookupTask(SynchronousSagaExecutionWrapper.java:29)
	at org.axonframework.saga.AbstractSagaManager.handle(AbstractSagaManager.java:90)
	at org.axonframework.eventhandling.SimpleEventBus.publish(SimpleEventBus.java:110)
	at org.axonframework.unitofwork.DefaultUnitOfWork$EventEntry.publishEvent(DefaultUnitOfWork.java:249)
	at org.axonframework.unitofwork.DefaultUnitOfWork.publishEvents(DefaultUnitOfWork.java:180)
	at org.axonframework.unitofwork.DefaultUnitOfWork.doCommit(DefaultUnitOfWork.java:83)
	at org.axonframework.unitofwork.AbstractUnitOfWork.commit(AbstractUnitOfWork.java:54)
	at org.axonframework.commandhandling.SimpleCommandBus.doDispatch(SimpleCommandBus.java:125)
	at org.axonframework.commandhandling.SimpleCommandBus.dispatch(SimpleCommandBus.java:78)
	at org.axonframework.commandhandling.AsynchronousCommandBus$DispatchCommand.run(AsynchronousCommandBus.java:91)
	at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
	at java.lang.Thread.run(Thread.java:662)
Caused by: org.hibernate.exception.LockAcquisitionException: Deadlock found when trying to get lock; try restarting transaction
	at org.hibernate.exception.internal.SQLExceptionTypeDelegate.convert(SQLExceptionTypeDelegate.java:95)
	at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:49)
	at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:125)
	at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:110)
	at org.hibernate.engine.jdbc.internal.proxy.AbstractStatementProxyHandler.continueInvocation(AbstractStatementProxyHandler.java:129)
	at org.hibernate.engine.jdbc.internal.proxy.AbstractProxyHandler.invoke(AbstractProxyHandler.java:81)
	at $Proxy86.executeUpdate(Unknown Source)
	at org.hibernate.hql.internal.ast.exec.BasicExecutor.execute(BasicExecutor.java:95)
	at org.hibernate.hql.internal.ast.QueryTranslatorImpl.executeUpdate(QueryTranslatorImpl.java:413)
	at org.hibernate.engine.query.spi.HQLQueryPlan.performExecuteUpdate(HQLQueryPlan.java:283)
	at org.hibernate.internal.SessionImpl.executeUpdate(SessionImpl.java:1181)
	at org.hibernate.internal.QueryImpl.executeUpdate(QueryImpl.java:116)
	at org.hibernate.ejb.QueryImpl.internalExecuteUpdate(QueryImpl.java:183)
	at org.hibernate.ejb.AbstractQueryImpl.executeUpdate(AbstractQueryImpl.java:99)
	... 22 more
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
	at com.mysql.jdbc.Util.handleNewInstance(Util.java:411)
	at com.mysql.jdbc.Util.getInstance(Util.java:386)
	at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1064)
	at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3609)
	at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3541)
	at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2002)
	at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2163)
	at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2624)
	at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:2127)
	at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2427)
	at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2345)
	at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2330)
	at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeUpdate(NewProxyPreparedStatement.java:105)
	at sun.reflect.GeneratedMethodAccessor90.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:597)
	at org.hibernate.engine.jdbc.internal.proxy.AbstractStatementProxyHandler.continueInvocation(AbstractStatementProxyHandler.java:122)
	... 31 more

How can I identify entities within an aggregate?

I have an entity in my aggregate, which the aggregate root has a one-to-many relationship with. If I want to target one of these entities in a command, how do I best identify it?

Imagine there is an Order aggregate, with two entities: Order, and OrderLine. The Order entity is the Aggregate root. Generally, your application will add, remove and edit OrderLines from an order during its lifecycle.

According to DDD (and general best practices), entities other than the aggregate root should not be directly referenced from outside the aggregate. That means that the OrderLine identifier should always be used in the context of an Order. In practical terms, that means that you should always pass both the OrderId and the OrderLineId, if you want to do something with an OrderLine. The OrderId is used to locate the Aggregate (Order), which is then asked to perform whatever operation on and OrderLine, identified with the OrderLineId.

There are roughly three options for choosing identifiers: random, hash or sequential. The advantage of random and hash identifiers is that the client can generate the identifier, and reference an instance without the need to wait for the result of the creation command.

Important note Never use identifiers generated by the query database to identify instances of entities. Since these identifiers are not part of the command model, you will end up with events that you cannot replay. Whatever you do, make sure the Aggregate knows about the identifiers used.

If you found yourself ending up with a list of entities without their own identifier (for example because you never anticipated a requirement to edit exiting items), you're going to need to generate identifiers for the past events. You're probably best off with a hash based identifier for those entities. Calculate the hash based on immutable information in the entity. When you add the identifier to the event, you can create an upcaster that builds the ID based on information available inside the event.

How do I enhance events with data that is not part of the Aggregate?

There are several possibilities to do this:

  1. Include non-stateful attribute in the Aggregate
    public class City extends AbstractAnnotatedAggregateRoot {
    
        // Name would normally not be included as it's not
        // necessary for the state of the object
        private String name;
    
        public City(AggregateIdentifier identifier) {
            super(identifier);
        }
    
        public City(AggregateIdentifier identifier, String name) {
            super(identifier);
            // Easy as the name is already an argument
            apply(new CityCreatedEvent(name));
        }
    
        public void remove() {
            // Here we can use the stored name to enhance the event
            apply(new CityRemovedEvent(name));
        }
    
        @EventHandler
        public void handle(CityCreatedEvent event) {
            // Store the name
            this.name = event.getName();
        }
    }
  2. Include immutable (!) data from other aggregate as attribute
    public class City extends AbstractAnnotatedAggregateRoot {
        // Reference to a country aggregate
        private UUID countryUUID;
    
        // Immutable (!) name of the country
        private String countryName;
    
        // Name would normally not be included as it's not
        // necessary for the state of the object
        private String cityName;
    
        public City(AggregateIdentifier identifier) {
            super(identifier);
        }
    
        public City(AggregateIdentifier identifier, UUID countryUUID, String countryName, String cityName) {
            super(identifier);
            // This event is easy as the names are already an arguments
            apply(new CityCreatedEvent(countryUUID, countryName, cityName));
        }
    
        public void remove() {
            // Here we can use the country and city name
            apply(new CityRemovedEvent(countryName, cityName));
        }
    
        @EventHandler
        public void handle(CityCreatedEvent event) {
            this.countryUUID = event.getCountryUUID();
            this.countryName = event.getCountryName();
            this.cityName = event.getCountryName();
        }
    }
  3. Query data in the command handler and pass it as an argument
    @Named
    public class CityCommandHandler {
    
        @Inject
        @Named("cityRepository")
        private Repository repository;
    
        @Inject
        private QueryService queryService;
    
        @CommandHandler
        public void handle(CreateCityCommand command) {
    
            // Checks the country reference exists and returns the name
            Country country = queryService.loadCountry(command.getCountryUUID());
    
            // Create the aggregate using the loaded country name
            City city = new City(new UUIDAggregateIdentifier(command.getCityUUID()), country.getUUID(), country.getName(), command.getCityName());
            repository.add(city);
    
        }
    
        @CommandHandler
        public final void handle(RemoveCityCommand command) {
    
            // Checks the country reference exists and returns the name
            Country country = queryService.loadCountry(command.getCountryUUID());
    
            // Load the city
            City city = repository.load(new UUIDAggregateIdentifier(command.getCityUUID()));
    
            // Use the name from the previous query
            city.remove(country.getName());
    
        }
    
    }
    public class City extends AbstractAnnotatedAggregateRoot {
    
        // Reference to a country aggregate.
        private UUID countryUUID;
    
        // Note, that the country name is NOT stored
        // as it is considered mutable
    
        // Name of the city for the event
        private String cityName;
    
        public City(AggregateIdentifier identifier) {
            super(identifier);
        }
    
        public City(AggregateIdentifier identifier, UUID countryUUID, String countryName, String cityName) {
            super(identifier);
            // Easy as the name is already an argument
            apply(new CityCreatedEvent(countryUUID, countryName, cityName));
        }
    
        // NOTE: Method signature looks strange! Doesn't it?
        public void remove(String countryName) {
            // Here we can use the name from the argument
            // and the stored city name
            apply(new CityRemovedEvent(countryName, cityName));
        }
    
        @EventHandler
        public void handle(CityCreatedEvent event) {
            this.countryUUID = event.getCountryUUID();
            this.cityName = event.getCityName();
        }
    
    }
  4. Include data in the command and pass it as an argument
    @Named
    public class CityCommandHandler {
    
        @Inject
        @Named("cityRepository")
        private Repository repository;
    
        @Inject
        private QueryService queryService;
    
        @CommandHandler
        public void handle(CreateCityCommand command) {
    
            // Checks the country reference exists and returns the name
            Country country = queryService.loadCountry(command.getCountryUUID());
    
            // Create the aggregate using the loaded country name
            City city = new City(new UUIDAggregateIdentifier(command.getCityUUID()), country.getUUID(), country.getName(), command.getCityName());
            repository.add(city);
    
        }
    
        @CommandHandler
        public final void handle(RemoveCityCommand command) {
    
            // Load the city
            City city = repository.load(new UUIDAggregateIdentifier(command.getCityUUID()));
    
            // Command includes the country name for the argument
            city.remove(command.getCountryName());
    
        }
    
    }
  5. Query data in the aggregate's method using an injected service
    @Named
    public class CityCommandHandler {
    
        @Inject
        @Named("cityRepository")
        private Repository repository;
    
        @Inject
        private QueryService queryService;
    
        @CommandHandler
        public void handle(CreateCityCommand command) {
    
            // Checks implicitly the country reference and loads the name
            String countryName = queryService.loadCountryName(command.getCountryUUID());
    
            // Create the aggregate using the loaded country name
            City city = new City(new UUIDAggregateIdentifier(command.getCityUUID()), command.getCountryUUID(), countryName, command.getCityName());
            repository.add(city);
    
        }
    
        @CommandHandler
        public final void handle(RemoveCityCommand command) {
    
            // Load the city
            City city = repository.load(new UUIDAggregateIdentifier(command.getCityUUID()));
    
            // Inject the query service into the aggregate
            city.setQueryService(queryService);
    
            // Inside this method the name will be queried
            city.remove();
        }
    }
    public class City extends AbstractAnnotatedAggregateRoot {
    
        // Reference to a country aggregate.
        private UUID countryUUID;
    
        // Note, that the country name is NOT stored
        // as it is considered mutable
    
        // Name of the city for the event
        private String cityName;
    
        // Query service used to load missing data
        private transient QueryService queryService;
    
        public City(AggregateIdentifier identifier) {
            super(identifier);
        }
    
        public City(AggregateIdentifier identifier, UUID countryUUID, String countryName, String cityName) {
            super(identifier);
            // Easy as the name is already an argument
            apply(new CityCreatedEvent(countryUUID, countryName, cityName));
        }
    
        public void remove() {
            // Load the name and include it in the event
            String countryName = queryService.loadCountryName(countryUUID);
            apply(new CityRemovedEvent(countryName, cityName));
        }
    
        public void setQueryService(QueryService queryService) {
            this.queryService = queryService;
        }
    
        @EventHandler
        public void handle(CityCreatedEvent event) {
            this.countryUUID = event.getCountryUUID();
            this.cityName = event.getCityName();
        }
    
    }
  6. Query data in the aggregate's method using a method specific query service. This was suggested by Greg Young (Course in Hamburg, September 2011) to make more explicit that an aggregate method uses a query.
    @Named
    public class CityCommandHandler {
    
        @Inject
        @Named("cityRepository")
        private Repository repository;
    
        @Inject
        private QueryService queryService;
    
        @CommandHandler
        public void handle(CreateCityCommand command) {
    
            // Checks implicitly the country reference and loads the name
            String countryName = queryService.loadCountryName(command.getCountryUUID());
    
            // Create the aggregate using the loaded country name
            City city = new City(new UUIDAggregateIdentifier(command.getCityUUID()), command.getCountryUUID(), countryName, command.getCityName());
            repository.add(city);
        }
    
        @CommandHandler
        public final void handle(RemoveCityCommand command) {
    
            // Load the city
            City city = repository.load(new UUIDAggregateIdentifier(command.getCityUUID()));
    
            // Provide a method specific query service
            city.remove(new CityRemoveQueryService() {
                public String loadCountryName(UUID countryUUID) {
                    // In this case we simply map the call to the common query service
                return queryService.loadCountryName(countryUUID);
                }
            });
    
        }
    
    }
    public class City extends AbstractAnnotatedAggregateRoot {
    
        // Reference to a country aggregate.
        private UUID countryUUID;
    
        // Note, that the country name is NOT stored
        // as it is considered mutable
    
        // Name of the city for the event
        private String cityName;
    
        public City(AggregateIdentifier identifier) {
            super(identifier);
        }
    
        public City(AggregateIdentifier identifier, UUID countryUUID, String countryName, String cityName) {
            super(identifier);
            // Easy as the name is already an argument
            apply(new CityCreatedEvent(countryUUID, countryName, cityName));
        }
    
        public void remove(CityRemoveQueryService queryService) {
            // Load the name and include it in the event
            String countryName = queryService.loadCountryName(countryUUID);
            apply(new CityRemovedEvent(countryName, cityName));
        }
    
        @EventHandler
        public void handle(CityCreatedEvent event) {
            this.countryUUID = event.getCountryUUID();
            this.cityName = event.getCityName();
        }
    
        // Explicit query service for the delete method
        public static interface CityRemoveQueryService {
            public String loadCountryName(UUID countryUUID);
        }
    
    }

Caution

Never do any queries in the an Event Handler method in an Aggregate! Replaying the events at a later time may else lead to a different event content.